Skip to content

API reference / Appointments

Bookings, conflicts, and availability

The appointments resource covers bookings, chair-blocking entries, and a derived availability endpoint that computes free slots for a chair on a given day. This page lists the object shape, the conflict-detection rule, and every endpoint's fields and errors.

Who can do what

Every endpoint requires auth and is scoped to the caller's practice. Unlike chairs and practice settings, appointments are not owner-only — any practice member, owner or assistant, can create, update, and delete them. appointmentId path segments must be canonical UUIDs; a malformed one returns not_found (404) without reaching the service layer.

The appointment object and its type

An appointment carries chairId, patientId, type, title, startAt, endAt, color, serviceRef, notes, createdBy, and metadata, plus id, practiceId, createdAt, updatedAt. type is one of appointment, reminder, or unavailable. An unavailable entry blocks a chair the same way a real appointment does for conflict purposes; a reminder entry never occupies a chair and is exempt from both the overlap check and the availability calculation, even if it has a chairId set.

Overlap (double-booking) detection

When chairId is set and type !== "reminder", creating or updating an appointment is rejected with conflict (409) if it overlaps an existing appointment or unavailable block on the same chair. Overlap uses strict inequality (existing.startAt < newEnd AND existing.endAt > newStart), so two entries that touch exactly at a boundary are not considered overlapping. Different chairs never conflict with each other, and on PATCH the appointment being updated excludes itself from its own conflict check.

Reminders as a side effect

Creating, updating, or deleting an appointment-type entry that has a patientId automatically schedules, reschedules, or cancels a background appointment-reminder task, subject to the practice's configured lead time — there is no separate API call to manage it. The delivery mechanics (channels, degraded-mode behavior) live in the notifications documentation rather than here.

See the error reference for what every code and status means, and the API reference hub for the other resources.

Endpoints

GET/api/v1/appointments

Lists appointments starting inside a time window, ordered by start time.

The window is half-open: an appointment is included when its startAt falls in [from, to). An appointment that started before from and is still running is not returned.

Query parameters

FieldTypeDefaultDescription
fromRequiredISO datetimeStart of the window, inclusive.
toRequiredISO datetimeEnd of the window, exclusive.
chairIduuidRestricts the result to a single chair.

Response · 200

{
  "data": {
    "appointments": [
      {
        "id": "a7c30e51-....",
        "type": "appointment",
        "startAt": "2026-07-08T10:00:00.000Z",
        "endAt": "2026-07-08T11:00:00.000Z"
      }
    ]
  },
  "requestId": "req_5f2c1e40-...."
}

Errors

CodeStatusWhen
validation_failed400from or to is missing or not a valid datetime, or chairId is malformed.
missing_authorization401No Authorization header was sent.

POST/api/v1/appointments

Creates an appointment, a reminder, or a chair-blocking unavailable entry.

Any practice member can do this — there is no owner-only gate on appointments, unlike chairs and practice settings.

A chairId or patientId belonging to another practice is rejected as validation_failed, not surfaced as a database error.

Body

FieldTypeDescription
typeRequiredstringOne of appointment, reminder, unavailable.
startAtRequiredISO datetimeWhen it starts.
endAtRequiredISO datetimeMust be strictly after startAt.
chairIduuidMust belong to your practice. Required for the entry to occupy a chair.
patientIduuidMust belong to your practice. Setting it on an appointment schedules the reminder.
titlestringTrimmed; must not be empty if present.
colorstringDisplay colour in the calendar.
serviceRefstringReference to the service being performed.
notesstringFree text.

Example request

{
  "type": "appointment",
  "startAt": "2026-07-08T10:00:00.000Z",
  "endAt": "2026-07-08T11:00:00.000Z",
  "chairId": "5c2f9b74-....",
  "title": "Checkup"
}

Response · 201

{
  "data": {
    "id": "a7c30e51-....",
    "type": "appointment",
    "startAt": "2026-07-08T10:00:00.000Z",
    "endAt": "2026-07-08T11:00:00.000Z"
  },
  "requestId": "req_5f2c1e40-...."
}

Errors

CodeStatusWhen
validation_failed400endAt is not after startAt, a chairId or patientId is unknown, or an unrecognized field was sent.
conflict409The entry overlaps an existing appointment or unavailable block on the same chair.

GET/api/v1/appointments/:appointmentId

Fetches one appointment.

Path parameters

FieldTypeDescription
appointmentIdRequireduuidCanonical UUID.

Response · 200

{
  "data": { "id": "a7c30e51-....", "type": "appointment" },
  "requestId": "req_5f2c1e40-...."
}

Errors

CodeStatusWhen
not_found404No such appointment in this practice.

PATCH/api/v1/appointments/:appointmentId

Updates an appointment. Every field from the create body, all optional.

endAt > startAt is re-checked whenever either changes, against the existing value of the other if only one is sent. The conflict check re-runs against the post-update chair and time range, excluding this appointment from its own check.

Path parameters

FieldTypeDescription
appointmentIdRequireduuidCanonical UUID.

Example request

{
  "startAt": "2026-07-08T10:30:00.000Z",
  "endAt": "2026-07-08T11:30:00.000Z"
}

Response · 200

{
  "data": {
    "id": "a7c30e51-....",
    "startAt": "2026-07-08T10:30:00.000Z",
    "endAt": "2026-07-08T11:30:00.000Z"
  },
  "requestId": "req_5f2c1e40-...."
}

Errors

CodeStatusWhen
validation_failed400An invalid field, an unknown chairId or patientId, or an unrecognized field.
conflict409The new time overlaps a different entry on the same chair.
not_found404No such appointment in this practice.

DELETE/api/v1/appointments/:appointmentId

Deletes an appointment, cancelling any reminder scheduled for it.

Path parameters

FieldTypeDescription
appointmentIdRequireduuidCanonical UUID.

Response · 200

{
  "data": { "deleted": true },
  "requestId": "req_5f2c1e40-...."
}

Errors

CodeStatusWhen
not_found404No such appointment in this practice.

GET/api/v1/appointments/availability

Computes the free slots on one chair for one calendar day.

Slots are bounded by the practice's working hours and slot length, falling back to defaults for any unset setting. Busy blocks are the appointment and unavailable entries overlapping that local day; reminder entries are excluded, because they do not occupy a chair.

Query parameters

FieldTypeDefaultDescription
dateRequiredstringYYYY-MM-DD, interpreted in the practice's timezone.
chairIdRequireduuidMust belong to your practice.
durationMinutesRequiredintegerLength of slot to look for, 1–1440.

Response · 200

{
  "data": {
    "slots": [
      { "startAt": "2026-07-08T06:00:00.000Z", "endAt": "2026-07-08T07:00:00.000Z" }
    ]
  },
  "requestId": "req_5f2c1e40-...."
}

Errors

CodeStatusWhen
validation_failed400date is missing or malformed, chairId is not a UUID or belongs to another practice, or durationMinutes is out of range.
not_found404The practice could not be resolved.

Common questions

Patient records are encrypted in transit and at rest, files are stored privately and served through short-lived signed links, and every change is written to an audit log. Staff accounts sign in with passkeys rather than shared passwords.

Any authenticated practice member can create, update, and delete appointments — owner and assistant roles both have full access, with no role gate on those actions. This is different from chairs and practice settings, which are owner-only, so it's worth checking per resource rather than assuming one rule applies everywhere.

Two entries on the same chair whose time ranges overlap under a strict-inequality rule, where touching exactly at a boundary does not count as overlapping. Reminder-type entries are exempt entirely, even with a chairId set, since they don't occupy a chair. A conflicting create or update is rejected with a 409 conflict response.

No. Creating, updating, or deleting a patient-linked appointment automatically schedules, reschedules, or cancels its reminder in the background, based on the practice's configured lead time. There is no separate endpoint to call for this — it happens as a side effect of the normal appointment write endpoints.