Keys
A key belongs to one business and is created in your own settings. It is shown once, at creation, and stored hashed — we cannot show it to you again, and neither can anybody who reaches the database.
Every request carries it as a bearer token. The tenant is resolved from the key and never from the body, so a key cannot be pointed at somebody else's business by changing a field.
curl https://app.voltsbook.com/v1/ping \
-H "Authorization: Bearer vb_live_…" /v1/ping echoes the business the key
resolved to and the webhook topics this deployment can emit. Use it as a credential
check before you build anything else.
Idempotency
Send Idempotency-Key on any write. Repeat
the request with the same key and you get the original response back rather than a
second appointment.
This matters most on the request you are least sure landed — a timeout where the booking may or may not exist. Without a key, the safe thing to do is read before retrying; with one, retrying is the safe thing to do.
curl -X POST https://app.voltsbook.com/v1/bookings \
-H "Authorization: Bearer vb_live_…" \
-H "Idempotency-Key: 8f2c1e40-…" \
-H "Content-Type: application/json" \
-d '{"serviceId":"…","staffId":"…","startAt":"2026-08-11T09:30:00Z",
"customer":{"firstName":"Ada","email":"ada@example.com"}}' Meta
Check a key works and find out what this deployment can do before you build against it.
/v1/ping Health and capability probe
Echoes the tenant resolved from the API key, the API version, and the webhook topics this deployment can emit. Integrators use it as a credential check.
/v1/openapi.json No key needed This document
Served without authentication so a client can be generated before a key is issued.
Catalog
What is bookable: services with their durations and prices, the people who perform them, and where.
/v1/services Bookable services
Includes each service’s duration and price so an agent can quote before booking.
/v1/staff Bookable staff
/v1/locations Bookable locations
/v1/products Products sellable alongside a booking
What `POST /v1/bookings` will accept in `products`. Pass `serviceId` to narrow the list to what that service is offered with; an empty `serviceIds` on a product means every service. `stockQuantity` is ADVISORY — it is a display figure, never a reservation, and what can actually be sold is settled inside the booking transaction. The business’s own purchase cost is not published here.
Availability
The times a booking would actually be accepted. Computed from opening hours, the person, the service, holidays, blocked time and what is already booked — so a slot you are offered is one you can take.
/v1/availability Bookable slots over a date range
The same availability engine, with the same rules, that a human booking through the widget gets. Slots held by a live reservation are already excluded. Omit `staffId`, or send `any`, to search across every qualified staff member.
Reservations
Hold a slot briefly while a customer finishes deciding. A reservation is not a booking and expires on its own.
/v1/reservations Hold a slot briefly
Optional first step of the 3-step flow. Returns a reservation id and its expiry; the hold is counted as occupancy so no one else is offered the slot. `ttlSeconds` is CLAMPED to 60–3600 rather than rejected outside it. Send either `startAt`, or both `date` and `time`.
/v1/reservations/{id} Inspect a hold
/v1/reservations/{id} Release a hold early
Puts the slot back on the market immediately instead of waiting for the TTL.
Bookings
Create, read, reschedule and cancel appointments. Every write goes through the same pipeline the booking page uses, so nothing reached this way behaves differently.
/v1/bookings Search bookings
Paginated. `phone` is digit-normalised before matching, so a national and an international rendering of the same number both find the booking. The shape is identical to the webhook payload.
/v1/bookings/{id} One booking
/v1/bookings Idempotency-Key Create a booking
Send either a `reservationId` (3-step flow) or a `serviceId` with `startAt` (or `date` and `time`) for the 2-step flow. The full booking pipeline runs — validation, server-side pricing, workflows and webhooks — under CUSTOMER-origin rules: the API never receives the admin overlap bypass. Prices and totals are recomputed server-side and are never accepted from the caller. Supply an `Idempotency-Key` to make a retry replay the original response instead of double-booking.
/v1/bookings/{id}/cancel Idempotency-Key Cancel a booking
Takes the same status-change path as an admin cancellation, so workflows and webhooks fire identically. `phone` IS REQUIRED: it must be the booking customer’s number, compared on trailing digits so a national and an international form of the same number match. An absent phone is refused with `caller_verification_required` and a wrong one with `forbidden` — an appointment id is not a secret, so the number is what authorizes acting on someone else’s booking. A booking whose customer has no phone on file cannot be cancelled through this API.
/v1/bookings/{id}/reschedule Idempotency-Key Move a booking
Validates the new slot with the same engine a fresh booking uses. Send either `startAt`, or both `date` and `time`. `phone` IS REQUIRED, on the same terms as cancel: moving a stranger’s appointment is the same authority as cancelling it.
Webhooks
Register an endpoint and choose topics. Each delivery is signed, retried with backoff when your endpoint is unhappy, and the endpoint is disabled automatically if it stays broken — a dead destination should not become a queue that never drains.
Verify the signature before you trust the body. It is an HMAC over the raw request bytes with your endpoint's secret, and comparing it is the only thing standing between your system and anyone who guesses the URL.
Topics (6)
-
booking.created -
booking.rescheduled -
booking.canceled -
booking.status_changed -
appointment.paid -
service.price_changed
Errors
Every refusal carries a machine-readable code
beside its message. Branch on the code — the prose is written for a human reading a log
and may be improved at any time.
-
SLOT_TAKEN409 - Somebody else took that time between your availability read and your write. Ask for availability again rather than retrying the same slot.
-
OUTSIDE_AVAILABILITY422 - The time is not one this business offers — closed, blocked, or nobody assigned who performs that service.
-
LIMIT_REACHED422 - A booking rule refused it: too many for that customer, that day or that window. The message names which.
-
PLAN_FORBIDS403 - The business’s plan does not include this feature. Not a key problem — the same request would work on a plan that grants it.
-
RATE_LIMITED429 - Too many requests. Back off and retry; the response says for how long.
Need a key?
Keys are created in your own settings, and the API is available on plans that include it. The free plan is enough to try everything else first.