Skip to main content

Bookings

Base path: /api/bookings

GET /bookings

Get all appointments. Staff members see only their own appointments when staff_personal_agenda_enabled is active; admins always see all. Cancelled bookings are excluded.

Auth: Logged in

Query parameters:

ParameterTypeDescription
pageintegerPage number (activates pagination)
limitintegerItems per page (default 50)

Response: Array of Booking objects (with client_name), or paginated object. Ordered by start_at descending.


GET /bookings/:id

Get a single booking.

Auth: Logged in

Response: Booking object with client_name.

Errors: 404 — Booking not found


POST /bookings

Create a new appointment.

Auth: Logged in

Request body:

FieldTypeRequiredDescription
client_idintegerYesClient ID
staff_idintegerYesStaff member ID
service_idintegerYesService ID
start_atdatetimeYesStart time
end_atdatetimeYesEnd time
notesstringNoNotes (visible to client)
internal_notesstringNoInternal notes (not visible to client)
provisionalbooleanNoIf true, status=provisional; a confirm/decline email is sent to the client
waitlist_idintegerNoLink to waitlist entry; triggers a waitlist confirmation email
base_urlstringNoBase URL for accept/decline links in provisional emails
note

Bookings on dates marked as holidays in the holidays table are rejected with a 400 error.

Behaviour after creation:

  • Regular booking: enqueues a booking.created job for confirmation email and creates an in-app notification.
  • Provisional booking: sends a confirm/decline email directly to the client with token links.
  • Waitlist booking: sends a "your waitlist slot is confirmed" email to the client.

Response (200): Booking object


PUT /bookings/:id

Update an appointment.

Auth: Logged in

Request body: Any booking fields to update (e.g., status, staff_id, start_at, end_at, notes).

Side effects:

  • When start_at or end_at changes (and status is not completed, no-show, or cancelled): a booking.updated email job is enqueued with a 30-minute delay to prevent duplicate emails during drag-and-drop adjustments.
  • When status changes to completed: a review request is enqueued, followup sequences are started, and the followup notification is removed.
  • When status changes to no-show: if noshow_enabled is active in settings, a Stripe checkout session is created and a no-show fee email is sent to the client.

Response (200): Updated Booking object


DELETE /bookings/:id

Cancel an appointment (sets status to cancelled). Enqueues a booking.cancelled job. If a waitlist entry exists for the same service and date, the first waiting client is notified.

Auth: Logged in


Booking Statuses

StatusDescription
scheduledConfirmed appointment
provisionalAwaiting client approval (booking request mode)
completedFinished appointment
cancelledCancelled appointment
no-showClient did not show up

Booking Object

{
"id": 1,
"client_id": 5,
"staff_id": 2,
"service_id": 3,
"start_at": "2026-03-24 10:00:00",
"end_at": "2026-03-24 10:30:00",
"status": "scheduled",
"notes": "",
"internal_notes": "",
"provisional_token": null,
"feedback_token": null,
"waitlist_id": null,
"client_name": "John Smith"
}