Skip to main content

Errors, Response Envelopes & Status Codes

Every surface of the Alien Intelligence platform returns errors in a predictable shape. This page collects the conventions in one place: the REST envelope returned by the Platform API, the HTTP status codes and what each means on this platform, how streaming endpoints surface failures, the GraphQL error shape, and a short guide to handling errors robustly.

The REST Envelope

The Platform API wraps every JSON response — success or failure — in a consistent envelope. Clients can branch on the boolean success field before reading any payload.

Success

A successful response carries success: true and the result under data:

Success
{
"success": true,
"data": { ... }
}

List endpoints add a meta block with pagination:

Success with pagination
{
"success": true,
"meta": {
"currentPage": 1,
"total": 42,
"perPage": 100,
"lastPage": 1
},
"data": [ ... ]
}

Error

A failed response carries success: false, a human-readable top-level message, and an error object that always contains the numeric HTTP status:

Error
{
"success": false,
"message": "Resource not found",
"error": {
"status": 404
}
}

The published ErrorResponse schema documents exactly three fields — success (always false), message (a string), and error.status (an integer). At runtime the platform may attach additional diagnostic fields to the error object (such as a short error name and a data payload), but only success, message, and error.status are part of the stable contract — treat anything else as best-effort detail.

tip

Branch on success, not on the HTTP status alone. The envelope is the source of truth; error.status mirrors the HTTP status code so you can read it even where the transport status is awkward to access.

Validation errors

When a request fails schema validation, the same envelope is returned with status: 422 and a per-field breakdown under error.messages:

Validation error
{
"success": false,
"message": "Validation failure",
"error": {
"status": 422,
"messages": [
{ "field": "name", "rule": "required", "message": "The name field is required" }
]
}
}

Each entry names the offending field, the rule it violated, and a readable message.

The Data API uses a different error shape

The Data API that runs on each data cluster is a FastAPI service, so it does not use the Platform envelope. Its errors are a flat object with a single detail field:

Data API error
{
"detail": "Entry with id 999 not found"
}

When you reach the Data API through the Platform's cluster proxy, the proxy forwards the Data API's status code and body transparently — so a proxied call can surface either envelope depending on which layer rejected the request.

The OpenAI-compatible surfaces are not enveloped

The OpenAI-compatible endpoints — the Responses API (POST /agent/{id}/responses), the Chat Completions API (POST /agent/{id}/chat/completions), and the Files API (/v1/files) — return bare, OpenAI-shaped objects, not the platform { success, data } envelope. A successful Files call returns a FileObject / FileList directly; a successful streaming call returns the OpenAI SSE event stream. Do not look for a success field on these surfaces.

Errors are surface-specific, so consumers must branch by surface, not assume the envelope:

  • Streaming surfaces (Responses, Chat Completions) report errors in the OpenAI { "error": { … } } shape — as a pre-stream HTTP 4xx/5xx body before the first event, or as a terminal in-stream failure event after it (see Streaming Errors).
  • Files API returns its success bodies OpenAI-shaped, but its error responses (400/401/404/500) fall back to the platform { success: false, message, error.status } envelope documented above — so a Files consumer reads OpenAI shapes on the happy path and the platform ErrorResponse on failure.

HTTP Status Codes

The platform uses standard HTTP semantics. The table below describes what each status means specifically on this platform.

StatusMeaningWhen you'll see it
400 Bad RequestMalformed or invalid requestA missing/invalid parameter, a malformed header (e.g. an invalid MCP config or composite dataset ID), or a payload the endpoint can't parse
401 UnauthorizedMissing or invalid credentialsNo Bearer token, an expired/invalid OAuth JWT, or a revoked/unknown API token. The dual-guard tries OAuth then API token; if both fail you get 401
403 ForbiddenAuthenticated, but not allowedYour organization role or a resource policy denies the action; you lack the required token ability; or the target cluster is suspended. You are known — you just can't do this
404 Not FoundResource does not existThe dataset, entry, workflow, job, or other resource ID/slug was not found (or is not visible to you)
409 ConflictState conflictA uniqueness or duplicate-resource conflict — e.g. creating an entry with a slug that already exists, or a tool definition that already exists
422 Unprocessable EntityValidation failureThe request was well-formed but failed schema validation. The error.messages array lists the offending fields (see above)
429 Too Many RequestsRate limitedSurfaced when a proxied upstream (e.g. a metered external API) rate-limits the call. The platform relays the upstream 429 and any Retry-After header. The Platform API itself does not currently throttle
500 Internal Server ErrorUnexpected server faultAn unhandled error on the platform side. Safe to retry with backoff; if it persists, it's a platform-side issue
503 Service UnavailableBackend temporarily unreachableThe target data cluster is offline (its last heartbeat is stale, or it is being deleted). Reads and writes through the proxy are rejected until it recovers

Where these are enforced

  • 401 / 403 are enforced by the authentication guards and the authorization policy system. The authorization check runs before any database query or external call — so a 403 means the request never touched your data.
  • 422 is produced centrally: VineJS validation failures are caught by the global exception handler and serialized into the envelope above.
  • 403 vs 503 for clusters is a deliberate distinction. A suspended cluster returns 403 (an authorization decision — the cluster is disabled for policy reasons). An offline cluster returns 503 (an availability problem — the cluster's heartbeat went stale and it may recover). See the cluster proxy authorization for how proxy requests are gated.

Streaming Errors

The streaming endpoints (the Responses API and the Chat Completions API) surface failures differently depending on when the failure happens relative to the first SSE event.

Before the stream starts

If the request is rejected before the first event is sent, you get an ordinary HTTP 4xx/5xx response whose body matches OpenAI's standard error envelope:

Pre-stream error
{ "error": { "message": "...", "type": "invalid_request_error", "code": "..." } }

After the stream starts

Once events are flowing, the connection has already committed to 200 OK, so a failure cannot change the HTTP status. Instead it arrives as a terminal response.failed event carrying Response.error with a code and message:

Mid-stream failure (Responses API)
{
"type": "response.failed",
"sequence_number": 42,
"response": {
"id": "resp_...",
"status": "failed",
"error": { "code": "server_error", "message": "..." },
"metadata": {
"x_alien_error_code": "upstream_timeout",
"x_alien_error_message": "upstream model timed out after 30s"
}
}
}

Response.error.code is constrained to OpenAI's documented literal codes — server_error, rate_limit_exceeded, invalid_prompt, and so on. Alien-specific failure detail is carried alongside it in metadata.x_alien_error_code / metadata.x_alien_error_message, so a consumer that wants the precise cause should read those.

A notable Alien-specific code is worker_disconnected — the agent worker dropped its connection mid-run (rather than the model itself failing). Clients should treat a terminal failure as retryable: re-issue the turn, optionally reusing the conversation handle, rather than leaving the user with a silently empty response.

note

SSE heartbeat comment lines (:keep-alive) are emitted during quiet periods and do not parse as events or advance the sequence_number. Don't mistake a quiet stream for a failed one.

See the Responses API errors section for the full event taxonomy and the native sequence_number-based resume that lets a UI recover an in-flight run after a network blip.

GraphQL Errors

The platform exposes a GraphQL surface at POST /graphql for metrics and reporting data (see Querying metrics with GraphQL). GraphQL errors follow the GraphQL specification rather than the REST envelope. The HTTP response is typically 200 OK, and failures are reported in a top-level errors array alongside any partial data:

GraphQL error
{
"data": null,
"errors": [
{
"message": "Resource not found",
"path": ["dataset"],
"extensions": { "code": "NOT_FOUND" }
}
]
}

Always check for a non-empty errors array even when data is present — GraphQL can return partial data with per-field errors. Provider-specific codes are conventionally placed under extensions.code.

Handling Errors

A few rules of thumb cover almost every case:

  • Branch on the envelope, not the transport. Read success (REST) or the errors array (GraphQL) before consuming a payload. For streams, watch for the terminal response.failed event even after a 200 OK.
  • Retry 5xx and 503, with backoff. 500 and 503 are transient — a 503 in particular means a data cluster is briefly offline and may recover on its own. Use exponential backoff with jitter, and cap the number of attempts.
  • Honor 429 and Retry-After. When a proxied upstream rate-limits you, wait for the interval the Retry-After header specifies before retrying.
  • Do not retry 4xx (except 429). 400, 401, 403, 404, 409, and 422 indicate something about the request that won't change on its own — fix the input, the credentials, or the permissions instead. Re-validate against error.messages for a 422.
  • Surface streaming failures to the user. A mid-stream response.failed (e.g. worker_disconnected) should produce a visible, retryable error rather than an empty result. Re-issue the turn to recover.
  • Log error.status and message. They are stable across the API; the human-readable message is for operators, not for parsing — branch on the status code, not on message text.

Next Steps