Bookings API

List, create, retrieve, and cancel bookings, plus query availability slots.

What this does

The Bookings API lets bots and integrations book appointments end-to-end on behalf of a contact. Each booking is created against an existing booking link (defined in the Bookings product) and runs through the same flow as the public booking widget — round-robin staff assignment, qualification routing, calendar sync, confirmation email, and reminder creation.

When to use it

Use this when a conversational bot needs to translate a free-text intent like "book me with Sarah next Tuesday at 2pm" into a real booking. The flow is usually:

  1. GET /api/v1/availability — find a free slot.
  2. POST /api/v1/bookings — create the booking against that slot.
  3. PATCH /api/v1/bookings/:id/cancel — optional cancel later.

Authentication and scopes

Requests must include Authorization: Bearer <key>. Each endpoint requires one of:

  • bookings:read — list bookings, fetch a single booking, query availability
  • bookings:write — create a booking, cancel a booking

Endpoints

List bookings

GET /api/v1/bookings

Query parameters:

| Param | Notes | |---|---| | booking_link_id | Filter to a single link | | status | e.g. confirmed, cancelled, no_show | | from / to | ISO 8601 — filter by start_time | | page, limit | Pagination (default page=1, limit=50, max 100) |

Response: { data: Booking[], pagination: { page, limit, total, hasMore } }.

Create a booking

POST /api/v1/bookings

{
  "booking_link_id": "link_abc123",
  "contact_name": "Alice Example",
  "contact_email": "[email protected]",
  "start_time": "2026-05-01T10:00:00Z",
  "notes": "First-time client",
  "client_timezone": "Europe/London"
}

booking_link_id or slug must reference a booking link in the authenticated entity. contact_name, contact_email, and start_time are required. Optional: contact_phone, notes, service_id, staff_id, client_timezone, answers (qualification answers — array of { question_id, answer }).

Response (201 Created): the created booking.

Errors:

| Code | Status | Meaning | |---|---|---| | booking_link_not_found | 404 | No matching link in this entity | | missing_field | 400 | contact_name / contact_email / start_time missing | | slot_taken | 409 | Race-condition guard — another booking grabbed the slot | | routing_rejected | 403 | Conditional routing rule rejected the answers | | validation_failed | 400 | A required qualification question was unanswered |

Get a single booking

GET /api/v1/bookings/:id

Returns the booking with the parent link's link_title and duration_minutes joined in.

Cancel a booking

PATCH /api/v1/bookings/:id/cancel

{ "reason": "rescheduled by client" }

reason is optional. Returns { data: { id, status: "cancelled", reason } }. Errors:

  • not_found (404) — no such booking in this entity
  • already_cancelled (409) — booking is already cancelled

Cancelling also fires the same downstream side-effects as the dashboard cancel flow: pending reminders are deleted, the calendar event is removed from any connected Google/Outlook calendar, and the waitlist for the slot is notified.

Get availability slots

GET /api/v1/availability?booking_link_id=…&date=YYYY-MM-DD

Returns the open slots for one day on one booking link. Either booking_link_id or slug must be supplied.

Response:

{
  "data": {
    "booking_link_id": "link_abc123",
    "slug": "30min-discovery",
    "date": "2026-05-01",
    "slots": [
      { "start": "2026-05-01T09:00:00Z", "end": "2026-05-01T09:30:00Z" }
    ]
  }
}

Slots already account for the link's availability windows, buffer time, existing bookings, calendar busy ranges, and configured availability exceptions.