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
| Status | Meaning | Retryable |
|---|---|---|
| 200 | Success | — |
| 201 | Created (narrative submit) | — |
| 202 | Accepted (async job enqueued — work is happening in the background) | — |
| 400 | Bad request / validation failure | No |
| 401 | Authentication failed (missing, invalid, or revoked key) | No |
| 403 | Authorization denied (insufficient scope, capability, or risk tier) | No |
| 404 | Resource not found or world policy denied | No |
| 409 | Conflict (resource not ready, idempotency conflict, duplicate content hash) | Sometimes |
| 422 | Validation error (schema violation, missing required field) | No |
| 429 | Rate limited | Yes |
| 500 | Internal server error | Yes |
| 503 | Service unavailable (feature disabled, ledger unconfigured) | Yes |
Error Codes
Authentication
| Code | Status | Description |
|---|---|---|
ERR_AUTH_INVALID | 401 | Bearer token missing, malformed, or not found in the system. Double-check that your Authorization header is formatted as Bearer <your_key>. |
ERR_AUTH_FORBIDDEN | 403 | The 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
| Code | Status | Description |
|---|---|---|
ERR_PERMISSION_INTERSECTION_DENIED | 403 | Your 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_DENIED | 404 | A 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_PERMITTED | 403 | The 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
| Code | Status | Description |
|---|---|---|
ERR_VALIDATION_FAILED | 422 | Request body fails schema validation. The message field will contain details about which fields are invalid or missing. |
ERR_MISSING_ORIGIN_SYSTEM | 422 | The 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_MISMATCH | 422 | The 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.
| Code | Status | Description |
|---|---|---|
ERR_EXTRACTION_NOT_READY | 409 | The 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_READY | 409 | The 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_SET | 409 | The 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_HASH | 422 | The same narrative text (identified by content hash) was already submitted within the deduplication window. This prevents accidental double-submissions. |
ERR_IDEMPOTENCY_CONFLICT | 409 | An 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
| Code | Status | Description |
|---|---|---|
ERR_DIRECTIVE_REJECTED | 422 | The 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):
- Check the
Retry-Afterheader (seconds) if present - If no header, use exponential backoff starting at 1 second
- Cap at 5 retries for 5xx errors
- Never retry 401, 403, or 422 errors
For idempotent operations (execute, execute/async), safe replay is guaranteed via the idempotencyKey.
