Staylah

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#

CodeHTTPMeaningWhat to do
unauthorized401Credentials missing, wrong, revoked, expired, wrong environment, IP blocked, or signature bad/stale/replayedCheck key, secret, environment, clock. Quote request_id to support.
forbidden403Permitted key, refused actionContact Staylah
missing_scope403Key lacks a required scope. details.required_scope names the first one missingAsk for the scope; check /whoami
rate_limited429Per-key ceiling exceeded. details.retry_after seconds, also sent as Retry-AfterBack off for retry_after
validation_failed422Payload rejected. details.fields is a field → messages mapFix the payload; do not retry unchanged
not_found404We have never heard of this identifierCheck the identifier
unmapped_resource404The code exists but is not mapped to you. details.resource, details.codeAsk Staylah to map it — distinct from not_found on purpose, because the fix is different
conflict409Conflicts with existing stateRead the current state before retrying
no_availability4xxNothing sellable for that stayBusiness outcome, not an error to retry
rate_changed4xxThe price moved since your quoteRe-quote, then re-book
not_implemented501Contract published, data not yet served. details.pending names what we are waiting onBuild against it; it will start returning data
internal_error500Our faultRetry 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_changed and internal_error are 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_after is currently a fixed 60, 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.