Skip to content

Errors

Every error response is application/problem+json (RFC 9457):

{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "No such resource for this tenant.",
"code": "resource_not_found"
}
  • status always matches the HTTP status code.
  • detail is human-readable and safe to log — it’s written for you to debug with, not for you to show a guest verbatim.
  • code is a stable, machine-readable string. Match your error handling on code, never on detail text, which can change wording without notice.

If you get an unexpected 404, read the code first. It tells you which check failed. See Authentication errors below. Then check, in this order: the resource ID, whether you’re using the key kind (sk_/pk_) the route expects, whether your key holds the scope the route needs, and — for a pk_ key — whether the request’s Origin header is on that key’s allowlist, and for an sk_ key whether your server’s IP address is on that key’s IP list (if it has one).

Every route that takes an API key can return these.

codeStatusMeaning
missing_authorization401The Authorization: Bearer sk_… or pk_… header is missing or malformed.
unknown_api_key404The key does not exist, or it was revoked.
key_kind_not_allowed404This route does not accept this kind of key. For example, a pk_ key on a route that needs sk_.
insufficient_scope404The key does not hold the scope this route needs. detail names the scope. See Secret key scopes.
origin_not_allowed404A pk_ request came from an origin that is not on the key’s list.
ip_not_allowed404An sk_ request came from an IP address that is not on the key’s list.
rate_limited429The key went over a rate limit. See Rate limits.
key_auth_not_configured404A safety net. A route with no allowed key kind refuses every key. Routes for dashboard staff only (for example /v1/api-keys) answer this to API keys.
EndpointcodeStatus
GET /v1/availabilityinvalid_window (from is not before to)400
GET /v1/availability, POST /v1/quotesresource_not_found404
GET /v1/availability, POST /v1/quotesvalidation_failed (the window is longer than 92 nights for availability, 366 for quotes)400
GET /v1/resources/{id}/calendarinvalid_window, range_too_large (more than 92 nights)400
GET /v1/resources/{id}/slotsnot_hourly (the resource is not an hourly space), invalid_duration (not one of the space’s durations)400
GET /v1/resources/{id}/slotsresource_not_found404
POST /v1/quotesrate_plan_required, invalid_window, invalid_duration (hourly space: the length is not an allowed duration)400
POST /v1/quotesrate_plan_not_found404
POST /v1/quotesunavailable (carries reasons)409
POST /v1/quotesno_pricing_rule (carries missingDates), fx_rate_unavailable422
POST /v1/holdsidempotency_key_required, invalid_quote_token, quote_expired, guest_required (a pk_ hold on an hourly space has no guest)400
POST /v1/holdsresource_locked (carries Retry-After), hold_conflict (the time is held or booked, or too close to another booking because of a buffer)409
POST /v1/holdsrate_limited (hold-creation rate), active_hold_limit_reached (too many active holds — see Rate limits)429
POST /v1/holds/{id}/otphold_not_found404
POST /v1/holds/{id}/otphold_not_held (the hold is expired, confirmed, or cancelled)409
POST /v1/holds/{id}/otp/verifybad_code (a wrong, expired, or used-up code — all three look the same on purpose)400
guest-OTP routesrate_limited429
POST /v1/holds/{id}/confirminvalid_guest_token, wrong_reservation400
POST /v1/holds/{id}/confirmhold_not_found404
POST /v1/holds/{id}/confirmnot_confirmable, payment_required (no succeeded payment yet for this hold — see Holds and confirmation)409
POST /v1/holds/{id}/payment-intentidempotency_key_required400
POST /v1/holds/{id}/payment-intentreservation_not_found404
POST /v1/holds/{id}/payment-intentreservation_not_holdable, stripe_not_configured, already_paid409
POST /v1/holds/{id}/checkout-sessionidempotency_key_required, invalid_redirect_url400
POST /v1/holds/{id}/checkout-sessionreservation_not_found404
POST /v1/holds/{id}/checkout-sessionreservation_not_holdable, stripe_not_configured, already_paid, payment_already_initiated409
GET /v1/bookings, GET /v1/paymentsinvalid_cursor400
GET/PATCH/DELETE /v1/bookings/{id}booking_not_found404
PATCH/DELETE /v1/bookings/{id}illegal_transition409
POST /v1/bookings/{id}/confirmbooking_not_found404
POST /v1/bookings/{id}/confirmnot_confirmable (the booking is not held, its hold expired, or an online payment already confirmed it; detail names the state)409
POST /v1/bookingsidempotency_key_required, invalid_window, rate_plan_required400
POST /v1/bookingsresource_not_found, rate_plan_not_found404
POST /v1/bookingsunavailable (carries reasons), resource_locked, booking_conflict, idempotency_key_conflict, stripe_not_configured, already_paid, payment_already_initiated, payment_setup_failed409
POST /v1/bookingsno_pricing_rule (carries missingDates), fx_rate_unavailable422
POST /v1/bookings/{id}/refundsbooking_not_found404
POST /v1/bookings/{id}/refundsno_succeeded_payment, stripe_not_configured, refund_exceeds_payment, stripe_refund_failed, nothing_to_refund409
PUT /v1/resources/{id}/rates, PUT /v1/resources/{id}/restrictionsno_room_type (the resource has no room type)409
PUT /v1/resources/{id}/restrictionsunknown_rate_plan_code400
POST /v1/rates/bulkresource_rate_plan_mismatch400
POST /v1/rates/bulkresource_not_found, rate_plan_not_found404
POST /v1/resources, PATCH /v1/resources/{id}invalid_resource (the fields do not fit the resource’s vertical), invalid_timezone (not an IANA zone name)400
POST /v1/resourcesroom_type_not_found404
POST /v1/room-typesduplicate_code409
GET/PUT …/slot-rules, GET/PUT …/hourly-pricing, GET/PUT …/opening-hoursnot_hourly (the resource is not an hourly space)400
PUT …/opening-hoursinvalid_hours (not in order, overlapping, or not in 5-minute steps)400
GET/PUT …/slot-rules, GET/PUT …/hourly-pricing, GET/PUT …/opening-hoursresource_not_found404
GET/POST /v1/webhook-endpoints, DELETE /v1/webhook-endpoints/{id}webhook_endpoint_not_found404
PATCH /v1/api-keys/{id} (dashboard session)allowlist_kind_mismatch (origins sent for a secret key, or IPs for a publishable key)400
PATCH /v1/api-keys/{id}, DELETE /v1/api-keys/{id} (dashboard session)api_key_not_found404
POST /v1/webhooks/stripeinvalid_signature (this route is for Stripe, not for you)400

Two parts of the API are switched off on the current deployment. Their routes still exist, so they answer with 503 and a stable code. They never move data.

RoutescodeStatus
Channel connections, channel conflicts, iCal feed tokens, GET /v1/feeds/{token}.icschannel_unavailable503
POST /v1/quotes/rental, GET/PUT /v1/rate-plans/{id}/rental-tiersrental_unavailable503

Do not retry a 503 with one of these codes. It will not succeed until the feature is switched on. See the Changelog.

codeStatusWhen
validation_failed (carries errors)400The request body, query or path does not match the route’s schema. errors is a list of { path, message }.
rate_limited429Your API key or IP address went over a rate limit. Always sent with Retry-After. See Rate limits.
payload_too_large413The request body is over 64 KB (256 KB for the Stripe webhook).

Every 429 carries a Retry-After header (seconds). Wait at least that long before you retry.

These codes belong to the operator dashboard’s session routes (/v1/auth/*, /v1/users, /v1/tenant, /v1/audit). They are not for sk_ or pk_ integrations, so you will only see them if you call those routes with a dashboard session.

codeStatusWhen
invalid_credentials401The password is wrong, the email is unknown, the email has no active workspace, or the account is locked. All look the same on purpose. A tenantId or tenantSlug that the person does not belong to is ignored. It does not cause this error.
invalid_workspace_selection401GET /v1/auth/workspace-choices or POST /v1/auth/select-workspace: the be_ws_select cookie is missing, expired (it lasts 5 minutes), or not valid, or the tenantId is not one of the person’s active workspaces. One code for all of these. Log in again.
missing_session, invalid_session401The session cookie is missing, expired, or not valid.
missing_refresh_token, invalid_refresh_token, reuse_detected401The refresh cookie is missing, not valid, or was already used.
invalid_reset_token400The password-reset token is not valid, or it expired.
rate_limited429Too many sign-in or password-reset requests. See Rate limits.
user_not_found, tenant_not_found404No such staff account or workspace.
email_already_invited409This email already has a staff account in this workspace.
invites_unavailable503POST /v1/users or POST /v1/users/invite: the server cannot build a working link for the invite email, so it saved nothing. This is a server setting. Retrying does not help. Tell the platform operator.
invalid_cursor400The cursor for the audit list is not valid.

Full per-endpoint error lists live in the API Reference, generated straight from the checked-in OpenAPI spec.

  • Rate limits — every limit, the 429 codes and Retry-After.
  • Authentication — how key kind and scope affect the 404 policy above.