Find sections across all documentation pages
A practical guide to the Zenoti REST API: auth, key booking endpoints, real-world rate limits, and what a production integration actually requires.
Zenoti's API is solid REST. If you've worked with any modern JSON API, the shape will feel familiar: predictable URL patterns, standard HTTP verbs, and JSON request/response bodies. This guide covers authentication, the endpoints you actually need for an online booking integration, the rate-limit behavior you'll hit in production, and the gap between "I can call an endpoint" and "this is reliable enough to take real bookings."
Disclosure:We're BookerKit. We sell a wrapper for the Zenoti booking funnel. This guide is honest about what's easy, what's hard, and where our product fits. If you want to build your own integration, everything here will help.
https://api.zenoti.com/v1/BookerKit touches a focused slice of the API surface: services, therapists, availability slots, guests, bookings, and payments/invoices. Everything else (inventory, payroll, marketing campaigns, etc.) is outside our scope but follows the same conventions.
Every request carries an API key in the Authorization header:
Authorization: apikey YOUR_API_KEYKeys are issued per center. If you manage multiple locations, each one has its own key. Keep keys server-side — never expose them in client-side JavaScript or embed code.
If you're using BookerKit, you add the key once in the dashboard and we handle it from there. See the Getting Started guide for setup.
These are the endpoints that matter for a booking integration, roughly in the order you'll call them.
GET /v1/Centers/{center_id}/servicesReturns the service catalog for a center. The response includes service names, descriptions, durations, prices, categories, and whether online booking is enabled. Filter the response to only show services where online booking is turned on.
GET /v1/centers/{center_id}/services/{service_id}/therapistsReturns the therapists (providers) who can perform a given service. Use this to let guests choose a specific provider, or skip it entirely and let Zenoti assign one automatically during slot selection.
Slot retrieval is a multi-step process, not a single call:
POST /v1/bookings with the guest, service, and optionally a therapist. This returns a booking_id but doesn't confirm anything.GET /v1/bookings/{booking_id}/slots returns time slots for the draft. The slots respect therapist schedules, service duration, and center hours.PUT /v1/bookings/{booking_id}/slots/{slot_id} locks the chosen time temporarily.This three-step dance is the most complex part of a booking integration. Slots expire, draft bookings can become stale, and you need to handle the case where a slot is taken between fetch and reserve.
Before booking, you need a guest record. Search for an existing guest by email, or create a new one:
POST /v1/guests
{
"center_id": "your-center-id",
"personal_info": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"mobile_phone": {
"country_code": 1,
"number": "5551234567"
}
}
}Always search before creating to avoid duplicate guest records. Zenoti's guest search endpoint accepts email as a query parameter.
Creating and confirming a booking are separate calls:
POST /v1/bookings with guest, service, therapist (optional), and center. Returns a draft booking.PATCH /v1/bookings/{booking_id}/confirm finalizes the booking. Until you confirm, the booking is a draft and won't appear on the center's schedule.The two-step model lets you collect payment or run validation between creation and confirmation.
Payment handling depends on the center's configuration:
hosted_payment_uri that handles PCI compliance. You redirect the guest to this URI, they enter card details, and Zenoti redirects back.Some Zenoti organizations require a referral.referral_sourcefield in the create-guest payload. If it's missing, the call fails silently or returns a vague error. This isn't always documented clearly — check with your Zenoti admin whether referral source is a required field for guest creation in your org. If it is, you'll need to include it in every guest creation request.
Zenoti returns rate-limit information in response headers:
RateLimit-Remaining — requests left in the current windowRateLimit-Reset — seconds until the window resetsRateLimit-Limit — total requests allowed per windowThe numeric ceiling can vary by org configuration and endpoint. When you hit a 429 Too Many Requests, back off using exponential backoff with jitter. A simple strategy:
// Exponential backoff with jitter
const backoff = (attempt: number): number => {
const base = Math.min(1000 * Math.pow(2, attempt), 30000)
const jitter = Math.random() * base * 0.1
return base + jitter
}For write operations (creating guests, confirming bookings), use idempotency keys so retries don't create duplicates. And always set request timeouts — a hung connection against a rate-limited API will cascade failures through your stack.
When Zenoti returns a hosted_payment_uriin the payment response, the guest needs to complete card entry (and potentially 3D Secure verification) on Zenoti's hosted page. This means:
The redirect flow adds UX friction. The guest leaves your page, enters a card on a Zenoti-branded page, and comes back. If your booking widget runs in an iframe, the redirect gets more complex because you need to break out of the iframe context for the payment page.
Zenoti supports webhooks for booking lifecycle events — creation, confirmation, cancellation, rescheduling, and no-shows. You register a callback URL in the Zenoti admin, and Zenoti sends a POST request when events fire.
If you're using BookerKit, you can also subscribe to booking_started and booking_completed events from BookerKit's signed webhook system. These include UTM parameters and click-ID attribution data that Zenoti's native webhooks don't carry.
Regardless of which webhook system you use, validate signatures on incoming payloads and protect your endpoint against SSRF. Never trust the source IP alone — always verify the cryptographic signature.
The gap between "I can hit the endpoint" and "this handles real bookings reliably" is where most of the work lives:
BookerKit handles all of this out of the box. If you're building your own, budget 3-4x the time you think the "happy path" will take.
Build your own when you need API surface beyond booking — inventory management, payroll, marketing campaigns, custom reporting, or workflows that span multiple Zenoti endpoints in ways a booking widget never would.
Use BookerKit when the booking funnel is your scope and you want attribution data (UTMs, click IDs) flowing through the entire flow. BookerKit gives you a white-label widget, conversion tracking, webhook events with attribution, and the production reliability layer described above — without building or maintaining any of it.
No official SDK at the time of writing. Integrations call the REST API directly in whatever language and HTTP client they prefer.
Through your Zenoti account — typically via the admin settings or API access section of your Zenoti dashboard. Each center may issue its own key.
Zenoti returns the current window's limit in the RateLimit-Limit response header. The numeric ceiling can vary by org configuration and endpoint.
Yes. BookerKit and a direct Zenoti integration are not mutually exclusive. BookerKit uses your API key for booking operations only. Any other Zenoti endpoint you call directly is unaffected.
Zenoti has webhooks for booking lifecycle events. If you're using BookerKit, you can also subscribe to booking_started and booking_completed events from BookerKit's signed webhook system, which include UTM and click-ID attribution data.
BookerKit is an independent integration, not an official Zenoti partner.
Any language with an HTTP client. There's no language-specific SDK — you send HTTP requests with a JSON body and an Authorization header.
BookerKit gives you a production-ready Zenoti booking widget with full attribution tracking, conversion analytics, and webhook events — no API integration work required.