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 noAuthorization 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.
Base URL
How it works
All booking API endpoints share the same path prefix:{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 (theturnstile_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:
301 or 308 redirect for aliases such as www.example-detailer.com. If two hostnames both render booking, both require separate CRM domain records.
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.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.