Skip to content

Save Visit Appointments

Request

Save one or more Appointments of one visit together with their Service lines and Product Items. The web app calls this operation when a Business User clicks Save in the appointment window.

This is a replace operation. Each object in records replaces the whole Appointment it identifies: date, Team Member, duration, resources, Tags, color, comment, walk-in visitor, Custom Fields, reminder settings, assistants, Service lines, and Product Items. There is no append or partial update. Read the visit first and send every key back exactly as read, changing only what you mean to change.

The legacy path POST /companies/{company_id}/attendance/records reaches the same handler. It is deprecated since 2026-07-01 and sunsets on 2027-03-01; use the canonical path. The v3 operation addVisitItem is planned as the append-only contract for adding one Product Item to a visit. It is not served yet.

Read, modify, write

  1. Read the visit with List Attendance Appointments, using attendance_id = record_{appointment_id} and these includes: attendance_service_items, attendance_service_items.assistants, attendance_service_items.attendance_service_composite.composite_service, attendance_good_items, attendance_good_items.marks, attendance_good_items.loyalty_abonement, attendance_good_items.loyalty_certificate, resource_instances, labels, comer, custom_field_values, client_notification_settings, and duration_details.
  2. Build one record for each Appointment you change, using the mapping below. Leave the other Appointments of the visit out.
  3. To add a Product Item, append a line with id: null. Keep every existing line with its id and its values unchanged.
  4. Send is_instant_sms_notification_required: false and ignore_conflicts: false.
  5. Request include: ["attendance_good_items"] and read the Product Items back from the response: loyalty programs may change their totals after the save.
Request keySource in the read
id, external_id, staff_id, date, duration, comment, custom_color, clients_count, attendance_status_slugAppointment attributes with the same names
client_idAppointment client_id; null when it is 0
technical_break_durationduration_details.technical_break_duration
resource_instance_ids, label_idsIDs of the included resource_instances and labels
comerIncluded comer (id, title) plus slug and type; null when absent
custom_field_valuesIncluded custom_field_values as code and value
client_notification_settingsIncluded client_notification_settings
assistantsattendance_service_items.assistants, with the line's service_id
attendance_service_items[]id, service_id, quantity, cost_per_unit, manual_cost, discount_percent; composite_service_id from the composite include
attendance_good_items[]id, good_id, unit_id, storage_id, staff_id, quantity, cost_per_unit, manual_cost, discount_percent; operation_unit_type from operation_type_slug; number and planned_activation_date from the loyalty includes; marks

date is Location wall-clock time without an offset. Echo it exactly; never derive it from timestamp.

Product Item fields

  • quantity is in sale units and positive.
  • cost_per_unit is the unit price before discount. For a new line, use the Product's sale price.
  • discount_percent is the line discount in percent.
  • manual_cost is the line total after discount_percent and before loyalty programs: round(cost_per_unit × quantity × (100 − discount_percent) / 100, 2). Echo the read's manual_cost, never its cost, which already includes loyalty discounts.
  • operation_unit_type is sale (sold to the Booking User) or consumable (written off as a Service consumable).
  • storage_id is the Inventory the Product is written off from, and staff_id is the Team Member credited with the sale. The web app uses the Appointment's Team Member.

What a save changes

  • Expect every record you send to be re-saved in full, even when you echo it exactly. The change detection treats an Appointment without a walk-in visitor or without a color as changed, so most exact echoes still re-save the Appointment. Only a record that passes every check is skipped, and then only its Product Items are synchronized.
  • A Service or Product Item line without id is created. An existing line that is missing from the request is deleted, together with any gift card or membership it issued. attendance_good_items: [] deletes every Product Item of the Appointment.
  • Omitting assistants removes the Appointment's assistants, and comer: null removes its walk-in visitor. custom_field_values: null and custom_color: null keep the stored values.
  • The visit's Product Items are saved as one list. Product Items of other Appointments in the visit are kept, but they are re-saved and go through the loyalty recalculation again; read them back too. Inferred from the code, not verified: the re-save sends each kept line's after-loyalty total as its new pre-loyalty total, so a loyalty discount on those lines may be applied twice, and it does not carry a membership's planned activation date, so a visit with a specific-date membership on another Appointment may fail to save. Prefer visits with a single Appointment when loyalty programs discount Products.

Preconditions and refusals

  • Status. Product Items require the visit status arrived (1) or confirmed (2). Any other status normally returns 403 with a message that product sales are restricted for this status. If no record in the request counts as changed, the request instead returns 200, saves none of the Product Items, and deletes every Product Item of the visit. Check the status before you send.
  • Arrived side effects. Changing the status to arrived (1) runs loyalty auto-programs, writes off Service consumables, and reschedules the Booking User's repeat-visit reminders. Changing it to no-show (-1) sends the cancellation notice.
  • Printed bill. When the visit's sale bill is printed, including a pending fiscal pre-save, Product Item changes return 400.
  • Stock. When the Location forbids negative stock, a line that would take an Inventory below zero returns 406. Otherwise stock may go negative.
  • Gift cards and memberships. A gift card needs number unless its type allows an empty code; a used number, a number outside the type's list, or an archived type is refused. A membership whose type expires on a specific date needs planned_activation_date. Existing gift card and membership lines must be echoed with the same number and planned_activation_date; otherwise the save fails or re-issues the gift card or membership.
  • Marks. Each goods marking code in marks is at most 256 characters.
  • Group Events. An Event Appointment is accepted only with exactly one unchanged Service line and a clients_count within the Event capacity; otherwise 400. Use Update Event Appointment for Event Appointments.
  • One visit per request. All records must belong to one visit and one Booking User and must not set different new statuses; otherwise 400. A deleted Appointment returns 404. An Appointment waiting for an online prepayment cannot be changed.
  • Conflicts. Unless ignore_conflicts is true, a busy time slot, a Team Member who does not provide the Services, a busy resource, a deleted Team Member, or removal of a prepaid Service returns 409.

Side effects

  • Every save sends the partner webhook "record update" for every Appointment of the visit and a push to the Team Member apps, even when only Product Items changed.
  • A changed date (beyond the Location's threshold), Team Member, or set of Services sends the Booking User a "record changed" message. Adding a Product Item alone sends no message.
  • A Product Item change re-applies the Booking User's loyalty card discount programs to the visit. The saved discount_percent, manual_cost, and cost may differ from the request.

Permissions

The Business User needs the right to create or edit Appointments. Product Items also check the following rights, each returning 403:

  • creating Product sales in Appointments, with its day limit, for a new line;
  • editing Product sales, their price, and their discount, with the edit day limit, for a changed line;
  • editing arrived Appointments, and editing fully paid arrived or confirmed Appointments.

Changes to the date or Team Member, Services, the comment, Service prices or discounts, and penalty decisions check their own rights.

Security
BearerPartnerUser
Path
location_idinteger, >= 1required

Location ID

Headers
Acceptstringrequired

Required B2B v2 response media type. Use application/vnd.api.v2+json. A charset parameter is also accepted.

Default:"application/vnd.api.v2+json"
Example:application/vnd.api.v2+json
Content-Typestringrequired

Request media type

Value:"application/json"
Authorizationstringrequired

Bearer {partner_token}, User {user_token}

Bodyapplication/jsonrequired
recordsArray of objects, non-empty(record)required

Appointments to save. All records must belong to the same visit and the same Booking User. Each record replaces the whole Appointment it identifies.

clientobject(client)

Booking User contacts. Send it only to create a Booking User or to attach one found by these contacts; every record must then have client_id: null. Omit it when you edit an existing Appointment.

ignore_conflictsboolean

Skip the scheduling conflict checks (time slot, Team Member services, resources, deleted Team Member, prepaid Service removal, Booking User contact changes). Permission checks still apply.

Default:false
includeArray of strings, unique(Attendance Appointment Includes)

Related resources to include in the response. The web app sends include in the request body.

Items Enum:"client""client.custom_field_values""staff""services""goods""resource_instances""labels""attendance_service_items""attendance_service_items.assistants""attendance_service_items.attendance_service_composite.composite_service"
curl -i -X POST \
  'https://developer.alteg.io/_mock/en/b2b-v2/openapi/locations/{location_id}/attendance/appointments' \
  -H 'Accept: application/vnd.api.v2+json' \
  -H 'Authorization: Bearer <YOUR_Bearer {PartnerToken}, User {UserToken}_HERE>' \
  -H 'Content-Type: application/json' \
  -d '{
    "ignore_conflicts": false,
    "include": [
      "attendance_good_items"
    ],
    "records": [
      {
        "id": 67890,
        "external_id": "",
        "client_id": 23456,
        "staff_id": 34567,
        "date": "2026-10-12T14:00:00",
        "duration": 3600,
        "technical_break_duration": 0,
        "comment": "",
        "custom_color": "",
        "attendance_status": 1,
        "label_ids": [],
        "resource_instance_ids": [],
        "custom_field_values": null,
        "comer": null,
        "client_notification_settings": {
          "sms_hours_before_visit": 0,
          "email_hours_before_visit": 0
        },
        "is_instant_sms_notification_required": false,
        "clients_count": 1,
        "assistants": [],
        "attendance_service_items": [
          {
            "id": 111222,
            "service_id": 45678,
            "quantity": 1,
            "cost_per_unit": 50,
            "manual_cost": 50,
            "discount_percent": 0,
            "composite_service_id": null
          }
        ],
        "attendance_good_items": [
          {
            "id": 333444,
            "good_id": 56789,
            "unit_id": 101,
            "storage_id": 202,
            "staff_id": 34567,
            "operation_unit_type": "sale",
            "number": null,
            "planned_activation_date": null,
            "marks": [],
            "quantity": 1,
            "cost_per_unit": 20,
            "manual_cost": 18,
            "discount_percent": 10
          },
          {
            "id": null,
            "good_id": 56790,
            "unit_id": 101,
            "storage_id": 202,
            "staff_id": 34567,
            "operation_unit_type": "sale",
            "number": null,
            "planned_activation_date": null,
            "marks": [],
            "quantity": 2,
            "cost_per_unit": 12.5,
            "manual_cost": 25,
            "discount_percent": 0
          }
        ]
      }
    ]
  }'

Responses

Appointments saved. data lists every Appointment of the affected visits, including Appointments that were not in the request.

Bodyapplication/json
dataArray of objects(Appointment Resource)required
includedArray of objects(includedResource)

Resources requested through include, when available

Response
{ "data": [ { … } ], "included": [ { … } ] }