Skip to content
Keptslot
Start free

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.

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.

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.

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
}
}

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/availability
  • POST /v1/bookings
  • POST /v1/bookings/{id}/reschedule
  • POST /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.

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.

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.

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.