code — it’s the stable machine-readable handle.
message is human-readable but English-only; the API doesn’t
localize. detail is operation-specific context (which session,
which scope, which state) for diagnostics, not a user-facing
string.
The HTTP statusCode correlates with the family but isn’t the
contract — multiple codes can share a status (403 covers
NOT_AUTHORIZED, NOT_OPERATOR, NOT_YOUR_SESSION).
How to handle them
Auth + authorization
| Code | Status | What it means | What to do |
|---|---|---|---|
NOT_AUTHENTICATED | 401 | Missing / expired credential. | Re-mint an API key or re-do the SIWE login. |
NOT_AUTHORIZED | 403 | Caller has no permission for the resource. | Check workspace membership / API-key scopes. |
INSUFFICIENT_SCOPE | 403 | API key is missing the scope this endpoint needs. | Mint a new key with the right scope set. |
NOT_OPERATOR | 403 | Operator-only endpoint, caller isn’t one. | Use an operator workspace. |
NOT_YOUR_SESSION | 403 | Caller is neither consumer nor operator of the session. | Verify you’re querying the right session id. |
Domain rules
| Code | Status | What it means | What to do |
|---|---|---|---|
INVALID_INPUT | 400 | Body / query failed validation. detail says which field. | Fix the payload. |
INVALID_STATE | 409 | Operation not allowed in the session’s current state. | Read the current state via get_session and retry the right transition. |
CONFLICT | 409 | Resource conflict (e.g. monotone sequence violation on a heartbeat). | Resolve the conflict server-side (newer sequence, different slug) and retry. |
NO_COVERAGE | 503 | No ONLINE operator covers the requested point. | Widen the request, try a different zone, or wait for an operator to come online. |
OUTSIDE_COVERAGE | 400 | Requested point is outside the operator’s declared coverage circle. | Move the request inside the operator’s range. |
UNAVAILABLE | 503 | Dispatch found no idle operator. | Retry shortly. |
FRAME_NOT_AVAILABLE | 404 | No decoded frame yet — only fires while the session is ASSIGNED (pre-first-frame) or after the publisher disconnects mid-session. Once state === "LIVE", the API only flips after a JPEG is buffered, so the first call is reliable. | Use wait_for_live to wait out the ASSIGNED → LIVE transition rather than spinning on get_frame. |
Wallet + settlement
| Code | Status | What it means | What to do |
|---|---|---|---|
NO_CREDIT | 400 | Workspace pool balance is zero. | Top up via the console. |
INSUFFICIENT_CREDIT | 400 | Pool balance won’t cover the requested duration at the current rate. | Top up or shorten maxDurationSeconds. |
Not-found
| Code | Status | What it means |
|---|---|---|
NOT_FOUND | 404 | Generic — the URL resolved but the resource id is unknown. |
SESSION_NOT_FOUND | 404 | The given sessionId doesn’t exist (or was hard-deleted). |
OPERATOR_NOT_FOUND | 404 | Unknown operator id. |
COVERAGE_AREA_NOT_FOUND | 404 | Legacy coverage area lookup. |
ACCOUNT_NOT_FOUND | 404 | Legacy account lookup. |
Infrastructure (5xx)
These mean our side is sad, not yours. Safe to retry with backoff.| Code | Status | What it means |
|---|---|---|
DATABASE_ERROR | 503 | Postgres unavailable. |
AUTHZ_ERROR | 503 | SpiceDB unavailable. |
VIDEO_ERROR | 503 | Cloudflare Stream unavailable. |
QUEUE_ERROR | 503 | Background job queue unavailable. |
EXTERNAL_ERROR | 502 | A downstream API didn’t respond. |
PAYMENT_ERROR | 502 | A downstream payment processor didn’t respond. (Reserved.) |
INTERNAL_SERVER_ERROR | 500 | Unexpected — please file an issue with the timestamp + request id. |
Versioning + new codes
Codes are append-only — once shipped, a code keeps its meaning. New ones may appear; if youswitch on code, fall through to a
generic handler for the default case (don’t crash on unknown
codes).
@luxxon/sdk’s LuxxonErrorCode type for
the typed enum — it tracks this page.