Errors
Every error uses one envelope. The status code says what class of problem it
is. The type field says which problem it is, and type is the field to
branch on. Do not match on message. That text is written for a person, and it
changes.
{ "error": { "type": "validation_failed", "message": "The request was not valid.", "param": "starts_at" }}param is present only on a 422 that names one field. Every other key in the
body is described under 402 below.
The error types
Section titled “The error types”type is stable. A new type can appear, so treat an unknown type as the
class its status code names.
| Status | type |
What happened |
|---|---|---|
| 401 | unauthorized |
The key is missing, or it is not valid. |
| 402 | plan_limit_exceeded |
A plan cap stopped the request. |
| 403 | secret_key_required |
The route needs a secret key. You sent a publishable key. |
| 403 | origin_not_allowed |
The Origin header is not on the allowed list of this publishable key. |
| 403 | turnstile_required |
The publishable-key request did not carry a Turnstile token the API could verify. |
| 403 | forbidden |
The credential is valid. The action is not permitted. |
| 404 | not_found |
No such record, or the record is not on this workspace. |
| 409 | slot_unavailable |
Somebody took that time first. |
| 409 | bookings_paused |
The workspace has paused online bookings. See bookings_paused below. |
| 409 | conflict |
That resource already exists. |
| 409 | booking_not_pending |
The booking is not awaiting confirmation. |
| 409 | no_show_not_applicable |
Only a past confirmed booking can be marked as a no-show. |
| 422 | validation_failed |
The request is malformed. param names the field. |
| 422 | verification_failed |
The 6-digit code was refused. |
| 422 | too_late_to_cancel |
The cancellation window is closed. |
| 422 | outside_working_hours |
The time is outside the working hours of that calendar. |
| 422 | idempotency_mismatch |
The Idempotency-Key was used before, with a different body. |
| 429 | rate_limited |
Too many requests. See the rate limits guide. |
| 429 | daily_booking_limit |
This caller has written its allowance of bookings at this workspace today. See the rate limits guide. |
| 429 | booking_volume_refused |
The account’s platform-safety volume ceiling was met. Retrying does not help. See the rate limits guide. |
| 500 | internal_error |
A fault on our side. |
| 503 | transient_failure |
Two writes contended. Retry the request. |
Retry a 503. Retry a 429 typed rate_limited or daily_booking_limit
after the Retry-After it carries. The rate limits guide
has the detail. Do not retry a 429 typed booking_volume_refused: it
does not clear until the account’s billing period rolls over. Do not retry
any other 4xx: the same request gets the same answer.
402: a plan limit refused the request
Section titled “402: a plan limit refused the request”A 402 means a plan cap stopped the request. The fields in the body depend on
the key you used.
| Key | The body contains |
|---|---|
Publishable (pk_) |
type, message |
Secret (sk_) |
type, message, dimension |
Account (ak_) |
type, message, dimension, limit, used |
This is deliberate. A publishable key runs in a browser, where a stranger can read the response. Such a caller must not learn the plan state of the business.
A secret key gets the dimension and no numbers. Caps are counted at the account, and a secret key reaches one workspace. The numbers tell a key issued for one workspace about its siblings.
dimension is one of two wire values:
| Value | The cap |
|---|---|
staff |
Active calendars. The dashboard calls these “calendars”. |
workspaces |
Active workspaces on the account. |
A 402 with no dimension
Section titled “A 402 with no dimension”dimension can be absent, and used and limit with it. A 402 with no
dimension names no cap, so there is nothing to count against and no number to
show. Show the message and stop.
{ "error": { "type": "plan_limit_exceeded", "message": "This plan limit has been reached.", "dimension": "staff", "limit": 5, "used": 5 }}bookings_paused
Section titled “bookings_paused”A 409. The workspace has paused online bookings, and online_bookings_paused
is true on its workspace settings. These calls answer it, for publishable and
secret keys alike:
GET /v1/availabilityPOST /v1/bookingsPOST /v1/bookings/{id}/reschedulePOST /v1/public/bookings/{token}/reschedule
Cancelling a booking still works, through /v1 and through the manage link.
So do verifying and confirming a booking that already exists, GET /v1/quote,
listing bookings, and the two public GET calls for the workspace and a
booking. The dashboard is not affected.
Availability answers bookings_paused instead of an empty list, so a
paused workspace is not mistaken for one with no free times. Do not retry:
the same request gets the same answer until the workspace turns the pause off.
Read online_bookings_paused on GET /v1/public/workspaces/{slug} to know
before you call.
Two 403 types that read alike
Section titled “Two 403 types that read alike”secret_key_required and origin_not_allowed both come from a publishable
key, and they need different fixes.
secret_key_required means the route is a server route. Send a secret key from
your server.
origin_not_allowed means the browser sent an Origin header that this key
does not list. Add the origin to the key, or use the hosted booking page.
CAUTION: The origin list is not a security boundary. GET /v1/public/workspaces/{slug}
gives the publishable key to anybody who holds the slug, by design. The list
stops a third party from embedding your key in their own site. It does not stop
a script.
turnstile_required
Section titled “turnstile_required”Also a publishable-key 403, and also collapsed on purpose: it covers a
missing token, an invalid one, a token solved on the wrong hostname, and
Cloudflare being unreachable, with nothing in the response to tell them
apart.
If you create bookings from your server with a secret key, this cannot reach you. If you create them from a browser frontend you built yourself, see the booking flow guide for the two ways to satisfy it. There is no site key of your own to configure here.
Notes on two more
Section titled “Notes on two more”A 404 also covers “this record belongs to another workspace”. We do not
answer 403 there, because a 403 confirms that the record exists.
verification_failed collapses four causes: a wrong code, an expired hold, too
many attempts, and an unknown booking. They are not told apart on purpose. A
response that told them apart is an oracle for a 6-digit code.