Core concepts
Responses and errors
One envelope everywhere, and the stable error codes to branch on.
One shape, everywhere on this surface.
Success
json
{
"success": true,
"data": { },
"request_id": "3f9c…",
"meta": { }
}
meta is present only when there is something to put in it (pagination, batch
counts).
Error
json
{
"success": false,
"error": {
"code": "missing_scope",
"message": "This API key is not permitted to perform that action.",
"details": { "required_scope": "ari.read" }
},
"request_id": "3f9c…"
}
error.code is a stable machine string — never a number, never a sentence.
Branch on code, never on message; treat the list as part of the contract.
Error codes#
| Code | HTTP | Meaning | What to do |
|---|---|---|---|
unauthorized | 401 | Credentials missing, wrong, revoked, expired, wrong environment, IP blocked, or signature bad/stale/replayed | Check key, secret, environment, clock. Quote request_id to support. |
forbidden | 403 | Permitted key, refused action | Contact Staylah |
missing_scope | 403 | Key lacks a required scope. details.required_scope names the first one missing | Ask for the scope; check /whoami |
rate_limited | 429 | Per-key ceiling exceeded. details.retry_after seconds, also sent as Retry-After | Back off for retry_after |
validation_failed | 422 | Payload rejected. details.fields is a field → messages map | Fix the payload; do not retry unchanged |
not_found | 404 | We have never heard of this identifier | Check the identifier |
unmapped_resource | 404 | The code exists but is not mapped to you. details.resource, details.code | Ask Staylah to map it — distinct from not_found on purpose, because the fix is different |
conflict | 409 | Conflicts with existing state | Read the current state before retrying |
no_availability | 409 | Nothing sellable for that stay. details.reason names which gate failed | Business outcome, not an error to retry blindly — see below |
rate_changed | 409 | The total you sent is not the price for the stay. details.expected_total, details.received_total | Re-quote, then re-book at the price you are given |
not_implemented | 501 | Contract published, data not yet served | No route emits this today (§0) |
internal_error | 500 | Our fault | Retry with backoff; report with request_id |
details.reason on no_availability tells you whether to try other dates or
to call us:
details.reason | Means | Yours to fix? |
|---|---|---|
no_inventory | Fewer rooms left than you asked for | No — try other dates or fewer rooms |
inventory_not_loaded | We have not loaded a calendar for one of those dates | No — ours. Report it |
restricted | A stop-sell, CTA/CTD or MLOS/MaxLOS rule blocks the stay | No — the hotel closed it |
no_rate | No rate is loaded for your account on those dates | No — ours. Report it |
occupancy_not_supported is returned as validation_failed (422), not
no_availability, because it is the one gate you can fix: the party does not
fit the room, or the rate plan is nationality-restricted.
One caveat still stands:
details.retry_afteris currently a fixed60, not the true reset window. Honour the value rather than assuming a minute — it will start being computed without a version bump, and a client hardcoded to 60 will hammer us early.
