# Use with an AI agent (/docs/ai-agents) The Booking Pro API is built to be driven by LLMs and AI agents. ## Claude Agent Skill [#claude-agent-skill] We publish a ready-made **Agent Skill** (authored to the open [agentskills.io](https://agentskills.io) standard, so it also works in Cursor, Codex, and GitHub Copilot). It teaches an agent how to authenticate, the conventions, common recipes, and the full endpoint catalog, all kept in sync with this reference. Give the agent a key (as an environment variable, never inline) and it can check availability, book, and manage contacts on your behalf. ## Every page is Markdown [#every-page-is-markdown] * Click **Copy Markdown** at the top of any page. * Or append `.md` to any page's URL to fetch its Markdown. * [`/llms.txt`](/llms.txt) is a compact index of the whole site. * [`/llms-full.txt`](/llms-full.txt) is every page concatenated, ready to paste into a context window. ## A note on keys [#a-note-on-keys] An AI agent calling the API needs a real key with real scopes. Mint a dedicated key for the agent under **Settings -> Developers**, grant only the scopes it needs, and revoke it if anything looks wrong. # List open slots (/docs/api-reference/availability/getAvailability) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Cancel a booking (/docs/api-reference/bookings/cancelBooking) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create a booking (/docs/api-reference/bookings/createBooking) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Retrieve a booking (/docs/api-reference/bookings/getBooking) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List bookings (/docs/api-reference/bookings/listBookings) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Reschedule a booking (/docs/api-reference/bookings/rescheduleBooking) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Retrieve a service (/docs/api-reference/catalog/getService) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List locations (/docs/api-reference/catalog/listLocations) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List services (/docs/api-reference/catalog/listServices) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Book a contact into a class (/docs/api-reference/classes/bookClass) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Cancel a contact's spot (/docs/api-reference/classes/cancelClassSpot) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Check a booked contact in (/docs/api-reference/classes/checkInClassSpot) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Retrieve a class with its roster (/docs/api-reference/classes/getClass) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List classes (the timetable) (/docs/api-reference/classes/listClasses) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create a contact (/docs/api-reference/contacts/createContact) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Retrieve a contact (/docs/api-reference/contacts/getContact) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List contacts (/docs/api-reference/contacts/listContacts) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update a contact (/docs/api-reference/contacts/updateContact) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Verify a key (/docs/api-reference/general/ping) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Retrieve a gift card (/docs/api-reference/gift-cards/getGiftCard) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List gift cards (/docs/api-reference/gift-cards/listGiftCards) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Check a member in (/docs/api-reference/members/checkInMember) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List a membership's check-ins (/docs/api-reference/members/listMemberVisits) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Resolve a member and check eligibility (/docs/api-reference/members/resolveMember) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List a contact's memberships (/docs/api-reference/memberships/listContactMemberships) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List membership plans (/docs/api-reference/memberships/listMembershipPlans) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List package templates (/docs/api-reference/packages/listPackages) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Authentication (/docs/authentication) The Booking Pro API authenticates every request with an API key sent as a bearer token: ``` Authorization: Bearer bp_sk_live_... ``` ## Keys are per-company [#keys-are-per-company] A key is bound to exactly one company. Whatever company minted the key is the company the request acts on, you never pass a company id, and you cannot reach another company's data with your key. If a request body or query includes an id that belongs to a different company, the API rejects it. ## Scopes [#scopes] Each key carries a set of scopes. A request that needs a scope the key does not have returns `403 insufficient_scope`. Grant only what an integration needs. | Scope | Grants | | ------------------- | ----------------------------------------------------------------- | | `read:availability` | Read open slots | | `read:catalog` | Read services and locations | | `read:contacts` | Read contacts | | `write:contacts` | Create and update contacts | | `read:bookings` | Read bookings | | `write:bookings` | Create, cancel, reschedule bookings | | `read:giftcards` | Read gift cards | | `read:packages` | Read package templates | | `read:memberships` | Read membership plans and what a customer holds | | `write:memberships` | Sell a membership to a contact | | `checkin:members` | Resolve and check in members (kiosk, turnstile, biometric device) | ## Locations [#locations] A key is scoped to specific locations, or to **all** locations in the company (an explicit choice at mint time). Reads and writes are always confined to the key's locations. `/ping` shows which locations a key can act on. ## Environments [#environments] Keys are prefixed by environment: * `bp_sk_live_...` acts on your real data. A sandbox test environment (`bp_sk_test_...`) is on the roadmap; today only live keys are issued. ## Keeping keys safe [#keeping-keys-safe] * **Never** put a key in browser or mobile client code. Treat it like a password. * Store keys in a secret manager or environment variables. * Rotate periodically: create a new key, switch traffic, then revoke the old one. * If a key leaks, revoke it immediately in **Settings -> Developers**. Revocation is instant. ## Errors [#errors] | Status | Meaning | | ------------------------ | ------------------------------------- | | `401 invalid_api_key` | Missing, malformed, or revoked key | | `403 insufficient_scope` | Key lacks the required scope | | `403` (location) | Target is outside the key's locations | See [Errors](/docs/errors) for the full envelope. # Changelog (/docs/changelog) ## 2026-09: Classes (gyms and studios) [#2026-09-classes-gyms-and-studios] * **Classes**: `GET /classes` (the timetable with booked / spots left), `GET /classes/{id}` (roster with what covers each spot, plus the waitlist), `POST /classes/{id}/book`, `POST /classes/{id}/cancel`, `POST /classes/{id}/check-in` (consumes the covering membership visit or package credit; honors the member check-in alert blocks). * New scopes: `read:classes`, `write:classes`. ## 2026-09: Members and check-in [#2026-09-members-and-check-in] * **Members**: `POST /members/resolve` (map a member code or fob UID to a member and check eligibility, for kiosks, turnstiles, and biometric devices), `POST /members/check-in` (record a facility check-in), `GET /members/{id}/visits`. * **Memberships**: `GET /contacts/{id}/memberships` (what a customer holds, with status and remaining credits). * New scopes: `write:memberships`, `checkin:members`. ## 2026-09: v1 launch [#2026-09-v1-launch] The first public version of the Booking Pro API: * **General**: `GET /ping`. * **Availability**: `GET /availability`. * **Catalog**: `GET /services`, `GET /services/{id}`, `GET /locations`. * **Contacts**: `GET /contacts`, `GET /contacts/{id}`, `POST /contacts`, `PATCH /contacts/{id}`. * **Bookings**: `GET /bookings`, `GET /bookings/{id}`, `POST /bookings`, `POST /bookings/{id}/cancel`, `POST /bookings/{id}/reschedule`. * **Stored value (read)**: gift cards, package templates, membership plans. * Per-key API keys with scopes, cursor pagination, idempotency keys, and a published Claude Agent Skill. # Classes (/docs/classes) Gyms and studios run **group classes** next to one-to-one appointments: a class has spots, a roster, and a waitlist, and a member's spot is covered by their membership or package when they check in. These endpoints need the `read:classes` and `write:classes` scopes. A salon or spa key gets an empty timetable. ## The timetable [#the-timetable] `GET /classes` lists the classes in a window (default the next 7 days, at most 31\), earliest first, with how many spots are booked and left: ```bash curl "https://api.bookingpro.ai/api/v1/classes?location_id=loc_...&from=2026-09-16T00:00:00Z&to=2026-09-17T00:00:00Z" \ -H "Authorization: Bearer bp_sk_live_..." ``` ```json { "data": [ { "id": "cls_...", "location_id": "loc_...", "service_id": "svc_...", "service_name": "Spin Class", "technician_name": "Dana Levi", "start": "2026-09-16T17:00:00Z", "end": "2026-09-16T17:45:00Z", "capacity": 16, "booked": 11, "spots_left": 5, "waiting": 0, "status": "scheduled" } ], "next_cursor": null } ``` `GET /classes/{id}` adds the roster (each spot with its `status` and what covers it in `paid_by`) and the waitlist in order. ## Book a spot [#book-a-spot] `POST /classes/{id}/book` with a `contact_id` holds a spot. Nothing is charged at booking; `paid_by` tells you what will be consumed at check-in: * `membership`: a plan that covers the service with a visit available (per-service limits such as "2 classes a week" are honored by class start time). * `package`: a prepaid credit. * `free_class`: one of the free classes the service allows before payment. * `none`: nothing covers it; the front desk charges at checkout. When the service **requires** a membership or package and the contact has none, you get `422 entitlement_required`. Pass `allow_without_entitlement: true` to book anyway (the desk charges later), which is what staff do. A full class returns `409 class_full`; members can join the waitlist from the customer app. Signup cutoff, max per day and first-timers-only rules return `422 booking_rule` with a plain-language message. Send an `Idempotency-Key`. ## Cancel a spot [#cancel-a-spot] `POST /classes/{id}/cancel` with the `contact_id` releases the spot. Inside the location's cancellation window it is a **late cancel** (`late: true`) and the covering credit is still consumed, unless you pass `excuse: true`. A freed spot goes to the waitlist automatically. ## Check in [#check-in] `POST /classes/{id}/check-in` with the `contact_id` marks the spot checked in and consumes the covering credit. The location's member check-in alert blocks apply: a blocked check-in returns `422 checkin_blocked` with `reasons` (for example `past_due`, `no_card`, `form_missing`); a manager overrides at the desk. ## What you cannot do here yet [#what-you-cannot-do-here-yet] Creating class times (the weekly series), the waitlist, and standing enrollments are managed in the dashboard and the customer app. Tell us if your integration needs them. # Errors (/docs/errors) Every error response uses the same JSON envelope and an appropriate HTTP status: ```json { "error": { "type": "invalid_request", "code": "missing_field", "message": "first_name is required.", "param": "first_name", "request_id": "req_9f2b7c" } } ``` * **`type`** groups the failure (see below). Branch on this. * **`code`** is a stable, machine-readable string. Safe to switch on. * **`message`** is human-readable. Do not parse it; it may change. * **`param`** names the offending field, when applicable. * **`request_id`** identifies this exact request. Quote it when contacting support. ## Types and statuses [#types-and-statuses] | `type` | HTTP | When | | ----------------- | --------- | ------------------------------------------------------------------ | | `invalid_request` | 400 / 422 | Malformed or failing-validation input | | `authentication` | 401 | Missing or invalid API key | | `permission` | 403 | Missing scope, or target outside the key's locations | | `not_found` | 404 | The resource does not exist in your company | | `conflict` | 409 | The requested time is no longer available | | `rate_limit` | 429 | Too many requests, back off (see [Rate limits](/docs/rate-limits)) | | `server` | 500 / 503 | Something failed on our side, retry with backoff | ## Handling conflicts on booking [#handling-conflicts-on-booking] `POST /bookings` and `/bookings/{id}/reschedule` return `409 slot_unavailable` if the slot was taken between your availability check and your write. Re-fetch [availability](/docs/api-reference/availability/getAvailability) and offer the customer another time, do not blindly retry. ## Retrying safely [#retrying-safely] Retry only `429` and `5xx`, with exponential backoff. For creates, always send an [`Idempotency-Key`](/docs/idempotency) so a retry cannot create a duplicate. # Idempotency (/docs/idempotency) Network requests fail in ambiguous ways: your request may have succeeded even though you never got the response. To make retries safe, send an **`Idempotency-Key`** header on create requests. ```bash curl -X POST https://api.bookingpro.ai/api/v1/bookings \ -H "Authorization: Bearer bp_sk_live_..." \ -H "Idempotency-Key: 8f1c2d3e-booking-42" \ -H "Content-Type: application/json" \ -d '{ "location_id": "...", "service_id": "...", "start": "2026-09-10T14:00:00Z", "contact_id": "..." }' ``` ## How it works [#how-it-works] * Generate a unique key per logical operation (a UUID works well). * If you retry with the **same** key, you get the **original** result back instead of a second booking/contact. * Use a **new** key for a genuinely new operation. ## Where it applies [#where-it-applies] `Idempotency-Key` is honored on: * `POST /bookings` * `POST /contacts` It is safe to send on any create. Reads and updates are naturally idempotent and ignore the header. # Booking Pro API (/docs) The Booking Pro API is a REST API for service businesses, salons, barbershops, spas, and wellness studios, to integrate their Booking Pro account with anything: a custom website, an automation, a partner platform, or an AI agent. Everything a receptionist can do through the dashboard, you can do through the API: check open time slots, create and reschedule appointments, manage the customer list, and read the catalog and stored value. ## Get started [#get-started] ## Base URL [#base-url] ``` https://api.bookingpro.ai/api/v1 ``` ## Conventions [#conventions] * **Auth**: every request sends `Authorization: Bearer bp_sk_live_...`. * **Money is in cents.** `price_cents: 5000` means $50.00. * **Times are ISO 8601 (UTC).** * **Errors** are a consistent envelope: `{ "error": { "type", "code", "message", "request_id" } }`. * **Lists** are cursor-paginated: `{ "data": [...], "next_cursor": "..." }`. ## Built for LLMs [#built-for-llms] Every page on this site is available as Markdown: click **Copy Markdown** at the top of any page, or append `.md` to its URL. There is a site-wide [`/llms.txt`](/llms.txt) index and a full [`/llms-full.txt`](/llms-full.txt). We also publish a ready-to-use [Claude Agent Skill](/docs/ai-agents). # Members and check-in (/docs/members-and-check-in) If you run a facility (a gym, a studio, a club), you can wire your own hardware, a kiosk, a turnstile, or a biometric face/fingerprint scanner, to Booking Pro so that when someone arrives, you can look up their membership and let them in. The flow is two steps: **resolve**, then **check in**. ## The identifier [#the-identifier] A member is identified by one of: * their **member code** (a short scannable code shown in the customer app and on their contact record), or * an **RFID fob or card UID** (the value a tap reader emits). A biometric device does not send a face to us. Instead, your device maps a face (or fingerprint) to one of these identifiers on your side, then sends us the identifier. We resolve it to the member. ## Step 1: Resolve and check eligibility [#step-1-resolve-and-check-eligibility] Call `POST /members/resolve` with the identifier and the location. It returns the member and whether they are allowed in right now, without recording anything: ```bash curl -X POST https://api.bookingpro.ai/api/v1/members/resolve \ -H "Authorization: Bearer bp_sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "identifier": "A1B2C3D4", "location_id": "loc_..." }' ``` ```json { "found": true, "membership_id": "mem_...", "contact_id": "con_...", "resolved_by": "code", "valid_for_checkin": true, "reason": "eligible", "plan_name": "Unlimited Monthly", "status": "active" } ``` Use `valid_for_checkin` to decide whether to open the door. If it is `false`, `reason` tells you why (for example `no_credits_remaining`, `membership_not_active`, `location_not_covered`). Nothing is logged, so you can call resolve as often as you like. ## Step 2: Check in [#step-2-check-in] Once you decide to let them in, call `POST /members/check-in` to record the visit. You can pass the same `identifier` (we resolve it again for you) or the explicit `membership_id` + `contact_id` from step 1: ```bash curl -X POST https://api.bookingpro.ai/api/v1/members/check-in \ -H "Authorization: Bearer bp_sk_live_..." \ -H "Idempotency-Key: door-3-2026-09-08T14:03:11Z" \ -H "Content-Type: application/json" \ -d '{ "identifier": "A1B2C3D4", "location_id": "loc_...", "method": "device" }' ``` ```json { "checked_in": true, "visit_id": "vis_...", "remaining_credits": 11 } ``` Always send an **`Idempotency-Key`** (for example the door id + timestamp) so a double-scan or a network retry never logs two visits. `remaining_credits` is `null` for unlimited plans. If the check-in is not allowed, you get a `422` with a clear reason in the message (the same rules the dashboard enforces: active membership, the plan allows facility access, the location is covered, cross-location rules, and available credits). ## Scope [#scope] Both endpoints require the `checkin:members` scope. Mint a dedicated key for your door/kiosk device with only that scope, and revoke it if the device is lost. ## Reading a member's history [#reading-a-members-history] `GET /members/{membership_id}/visits` returns recent check-ins (newest first), so you can show attendance or reconcile your turnstile logs. # Pagination (/docs/pagination) List endpoints return a page of results plus a cursor for the next page: ```json { "data": [ { "id": "..." }, { "id": "..." } ], "next_cursor": "eyJvIjoyNX0" } ``` * Pass `?limit=` to set the page size (1-100, default 25). * Pass `?cursor=` with the previous response's `next_cursor` to get the next page. * When `next_cursor` is `null`, you have reached the end. ```bash # first page curl "https://api.bookingpro.ai/api/v1/contacts?limit=50" \ -H "Authorization: Bearer bp_sk_live_..." # next page curl "https://api.bookingpro.ai/api/v1/contacts?limit=50&cursor=eyJvIjoyNX0" \ -H "Authorization: Bearer bp_sk_live_..." ``` Cursors are opaque, do not construct or parse them. Loop until `next_cursor` is `null` to drain a full collection. # Quickstart (/docs/quickstart) ## 1. Create an API key [#1-create-an-api-key] In your Booking Pro dashboard, open the left sidebar, click **Settings**, then **Developers** (you must be an owner or admin). Click **Create key**, choose the scopes you need, and copy the key. You will see the full key **once**. It looks like: ``` bp_sk_live_a1b2c3d4e5f6g7h8i9j0 ``` Store it in a secret manager or an environment variable. If you lose it, revoke it and create a new one. ## 2. Make your first request [#2-make-your-first-request] Every request sends the key as a bearer token. Start with `/ping`, which just echoes back what your key can do: ```bash curl https://api.bookingpro.ai/api/v1/ping \ -H "Authorization: Bearer bp_sk_live_..." ``` ```json { "company_id": "3b1e....", "environment": "live", "scopes": ["read:catalog", "read:bookings", "write:bookings"], "location_ids": ["a1c2...."], "request_id": "req_9f2b7c" } ``` ## 3. Do something real [#3-do-something-real] List your locations, then your services at one of them: ```bash curl https://api.bookingpro.ai/api/v1/locations \ -H "Authorization: Bearer bp_sk_live_..." curl "https://api.bookingpro.ai/api/v1/services?location_id=a1c2...." \ -H "Authorization: Bearer bp_sk_live_..." ``` ## 4. Book an appointment [#4-book-an-appointment] Check availability, then create the booking. Send an `Idempotency-Key` so a retry never double-books: ```bash # find open slots curl "https://api.bookingpro.ai/api/v1/availability?location_id=a1c2....&service_id=svc_....&date=2026-09-10" \ -H "Authorization: Bearer bp_sk_live_..." # book one curl -X POST https://api.bookingpro.ai/api/v1/bookings \ -H "Authorization: Bearer bp_sk_live_..." \ -H "Idempotency-Key: 8f1c-quickstart-1" \ -H "Content-Type: application/json" \ -d '{ "location_id": "a1c2....", "service_id": "svc_....", "start": "2026-09-10T14:00:00Z", "contact": { "first_name": "Jordan", "phone": "+15551234567" } }' ``` That is the whole loop. Explore every endpoint in the [API reference](/docs/api-reference/general/ping), which has an interactive playground. # Rate limits (/docs/rate-limits) Each API key has its own rate limit. The default is **100 requests per minute**; some keys are configured higher. When you exceed it you get `429` with a `Retry-After` header. ## Headers [#headers] Every response includes your current standing: | Header | Meaning | | ----------------------- | --------------------------------------------- | | `X-RateLimit-Limit` | Requests allowed per window | | `X-RateLimit-Remaining` | Requests left in the current window | | `X-RateLimit-Reset` | Unix seconds when the window resets | | `Retry-After` | (on 429 only) seconds to wait before retrying | ## Backing off [#backing-off] On a `429`, wait `Retry-After` seconds, then retry with exponential backoff and jitter. Do not hammer, repeated over-limit requests keep the window full. ``` if response.status == 429: sleep(response.headers["Retry-After"]) retry() ``` ## Staying under the limit [#staying-under-the-limit] * Prefer one paginated list call over many single-record calls. * Cache catalog and location data, it rarely changes. * Use webhooks (coming soon) instead of polling for changes. # Versioning (/docs/versioning) The API is versioned in the URL path: ``` https://api.bookingpro.ai/api/v1 ``` ## Our promise [#our-promise] Within `v1` we only make **additive** changes: * New endpoints. * New optional request fields. * New fields in responses. Your integration should ignore unknown response fields so these additions never break it. ## Breaking changes [#breaking-changes] A breaking change (removing a field, renaming one, changing a type, or changing required inputs) ships under a new version, `v2`, and `v1` keeps working. We announce new versions and deprecations well in advance in the [changelog](/docs/changelog).