Skip to content

Errors

Direct HTTP integrations receive standard status codes plus SkyState error bodies where applicable. The JavaScript SDKs translate these into SkyStateError values, subsystem status snapshots, and React onError callbacks depending on where the error occurs.

Common situations that result in errors:

  • Route target not found - the project or account route identifier is not recognized; an invalid environment value (not one of development, staging, production) returns 400 instead
  • No state yet - public-state and user-state reads can return 204; SDK keyed reads continue to use their fallback value
  • Quota exceeded - the account's monthly API-request quota or another tier limit has been reached; see Rate Limiting
  • Authentication failure - the API key is invalid or has been revoked; see Dev API Keys
  • Version conflict - a write precondition failed and returned 412: an update sent a stale or missing If-Match version, an If-None-Match: * create found that state already exists, or the write used If-Match: "0", which always fails
  • Quota failure - a write would exceed a tier limit, such as the Stored Users limit for user state
  • Service unavailable - the API could not reach its database and returned 503; the response carries Retry-After: 5 and the request can be retried unchanged; see Retry-After

SDK error codes

The SDK translates failures into typed SkyStateError values with a code: SkyStateErrorCode field. The full code list and HTTP-status-to-code mapping are below; the canonical mapping (Section 8.5) lives in the repository specification document (docs/3_specification.md).

  • authentication - token refresh rejected, a 401/403 on a signed-in request, or a write attempted without sign-in
  • configuration - unknown account, project, or environment slug (404); retried, so a fixed slug heals without a reload
  • quota - monthly request quota or tier limit exceeded (402); the SDK keeps queued writes and retries at a slow pace. A monthly-quota 402 sets Retry-After to the seconds until the monthly counter resets (readable cross-origin) and includes a resetAt ISO timestamp in the body
  • rate_limited - per-minute rate limit exceeded (429)
  • server_unavailable - 5xx response; retried on the SDK's own backoff. A 503 means the API could not reach its database and carries Retry-After: 5, readable cross-origin; the SDK does not read the header on 5xx - see Retry-After
  • protocol - a response the SDK could not interpret: an unexpected status or a malformed body
  • no_response - no HTTP response at all (offline, DNS failure, timeout, CORS); retried
  • missing_config, missing_provider, disposed - programmer misuse; thrown synchronously at the call site
  • invalid_path - an invalid state key or non-JSON write value. Detected at the call site it throws synchronously; a deferred updater failure caught at drain time is instead reported via onError and pinned as a permanent health error
  • patch_invalid, patch_unsupported_operation, patch_invalid_path, patch_missing_value, patch_path_untraversable, patch_invalid_array_index, patch_invalid_state_root, patch_state_too_large - the server rejected that specific write (400); the SDK drops it and continues the queue

There is no conflict code: a 412 version race is reconciled inside the SDK (refetch and replay) and never surfaces as an error.

Alongside code and message, variants carry extra fields:

  • httpStatus - on errors that arrived as an HTTP response; absent on no_response and the misuse codes. On authentication it is number | null, because auth can fail before any HTTP response is received
  • path - the user-state key a keyed write was for; always set on write rejections and invalid_path, string | null on other request errors, absent on the sync-thrown misuse codes
  • retryAfter - on rate_limited only: the server's Retry-After delay in seconds, or null when the header was absent or unreadable

Retrying applies to the background loops: public-state loads, user-state reads and writes, and token refresh. The one-shot code exchange during sign-in is not retried, so a failure there surfaces once and halts the callback.

Retries back off with jitter: the first retry comes roughly 1-5 seconds after the failure, rising to a steady state of roughly 2.5-5 minutes between attempts. quota errors are paced slower: a jittered 2.5-5 minutes at first, doubling up to a 30-minute ceiling. A rate_limited response honors the server's Retry-After, capped at 30 minutes.

For SDK-specific error handling, see the SDK docs.