Errors & reliability
Booking API error codes, custom-OTA push semantics, the idempotency contract, the self-healing sweeps, and rate limits.
Booking API errors
Errors return a non-2xx status and a tRPC error body; the stable fields are error.data.code and error.data.httpStatus.
code | HTTP | Meaning | What to do |
|---|---|---|---|
UNAUTHORIZED | 401 | missing/invalid API key | check the x-api-key header and the key's environment |
BAD_REQUEST | 400 | invalid input or a domain rule rejected it | fix the input; the message says what failed |
NOT_FOUND | 404 | the resource isn't in this org | verify the id and the key's org |
CONFLICT | 409 | the nights are no longer available | re-query availability and offer an alternative |
CONFLICT is a normal outcome, the ledger refusing to double-book, not a server fault. Always handle it.
Custom-OTA push results
When StayBind calls your endpoints, your HTTP status decides what happens next:
| Your response | StayBind treats it as | Effect |
|---|---|---|
2xx | success | done |
5xx / network error | retryable | back off and retry; the nightly full sync re-pushes anyway |
4xx | permanent rejection | stop retrying, flag the connection for the operator (usually a bad mapping) |
When StayBind receives your reservation webhook, it always answers 200, see the reliability contract.
Idempotency
Idempotency is the backbone of safe at-least-once delivery. Two ids carry the contract on a reservation:
providerResIdis the per-delivery key. StayBind ingests each distinct value exactly once; re-sending the same one (a webhook retry, an overlapping reconcile pull) is a no-op. Make it unique per delivery/revision.providerBookingIdis the stable booking id, constant across revisions. Amodifiedorcancelleddelivery uses it to find and update the existing booking instead of creating a new one. Without it, revisions look like new bookings.
On your side, apply outbound availability/rate/restriction pushes as upserts keyed by date (never increments), so order and duplicates don't matter.
Payment captures are likewise idempotent on the payment id, a re-delivered capture confirms the booking once.
The self-healing sweeps
You get these for free; they're why a dropped request never loses a booking:
| Sweep | Cadence | What it catches |
|---|---|---|
| Reconcile pull | ~every 15 min | a reservation whose webhook was missed (pulled from your GET /reservations) |
| Nightly full sync | once daily, off-peak | availability drift (re-pushes the forward horizon to every channel) |
| Acknowledge after commit | per delivery | a delivery is acked only after it's durably written, so a crash mid-ingest re-surfaces it rather than dropping it |
Conflicts and overbooking
The ledger's database constraint makes overlapping bookings impossible to commit. Two outcomes follow:
- A booking attempt for taken nights returns
CONFLICT(Booking API) or is reported in the webhook'sconflictsarray (custom OTA). - A true cross-channel overbooking (two OTAs genuinely sold the same night within the propagation window) is terminal, the booking is real on the OTA, so StayBind records it and alerts a human to relocate or refund, rather than silently dropping it.
Rate limits
- Outbound (StayBind → your OTA): throttled per account so a busy operator never floods your API; bursts are coalesced. Make your endpoints accept batched
updatesarrays. - Inbound (you → StayBind): post freely, idempotency makes retries safe. Use sane client-side back-off on
5xx, and prefer batching multiple reservations into one webhook call over many tiny calls.