Starholder API

Error Handling

Error envelope format, error codes, retry strategy, and status code mapping.

The Starholder API is designed so that errors are structured, predictable, and include enough information to fix the problem without guessing. Every error response tells you what went wrong, which request it applies to, and whether you should retry. This page covers the response format, every error code you might encounter, and how to handle retries.

Response Envelope

Every API response follows the same structure, so you always know what to expect regardless of which endpoint you call. Parse the top-level ok field first — it tells you whether the request succeeded before you look at anything else.

Success

{
  "ok": true,
  "data": { ... },
  "meta": {
    "correlationId": "corr_abc123def456",
    "requestId": "req_001"
  }
}

meta is present when the handler includes request metadata. Some older routes ($STAR wallet, key lifecycle) return raw JSON without the ok wrapper — these are being migrated over time.

Error

{
  "ok": false,
  "error": {
    "code": "ERR_AUTH_INVALID",
    "message": "API key not found or inactive",
    "retryable": false,
    "correlationId": "corr_abc123def456"
  }
}

When retryable is true, the error may include retryAfterMs and the response will carry a Retry-After header (in seconds).

HTTP Status Codes

StatusMeaningRetryable
200Success
201Created (narrative submit)
202Accepted (async job enqueued — work is happening in the background)
400Bad request / validation failureNo
401Authentication failed (missing, invalid, or revoked key)No
403Authorization denied (insufficient scope, capability, or risk tier)No
404Resource not found or world policy deniedNo
409Conflict (resource not ready, idempotency conflict, duplicate content hash)Sometimes
422Validation error (schema violation, missing required field)No
429Rate limitedYes
500Internal server errorYes
503Service unavailable (feature disabled, ledger unconfigured)Yes

Error Codes

Authentication

CodeStatusDescription
ERR_AUTH_INVALID401Bearer token missing, malformed, or not found in the system. Double-check that your Authorization header is formatted as Bearer <your_key>.
ERR_AUTH_FORBIDDEN403The type of caller (actor class) making this request isn't allowed on this route. For example, an API key hitting a session-only endpoint.

Authorization

CodeStatusDescription
ERR_PERMISSION_INTERSECTION_DENIED403Your key lacks the required scope or capability for this operation. "Intersection" refers to the overlap between what your key is allowed to do and what the route requires — if there's no overlap, the request is denied. Check both your key's capability set and its world scopes.
ERR_WORLD_POLICY_DENIED404A world-level policy flag required by this route isn't enabled. This returns 404 (not 403) because when a feature is disabled, the route effectively doesn't exist — revealing its existence would leak information about the world's configuration. Contact the world owner to enable the relevant policy flag.
ERR_MUTATION_NOT_PERMITTED403The specific type of mutation you're attempting isn't allowed for your actor class. This is distinct from scope/capability checks — some mutations are restricted by design regardless of permissions.

Validation

CodeStatusDescription
ERR_VALIDATION_FAILED422Request body fails schema validation. The message field will contain details about which fields are invalid or missing.
ERR_MISSING_ORIGIN_SYSTEM422The x-origin-system header is required on query and execute routes but wasn't provided. Set it to a string that identifies your agent (e.g., "my-trading-bot").
ERR_CONTENT_HASH_MISMATCH422The contentHash you supplied doesn't match the hash computed from the narrative text. This prevents accidental submission of mismatched content. Recompute the hash from the exact text you're submitting.

Resource State

These errors relate to the state of resources as they move through processing pipelines. Several of them are expected during normal operation — they don't mean something is broken, just that the resource isn't ready yet.

CodeStatusDescription
ERR_EXTRACTION_NOT_READY409The extraction pipeline (which analyzes submitted narrative content) is still running. This is normal — after submitting a narrative, extraction takes time. Poll the resource periodically and retry when it completes.
ERR_MANIFEST_NOT_READY409The manifest (a structured breakdown of media assets needed for a narrative) hasn't been generated yet. Like extraction, this is part of the normal submission lifecycle. Poll and retry.
ERR_STRATEGY_ALREADY_SET409The manifest's fulfillment strategy has already been chosen and can't be changed. Strategies are immutable once set to prevent conflicting work.
ERR_DUPLICATE_CONTENT_HASH422The same narrative text (identified by content hash) was already submitted within the deduplication window. This prevents accidental double-submissions.
ERR_IDEMPOTENCY_CONFLICT409An async job with the same idempotency key already exists and is in a conflicting state. Use a new idempotency key if you intend this to be a separate operation.

Directive

CodeStatusDescription
ERR_DIRECTIVE_REJECTED422The directive was rejected during preflight — a validation step where the system checks whether your directive makes sense before actually executing it. For example, directing a persona to do something contradictory or referencing an entity that doesn't exist. The message field will explain why the directive was rejected.

Correlation IDs

Every /api/v1 response includes X-Correlation-Id in the response header. If you supply one in the request, it is echoed back. Otherwise the server generates one in the format corr_<16 hex chars>.

Use correlation IDs when reporting issues or tracing requests through logs.

Retry Strategy

For retryable errors (429, some 409, 5xx):

  1. Check the Retry-After header (seconds) if present
  2. If no header, use exponential backoff starting at 1 second
  3. Cap at 5 retries for 5xx errors
  4. Never retry 401, 403, or 422 errors

For idempotent operations (execute, execute/async), safe replay is guaranteed via the idempotencyKey.