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:
GET /api/v1/availability— find a free slot.POST /api/v1/bookings— create the booking against that slot.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 availabilitybookings: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 entityalready_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.