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
recordsreplaces 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 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, andduration_details. - Build one record for each Appointment you change, using the mapping below. Leave the other Appointments of the visit out.
- To add a Product Item, append a line with
id: null. Keep every existing line with itsidand its values unchanged. - Send
is_instant_sms_notification_required: falseandignore_conflicts: false. - Request
include: ["attendance_good_items"]and read the Product Items back from the response: loyalty programs may change their totals after the save.
| Request key | Source in the read |
|---|---|
id, external_id, staff_id, date, duration, comment, custom_color, clients_count, attendance_status_slug | Appointment attributes with the same names |
client_id | Appointment client_id; null when it is 0 |
technical_break_duration | duration_details.technical_break_duration |
resource_instance_ids, label_ids | IDs of the included resource_instances and labels |
comer | Included comer (id, title) plus slug and type; null when absent |
custom_field_values | Included custom_field_values as code and value |
client_notification_settings | Included client_notification_settings |
assistants | attendance_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.
quantityis in sale units and positive.cost_per_unitis the unit price before discount. For a new line, use the Product's sale price.discount_percentis the line discount in percent.manual_costis the line total afterdiscount_percentand before loyalty programs:round(cost_per_unit × quantity × (100 − discount_percent) / 100, 2). Echo the read'smanual_cost, never itscost, which already includes loyalty discounts.operation_unit_typeissale(sold to the Booking User) orconsumable(written off as a Service consumable).storage_idis the Inventory the Product is written off from, andstaff_idis the Team Member credited with the sale. The web app uses the Appointment's Team Member.
- 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
idis 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
assistantsremoves the Appointment's assistants, andcomer: nullremoves its walk-in visitor.custom_field_values: nullandcustom_color: nullkeep 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.
- Status. Product Items require the visit status arrived (
1) or confirmed (2). Any other status normally returns403with a message that product sales are restricted for this status. If no record in the request counts as changed, the request instead returns200, 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
numberunless 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 needsplanned_activation_date. Existing gift card and membership lines must be echoed with the samenumberandplanned_activation_date; otherwise the save fails or re-issues the gift card or membership. - Marks. Each goods marking code in
marksis at most 256 characters. - Group Events. An Event Appointment is accepted only with exactly one unchanged Service line and a
clients_countwithin the Event capacity; otherwise400. 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 returns404. An Appointment waiting for an online prepayment cannot be changed. - Conflicts. Unless
ignore_conflictsistrue, 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 returns409.
- 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, andcostmay differ from the request.
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.
Appointments to save. All records must belong to the same visit and the same Booking User. Each record replaces the whole Appointment it identifies.
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.
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.
Related resources to include in the response. The web app sends include in the request body.
- Mock serverhttps://developer.alteg.io/_mock/en/b2b-v2/openapi/locations/{location_id}/attendance/appointments
- Productionhttps://api.alteg.io/api/v2/locations/{location_id}/attendance/appointments
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
}
]
}
]
}'{ "data": [ { … } ], "included": [ { … } ] }