Skip to main content
The Public Booking API lets you build custom booking flows or embed a fully functional booking experience on an approved external website without using the hosted booking page.

Integration options

Pick the path that matches what you’re building:

Shared native runtime

Recommended for a bespoke business website. Install one versioned loader and style the namespaced booking markup with the website’s own CSS.

Public Booking API

Advanced option for a completely custom client. Read live services and pricing, show availability, hold a slot, and create a pending-review order or lead. This is what the rest of this reference documents.

Website lead capture

Just want website form submissions to land in the CRM as leads? Copy the ready-made code from Settings → Website Integration → Website Leads → Website Forms.

WooCommerce webhooks

Selling through a WordPress store? Point WooCommerce order webhooks at the CRM and every order arrives as a lead, signature-verified.
The Public Booking API uses a per-tenant token for access control. There are no API keys or bearer tokens to manage.

Authentication & tokens

Every endpoint is scoped by a token in the URL path — there is no Authorization header. Two kinds of token are accepted, and either works on every endpoint: The token must match ^[a-zA-Z0-9_-]{1,255}$. A malformed token returns 400 invalid_token; a well-formed token that doesn’t resolve to a tenant with booking enabled returns 404 not_found.
The booking slug and lead token are public by design — they appear in your page source. They only grant access to the public booking endpoints for one workspace; they cannot read or modify any other CRM data. Never put a secret CRM credential in front-end code.

Base URL

How it works

All booking API endpoints share the same path prefix:
Replace {token} with your booking slug or lead token. These endpoints cover the full booking flow:

Get config

Fetch services, body types, pricing, and page branding.

Get availability

Return short-lived offers for open time slots on a given date.

Hold a slot

Reserve one offered slot while the customer finishes the form.

Get membership plans

List the plans a customer can join while booking.

Submit booking

Create an order or lead from customer input.

Rate limiting

Availability, hold creation, and submission each use a separate durable quota: A limited response returns 429 too_many_requests and includes Retry-After. Submit attempts consume their quota only after Turnstile succeeds. Config and membership requests do not consume these three operation-specific quotas.

CAPTCHA requirement

The submit endpoint requires a Cloudflare Turnstile CAPTCHA token in the request body (the turnstile_token field). Use the Turnstile site key supplied for your CRM booking integration, set the action to booking_submit, and pass the resulting token when submitting.
The booking token and Turnstile site key are separate public values. The booking token identifies your CRM workspace. The site key renders the approved CRM booking widget. Neither value is a secret.

Register an external hostname

Before an external browser can call the booking endpoints, its exact hostname must be registered and active. Registration connects the hostname to the tenant and to CRM-managed Turnstile protection. Open Settings → Website Integration → Online Booking → Native Website Booking, add the canonical hostname, publish the displayed DNS TXT record, and select Verify domain. After the state becomes Active, GET /config returns the matching public site key and Turnstile action. Send the hostname only, without a scheme, port, or path:
Use a permanent 301 or 308 redirect for aliases such as www.example-detailer.com. If two hostnames both render booking, both require separate CRM domain records.
Do not create an unrelated Turnstile widget and paste its site key into your page. The CRM validates the token with the server-side secret paired with the approved site key. A token from another widget fails with captcha_failed.
The Turnstile site key is public and belongs in browser code. The paired secret key is private and must never appear in HTML, JavaScript, source control, browser logs, or API responses.

Order vs. lead creation

When a booking is submitted, the CRM creates either an order or a lead depending on how the tenant has configured their booking page: Either way, a successful submit returns success, confirmation_message, resource_type, resource_id, review_status, and time_reserved. The record appears in the business’s workspace immediately. time_reserved is true for an order and false for a lead. Your integration can’t choose between the two: the setting belongs to the business. Read resource_type from the response rather than assuming a booking became an order.

End-to-end integration flow

A complete custom booking integration uses config, availability, hold, and submit in order. The hold is required: a slot offer alone cannot be submitted. The memberships endpoint is optional. Every endpoint uses the same exact-origin policy and can be called from an active registered hostname.
1

Fetch config

GET /config once on page load. Use the response to render your service list, vehicle body-type selector (for auto businesses), currency, and branding. Cache it for the session — it rarely changes.
2

Render services and body types

Build the form from services[] (id, name, base_price, estimated_duration) and, for auto businesses, body_types[] (id, name, surcharge_percent). Compute the displayed price as base_price × (1 + surcharge_percent/100) — but only for services whose applies_body_type_surcharge is true. Applying the surcharge to every service overcharges the customer on the ones that opt out.
3

Fetch availability

When the customer picks a date, call GET /availability?date=YYYY-MM-DD&service_ids=.... Pass the selected service IDs so slot length matches the real job duration. Render each slot’s start and end, but keep its offer_token private to your form state. Check expires_at before the next step.
4

Hold the selected slot

Send the chosen offer_token to POST /hold. Store the returned hold_token and complete submission before its expires_at. Creating another hold for the same visitor releases the previous active hold.
5

Submit the booking

Collect a Turnstile token, then POST /submit with the customer object, service_ids, hold_token, and optional vehicle, membership, and notes. On 200, show confirmation_message. The server uses the held services and time as the authoritative booking snapshot.
6

Handle errors

Map the documented error codes to user-facing messages. For offer_expired or slot_unavailable, fetch availability again and ask the customer to choose a new time. For 429 too_many_requests, honor Retry-After.
A minimal end-to-end implementation in the browser:
End-to-end flow

Security model

The booking API is intentionally public — it is meant to be called from a customer’s browser on an approved external domain — so it does not rely on a secret API key. Instead, several layers work together to keep it safe to expose: Config, availability, holds, memberships, submit, and their preflight handlers use one origin resolver. An approved origin receives its exact Access-Control-Allow-Origin value; rejected origins do not receive a permissive CORS header. CORS alone is not the security boundary. The tenant token, active exact hostname, plan entitlement, Turnstile verification, rate limiting, authoritative pricing, and atomic commit work together. Treat the booking token and Turnstile site key as public, and never put a secret CRM credential in front-end code.