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:
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number (activates pagination) |
limit | integer | Items 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:
| Field | Type | Required | Description |
|---|---|---|---|
client_id | integer | Yes | Client ID |
staff_id | integer | Yes | Staff member ID |
service_id | integer | Yes | Service ID |
start_at | datetime | Yes | Start time |
end_at | datetime | Yes | End time |
notes | string | No | Notes (visible to client) |
internal_notes | string | No | Internal notes (not visible to client) |
provisional | boolean | No | If true, status=provisional; a confirm/decline email is sent to the client |
waitlist_id | integer | No | Link to waitlist entry; triggers a waitlist confirmation email |
base_url | string | No | Base URL for accept/decline links in provisional emails |
Bookings on dates marked as holidays in the holidays table are rejected with a 400 error.
Behaviour after creation:
- Regular booking: enqueues a
booking.createdjob 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_atorend_atchanges (and status is notcompleted,no-show, orcancelled): abooking.updatedemail 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: ifnoshow_enabledis 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
| Status | Description |
|---|---|
scheduled | Confirmed appointment |
provisional | Awaiting client approval (booking request mode) |
completed | Finished appointment |
cancelled | Cancelled appointment |
no-show | Client 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"
}