VoltsBook

/v1 API reference

The same pipeline, over HTTP.

A booking made here takes the same lock, the same in-transaction re-check and the same database constraint as one made on the booking page. There is no faster path that skips a rule.

Base URL

https://app.voltsbook.com/v1

Every request

Authorization: Bearer vb_live_…

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.

GET /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.

GET /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.

GET /v1/services

Bookable services

Includes each service’s duration and price so an agent can quote before booking.

GET /v1/staff

Bookable staff

GET /v1/locations

Bookable locations

GET /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.

GET /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.

POST /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`.

GET /v1/reservations/{id}

Inspect a hold

DELETE /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.

GET /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.

GET /v1/bookings/{id}

One booking

POST /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.

POST /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.

POST /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_TAKEN 409
Somebody else took that time between your availability read and your write. Ask for availability again rather than retrying the same slot.
OUTSIDE_AVAILABILITY 422
The time is not one this business offers — closed, blocked, or nobody assigned who performs that service.
LIMIT_REACHED 422
A booking rule refused it: too many for that customer, that day or that window. The message names which.
PLAN_FORBIDS 403
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_LIMITED 429
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.

Take your first booking today.

Free plan, no card, and your trade already set up with real services and prices.

Start free