Skip to main content
An appointment is a time a patient is booked to see a clinic: which patient, which service (a consultation, a check-up), which clinician, and when. Booking through the API follows the same rules as booking in the clinic’s own app: the service must be bookable, the clinician must offer it, the clinic must be taking bookings for that person at that time, and nobody can be double-booked. If you are new to scheduling, the flow is always the same four steps:
  1. Discover what can be booked: the clinic’s services and clinicians.
  2. Check the free times for a service.
  3. Book one, for a patient you already have (see Patients).
  4. Change it later: confirm it, move it, or cancel it. Nothing is ever deleted.

Who can use it

Appointments use three permissions. See Permissions for how a key gets them. The two patient-information permissions need the API add-on and the accepted agreement, and for an organization the approval for patient data. Read Patient data access first. A test key (ehr_test_…) reaches only your workspace’s sandbox, with invented patients and services, so you can try everything below safely. An appointment for a patient the clinic has restricted never appears in a list. A restricted patient’s appointment, one that does not exist, and one in another clinic all answer 404 with code: "not_found": they are never told apart. See Errors.

What an appointment looks like

Only id and status are always present. The fields you will use most: Fields about the clinic’s internal scheduling and AI scoring (for example ai_category and predicted_show_probability) are returned for information only and cannot be written.

See bookable services

The things a patient can book, such as a consultation. Use a service’s id to find times and to book. GET /v1/clinics/{clinic_id}/appointments/services
There are no query parameters.

See clinicians

The clinic’s clinicians, optionally only those who offer one service. GET /v1/clinics/{clinic_id}/appointments/clinicians

Find free times

The times a service can be booked over a range of dates. GET /v1/clinics/{clinic_id}/appointments/availability
date and time are in the clinic’s timezone, which comes back beside the list. A free time is a hint, not a hold. Someone else can take it before you book, so always be ready for a 409 when you book (see Things to know).

List appointments

GET /v1/clinics/{clinic_id}/appointments
(Each item is a full appointment, shortened here.)

Book an appointment

Books a patient for a service at a time. Send an Idempotency-Key header or the request is refused, so that a retry never books twice. See Retries and duplicates. POST /v1/clinics/{clinic_id}/appointments
A field that is not listed is refused with invalid_request. Not supported through the API: a visit that bundles several services, redeeming a package, deposit or discount, attaching default documents, and group appointments. Book one patient, one service at a time. The answer is the appointment:
If a retry with the same key arrives after the first succeeded, you get the same appointment back and nothing is booked a second time.

Read one appointment

GET /v1/clinics/{clinic_id}/appointments/{appointment_id}

Change or confirm an appointment

Moves an appointment, changes its clinician, venue, urgency, notes or reminder settings, or confirms it. Send only what changes. Send an Idempotency-Key. PATCH /v1/clinics/{clinic_id}/appointments/{appointment_id}
You cannot change patient_id, service_id, location_id or any payment field after booking. Sending one is refused with invalid_request, as is any other field not listed. To book a different patient or service, cancel and book again. An appointment that is already cancelled, completed or no_show can no longer be changed. With a test key, changing start_at is not supported in the sandbox: cancel and book again. The answer is the updated appointment, shaped as in Read one appointment.

Cancel an appointment

POST /v1/clinics/{clinic_id}/appointments/{appointment_id}/cancel This is the only way to set an appointment to cancelled. It does not delete anything: the appointment stays on record with its reason. The body is optional. Send an Idempotency-Key.
Cancelling an appointment that is already cancelled is not an error: it succeeds again and returns the same appointment, with status still cancelled. An appointment that is completed or no_show cannot be cancelled.

Things to know

Refusals you may meet Every error has the shape described in Errors. Idempotency. Book, change and cancel all need an Idempotency-Key. Use a fresh one per action and the same one for a retry. The three read routes and the discovery routes need none. Pagination. Only the list of appointments is paged (limit, starting_after, has_more, next_cursor). The services, clinicians and availability lists return everything in one answer. See Pagination. Dates and time zones. You send start_at with an offset. Free times and each appointment’s timezone are in the clinic’s own time zone. FHIR. Add Accept: application/fhir+json to the list or the single read and you get an Appointment (or a Bundle of them). You can also book (POST) and change (PATCH) by sending a FHIR Appointment with Content-Type: application/fhir+json; the appointment must reference the clinic’s service as a HealthcareService in basedOn. The answer to a write is still our JSON shape. The discovery routes and the cancel route have no FHIR form. See FHIR R4. Organization keys and connections. An organization key reaches only the clinics it is connected to. See Connections.

Common tasks

Book a patient’s next visit
  1. GET …/appointments/services and choose a service.
  2. GET …/appointments/availability?service_id=…&from=…&to=… and choose a time.
  3. POST …/appointments with the patient, service, time and a fresh Idempotency-Key. On 409 slot_taken, go back to step 2.
Find a patient, then list their appointments
  1. Find the patient with Search by identifiers.
  2. GET …/appointments?patient_id=PATIENT_ID&from=2026-10-01&to=2026-12-31.
Show a clinician’s day
  1. GET …/appointments/clinicians for the clinician’s id.
  2. GET …/appointments?clinician_id=…&from=2026-10-12&to=2026-10-12.
Move an appointment
  1. PATCH …/appointments/APPOINTMENT_ID with the new start_at and a fresh Idempotency-Key. A 409 means the new time is not free.
Cancel it
  1. POST …/appointments/APPOINTMENT_ID/cancel with an optional reason.
Keep your own calendar in step
  1. Page through GET …/appointments?updated_since=LAST_TIME until has_more is false, and store the time you started.