Skip to main content
To let a customer book a service, you read a published Appointment Definition, fetch its availability for a location and date, then create an appointment against the chosen slot. This guide covers that flow and the queries most front ends need. For the model behind it, see the Appointments concept. All endpoints are bearer authenticated and live under https://api.[tenant].getomneo.com/api/v3.

Booking flow

1

List published definitions

2

Read the definition and its booking questions

The definition returns its normal and special hours, linked locations, staff, and whether it has a booking questionnaire. The questions endpoint returns the active form slots to render.
3

Fetch availability for a location and date

When the definition has requires_staff: true, pass staff_id, and first call available-staff to populate a staff picker. For a calendar view, use available-slots-range with start_date and end_date (capped at 31 days) instead of calling the single-day endpoint repeatedly.
4

Create the appointment

For an instant definition the response status is confirmed. For an approval_required definition it is requested. Times are sent in timezone and stored in UTC. Pass assigned_staff_id when the definition requires staff.

Availability responses

available-staff and available-slots are POST endpoints that take a location_id and date in the body and return what a booking UI needs to render pickers. Call available-staff first when the definition has requires_staff: true, to populate a staff picker for the date:
Then call available-slots for the chosen staff member (or without staff_id for a non-staff definition):
Each element of slots is one bookable start time for the day together with the capacity still available at that time. The array is empty when the day has no availability, so an empty slots is the normal “fully booked or closed” response, not an error. For a staff-required definition the slots belong to the staff member in the response, so fetch slots per staff member. staff is null for a non-staff definition. The meta block echoes the definition settings that shaped the calculation, so a UI can show buffers and lead time without a second read.
Use available-slots-range with start_date and end_date (capped at 31 days) for a calendar view. It returns the same per-day shape for each date in the range instead of one day at a time.
The create response includes the saved answers[], so you can confirm what was captured without a follow-up read.

The appointment resource

A create (POST), read (GET), or update (PUT) returns the appointment wrapped in a data object. This is the shape a booking against the approval_required bra-fitting definition returns, so status is requested and the confirmation timestamps are still null:
The scheduled times are returned in UTC even though the request sent them as local Melbourne time. 10:00 on 11 May in Australia/Melbourne becomes 2026-05-11T00:00:00Z. Convert back to local time on the client using timezone.

Fields you send

These are the fields a create or update accepts. On the profile-scoped route, profile_id is inferred from the URL.

Fields returned

The answer object

Each element of answers[] records one captured answer and how it was routed to its destination. Each element of links[] records one additional Profile or List attached through the link endpoints, separate from the profile_id the appointment is booked for.

Reading an appointment

Read a single appointment with GET /api/v3/appointments/{id}. The response is the same resource shape shown above, with its nested profile, location, assigned_staff, answers, and links loaded.

Deleting an appointment

DELETE /api/v3/appointments/{id} removes the appointment and returns 204 with no body. To keep a record of the booking instead, set status to cancelled rather than deleting, which preserves the appointment with its cancelled_at timestamp.

Response codes

Fetching a customer’s appointments

Use the profile-scoped endpoint to list appointments for a specific profile:
The profile-scoped route is the per-customer counterpart to the global routes. It exposes the full set of operations for one Profile, and the create infers profile_id from the URL so you do not send it in the body: A nested route returns 404 when the appointment id does not belong to the profile in the URL, which lets a customer-facing UI scope safely to the signed-in customer. The global endpoint is also available and accepts a profile filter, which is useful for admin queries that combine filters across multiple profiles or need additional filtering dimensions not exposed on the profile-scoped route:
The appointments list supports filtering on its stored fields, so you can narrow a customer’s appointments server-side rather than on the front end: Combine filters with AND logic. For a customer’s upcoming confirmed bookings:
The list endpoint is paginated. Use page[size] to control the page size and page through meta.pagination like every other Omneo list endpoint.

Listing and filtering definitions

The Appointment Definition list supports these filters: For example, every published definition bookable at a location:

Filtering appointments by type

The Appointment Definition is the type. To fetch every appointment of a given type, filter the appointments list by its definition rather than fetching everything and filtering on the front end:
This is the same Definition/Instance filter pattern used elsewhere in the API.
To filter the appointments list by service, use appointment_definition_id (the definition’s id). The list does not accept the definition handle as a filter, so if you only have a handle, resolve it to an id first:
Then pass the returned id as filter[appointment_definition_id].
An appointment has no separate type field: its definition is its type, so filter by appointment_definition_id to group appointments of one kind.

Fetching visible definitions for a profile

When building a customer-facing booking flow, use the profile-scoped visibility endpoint to retrieve only the published, non-archived definitions that Omneo evaluates as visible for a specific Profile:
This endpoint requires the read-appointment-definitions token scope. It applies each definition’s visibility_condition (a JsonLogic rule) against the Profile’s attributes and returns only those definitions where the rule evaluates to true. Definitions with a null visibility_condition are excluded. For customer UIs where every profile should see the same published definitions, set visibility_condition to {"==": [1, 1]} on those definitions so they pass the check for all profiles. See Visibility conditions in the Appointments concept for how the condition is evaluated.

Managing the lifecycle

Update an appointment with PUT /api/v3/appointments/{id}. Sending a status stamps the matching timestamp automatically, for example confirmed sets confirmed_at and completed sets completed_at. Omneo does not enforce a fixed transition order, so you can set any status directly: a requested booking can be cancelled or marked no_show without being confirmed first.
Booking answers are captured at creation only. To collect post-visit feedback, create a separate questionnaire and submit it through POST /api/v3/questionnaires/{id}/submissions.

Linking records

An appointment can reference other records, for example to tie a consultation to the sale that followed. There are two mechanisms: a single Transaction or Order set on the appointment itself, and additional Profiles or Lists attached through the link endpoints.

Attaching a Transaction or Order

Attach a completed Transaction or a pending Order with a resolver object on create or update. Each is a single association. You do not need the internal id: send whichever identifier you have and Omneo resolves it.
Send the field as null to detach:

Linking Profiles or Lists

Link one or more additional Profiles or Lists to an appointment through the dedicated link endpoint. These are separate from the profile_id the appointment is booked for: use them for a second attendee, a stylist’s Profile, or a marketing List.
The body is { "type": "profile" | "list", "id": <value> }. Use a Profile UUID when type is profile, and an integer List id when type is list. Linking returns 200 and is idempotent, so linking the same record twice has no extra effect. Unlink with the same body shape:
Unlink returns 204 with no body.

Walk-in queues and waitlists

For definitions that allow walk-ins, check a customer into the queue with POST /api/v3/appointment-queues. When no slot suits, register interest with POST /api/v3/appointment-waitlists and fulfil it later by creating an appointment. See Walk-in queues and waitlists for both flows, their statuses, and how to convert either into an appointment.

End-to-end example: an approval-required fitting

This walks the full lifecycle of a staff-assigned, approval-required booking, from choosing the service to attaching the sale that followed.
1

Resolve the definition by handle

Take the returned id (here 1) and read its booking questions to render the form: GET /api/v3/appointment-definitions/1/questions.
2

Pick a staff member and a slot

Show the staff whose has_available_slots is true, then fetch that staff member’s slots with available-slots, passing staff_id.
3

Create the request

The response is 201 with status: "requested".
4

Approve the request

status becomes confirmed and confirmed_at is stamped. If the definition has a confirmed_target_id, this is when its notification schedules.
5

Run the appointment and tie it to the sale

completed_at is stamped and transaction resolves to the linked sale, so the booking and its outcome are recorded together.

Errors

Every appointment endpoint uses the same status codes.

Edge cases and gotchas

  • Times are stored in UTC. You send scheduled_start_at in the request timezone, but every datetime the appointment returns is UTC. Convert back on the client using the returned timezone, and do not assume the returned string matches what you sent.
  • Booking answers are captured at create only. Sending answers on a PUT does not add or change them. Collect post-visit input with a separate questionnaire submitted through POST /api/v3/questionnaires/{id}/submissions.
  • Assign staff when the definition requires it. For a definition with requires_staff: true, send assigned_staff_id. Fetch the eligible pool from available-staff first.
  • Delete is not the same as cancel. DELETE removes the record and returns 204. Setting status to cancelled keeps the appointment with its cancelled_at timestamp, which is usually what you want for reporting.
  • Linking is idempotent. Linking the same Profile or List twice through the link endpoint has no extra effect, so a retry is safe.
  • No fixed status order. Omneo stamps whatever status you set and does not enforce a path, so a requested booking can go straight to cancelled or no_show.