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 | 4xx | Nothing sellable for that stay | Business outcome, not an error to retry |
rate_changed | 4xx | The price moved since your quote | Re-quote, then re-book |
not_implemented | 501 | Contract published, data not yet served. details.pending names what we are waiting on | Build against it; it will start returning data |
internal_error | 500 | Our fault | Retry with backoff; report with request_id |
Two caveats while the surface is still 501 (§0):
- Six of these codes have no emitter yet.
forbidden,not_found,conflict,no_availability,rate_changedandinternal_errorare part of the published contract and are what you will receive once the data goes live, but nothing in the app returns them today. Write the handlers; do not expect to exercise them in sandbox. 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.
