Skip to main content
Errors always come back in the same envelope:
{
  "statusCode": 403,
  "code": "NOT_AUTHORIZED",
  "message": "Not authorized for this operation",
  "detail": "session:viewDenied",
  "timestamp": "2026-05-13T08:30:00Z"
}
Branch on 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

import { Luxxon, type LuxxonError } from "@luxxon/sdk";

try {
  const { session, viewer } = await lx.requestLiveView({
    lat: 4.71, lng: -74.05, maxDurationSeconds: 30,
  });
} catch (err) {
  const e = err as LuxxonError;
  switch (e.code) {
    case "NO_COVERAGE":
      // No operator in range. Try a wider zone or wait + retry.
      break;
    case "INSUFFICIENT_CREDIT":
      // Pool balance too low. Top up via the console.
      break;
    case "INVALID_STATE":
      // Session moved on while we were calling.
      break;
    default:
      throw err;
  }
}

Auth + authorization

CodeStatusWhat it meansWhat to do
NOT_AUTHENTICATED401Missing / expired credential.Re-mint an API key or re-do the SIWE login.
NOT_AUTHORIZED403Caller has no permission for the resource.Check workspace membership / API-key scopes.
INSUFFICIENT_SCOPE403API key is missing the scope this endpoint needs.Mint a new key with the right scope set.
NOT_OPERATOR403Operator-only endpoint, caller isn’t one.Use an operator workspace.
NOT_YOUR_SESSION403Caller is neither consumer nor operator of the session.Verify you’re querying the right session id.

Domain rules

CodeStatusWhat it meansWhat to do
INVALID_INPUT400Body / query failed validation. detail says which field.Fix the payload.
INVALID_STATE409Operation not allowed in the session’s current state.Read the current state via get_session and retry the right transition.
CONFLICT409Resource conflict (e.g. monotone sequence violation on a heartbeat).Resolve the conflict server-side (newer sequence, different slug) and retry.
NO_COVERAGE503No ONLINE operator covers the requested point.Widen the request, try a different zone, or wait for an operator to come online.
OUTSIDE_COVERAGE400Requested point is outside the operator’s declared coverage circle.Move the request inside the operator’s range.
UNAVAILABLE503Dispatch found no idle operator.Retry shortly.
FRAME_NOT_AVAILABLE404No 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 ASSIGNEDLIVE transition rather than spinning on get_frame.

Wallet + settlement

CodeStatusWhat it meansWhat to do
NO_CREDIT400Workspace pool balance is zero.Top up via the console.
INSUFFICIENT_CREDIT400Pool balance won’t cover the requested duration at the current rate.Top up or shorten maxDurationSeconds.

Not-found

CodeStatusWhat it means
NOT_FOUND404Generic — the URL resolved but the resource id is unknown.
SESSION_NOT_FOUND404The given sessionId doesn’t exist (or was hard-deleted).
OPERATOR_NOT_FOUND404Unknown operator id.
COVERAGE_AREA_NOT_FOUND404Legacy coverage area lookup.
ACCOUNT_NOT_FOUND404Legacy account lookup.

Infrastructure (5xx)

These mean our side is sad, not yours. Safe to retry with backoff.
CodeStatusWhat it means
DATABASE_ERROR503Postgres unavailable.
AUTHZ_ERROR503SpiceDB unavailable.
VIDEO_ERROR503Cloudflare Stream unavailable.
QUEUE_ERROR503Background job queue unavailable.
EXTERNAL_ERROR502A downstream API didn’t respond.
PAYMENT_ERROR502A downstream payment processor didn’t respond. (Reserved.)
INTERNAL_SERVER_ERROR500Unexpected — 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 you switch on code, fall through to a generic handler for the default case (don’t crash on unknown codes).
default:
  // Unknown code — log + surface to the user as a generic error.
  console.error("luxxon: unknown error", e.code, e.message);
See @luxxon/sdk’s LuxxonErrorCode type for the typed enum — it tracks this page.