Skip to content

Errors & rate limits

{ "success": false, "error": { "code": "SLOT_TAKEN", "message": "That slot was just taken. Pick another time." } }

Branch on error.code — it is stable. error.message is English and safe to display, but your own localised copy will read better.

StatusCodeCauseWhat to do
404RESOURCE_NOT_FOUNDUnknown slug, room, hold or tokenCheck the identifier
404SITE_NOT_LIVEThe studio unpublished its siteHide booking; show phone / WhatsApp
403STUDIO_NOT_VERIFIEDOwnership not confirmed yetStudio-side; nothing a guest can do
409ROOM_NOT_BOOKABLEOnline booking off, or room unpriced/inactiveRemove it from your room list
StatusCodeCauseWhat to do
400VALIDATION_ERRORMalformed field, bad duration, idempotency key reused with a different bodyFix the payload; re-derive durations from the room
400CAPTCHA_FAILEDCaptcha token missing, expired or already usedReset the widget, get a fresh token
400PAYMENT_MODE_DENIEDMode not in room.paymentModesOffer only the modes the room allows
StatusCodeCauseWhat to do
409OUTSIDE_HOURSThe room is closed thenRefetch availability — your span maths drifted
409OUTSIDE_BOOKING_WINDOWBreaks minNoticeMin or maxAdvanceDaysClamp the calendar to the window
409SLOT_TAKENSomeone booked it firstRefetch, keep the guest’s details, offer another time
410HOLD_EXPIRED12 minutes passedRestart from the hold with a new idempotency key
409HOLD_NOT_VERIFIEDConfirming before verifyingSend the guest back to the code step
409HOLD_ALREADY_CONFIRMEDDouble submitShow the existing booking
409CANCEL_TOO_LATEInside the cancellation windowShow the studio’s phone
409INVALID_STATEThe action makes no sense nowRe-read the booking
StatusCodeCauseWhat to do
400CODE_INVALIDWrong or expired codeLet them retype; show attempts left
429TOO_MANY_ATTEMPTS5 wrong codesThe hold is burnt — restart
429RATE_LIMIT_EXCEEDEDToo many requestsBack off, see below
StatusCodeCauseWhat to do
409PAYMENTS_UNAVAILABLEStripe not configuredOffer on_site
503SMS_UNAVAILABLESMS provider downOffer channel: "email"
500INTERNAL_ERROROur faultRetry once; include X-Request-ID if you report it
ScopeLimit
Reads (GET /public/*)120 req/min per IP
Writes (holds, verify, resend, confirm, cancel)15 req/min per IP
Holds per contact5 per hour
Holds per IP20 per hour
Code resends3 per hold, ≥ 60 s apart
Verification attempts5 per hold

Exceeding any of them returns 429 RATE_LIMIT_EXCEEDED.

  • GETs are safe to retry.
  • POST /holds is safe to retry with the same idempotencyKey — you get the same hold back, no second SMS.
  • verify, confirm and cancel are not idempotent. On a network timeout, re-read state (GET /holds/{id} is not public, so use the manage endpoint once a booking exists) before firing again.
  • Back off exponentially on 429 and 5xx. Never retry a 4xx other than 429 without changing the request.