Docs

Errors

Every error code the API returns, its HTTP status, whether it is worth retrying, and the details each one carries

Every error the platform returns follows the same envelope and the same handful of HTTP status codes. This page is the canonical reference: the envelope shape, the status-to-code mapping, and a recovery matrix you can implement against.

For rate-limit specifics see Rate limits. For pagination-specific error cases see Pagination and errors.

Canonical error envelope

All errors use this exact shape:

json
{
  "error": {
    "code": "invalid_input",
    "message": "Field 'task' is required.",
    "details": {
      "field": "task",
      "expected": "string"
    }
  }
}

Three fields:

  • code: stable machine-readable identifier. Safe to branch on.
  • message: human-readable description. Safe to log. Do not pattern-match against it programmatically; copy may change.
  • details: optional JSON object with structured context. Shape depends on the code. Present when the server has something useful to attach (offending field, rate limit window, violated constraint).

The envelope is consistent across REST and MCP surfaces. MCP tool error responses wrap the same object as the result of the tool call so you can branch on code identically.

HTTP status code mapping

HTTP statusCommon codesWhen it fires
400invalid_input, invalid_argument, validation_errorThe request body, query string, or tool input failed validation. Fix the payload and retry.
401unauthenticated, invalid_token, expired_tokenNo credentials were presented, or the bearer token could not be parsed. Re-auth.
403forbidden, scope_denied, insufficient_roleThe caller is authenticated but lacks the required scope, role, or tenant membership. Grant the scope or upgrade the role.
404not_foundThe resource does not exist, or it exists but belongs to another tenant. See "Security-sensitive errors" below.
409conflict, duplicate, version_conflictThe write would violate a uniqueness constraint or clobber a newer version of the resource. Read, merge, retry.
429rate_limitedRate limit exceeded. Back off and retry; see Rate limits.
500internal, server_errorUnexpected server-side failure. Retry with backoff; file an issue if persistent.
503service_unavailable, dependency_unavailableAn upstream dependency (database, Anthropic API, worker queue) is temporarily down. Retry with backoff.

Status codes are the primary signal for your client-side dispatcher; code is the secondary signal for precise handling.

Public-surface gate codes

Three codes gate the public API surface itself. They fire before any operation logic runs, so every endpoint can return them:

CodeStatusMeaning
not_on_public_api403The operation exists but is not part of the public surface for an external credential (API key or external OAuth token). Only the operations in the tool catalog are externally reachable; everything else serves the console only.
quota_exceeded429An operator-set monthly cap on a verb family (search / evals) is exhausted for the workspace. details carries the family, the limit, and usage; the cap resets at the start of the next month.

not_on_public_api is terminal for the credential — do not retry; quota_exceeded is terminal for the month unless the cap is raised.

Per-tool error codes

Some operations define richer codes on top of the standard set. They still use the canonical envelope; only the code value is more specific.

agents.resume (POST /agents/:session_id/resume)

CodeStatusMeaning
invalid_input400Resume payload did not match pending_input_schema. details.validation_error has the mismatch.
invalid_state409Run is not in awaiting_input. details.current_status shows where it actually is.
not_found404Run id unknown, or belongs to another tenant.

The agent library (/agents CRUD)

CodeStatusMeaning
slug_conflict409An explicit slug on create collides with an existing agent (or shadows an Amdahl library agent). Pick another slug.
locked409The target is a code-defined Amdahl library agent; update / delete are refused.

The synchronous verbs (search.query, search.fields)

Past parameter validation the verbs do not use error envelopes for degradations — every partial outcome is a typed field on a success: true result (available: false, freshness.source, mode_ran). The only error codes they return are invalid_argument (a named bad field, operator, or identifier — e.g. an unknown field name on search.query) and the gate codes above.

Check each operation's entry in the tool catalog and the API reference for the authoritative list of codes it emits.

Read 404s name the shapes that ARE valid

When a URI-addressed read returns nothing, the error tells you what the scheme actually serves instead of only what was missing:

json
{
  "error": {
    "code": "not_found",
    "message": "Not found: eval_run://missing-run. Valid eval_run:// shapes: eval_run://<id>, eval_run://list, eval_run://<id>/report, eval_run://<id>/compare/<other_id>.",
    "details": {
      "valid_uri_shapes": [
        "eval_run://<id>",
        "eval_run://list",
        "eval_run://<id>/report",
        "eval_run://<id>/compare/<other_id>"
      ]
    }
  }
}

You get the same body whether the row is missing or the id was never well-formed (GET /eval-runs/not-a-uuid). The two are deliberately indistinguishable, so a 404 cannot be used to probe whether an id is valid — see Security-sensitive errors.

The same list rides on the MCP / read_resource not-found body, and on the refusal you get for a URI whose scheme exists but whose sub-path nobody serves (eval_run://<id>/cases). That refusal is invalid_uri rather than not_found, because the URI shape is wrong rather than the row missing. invalid_uri reaches you on the URI-addressed surfaces only — read_resource and MCP resources/read. Over REST there is no route to mis-shape: an unserved path is an ordinary unmatched route, not this envelope.

Shapes are listed one per URI shape, without query parameters — eval_run://list appears once, not once per filter combination. Query vocabulary is not part of the shape and lives on operation://list.

details.valid_uri_shapes is additive and scope-filtered: it lists only the shapes your credential may actually read, and is omitted entirely when there are none. Treat it as a hint for correcting a request, not as a catalog — operation://list remains the authoritative one.

Security-sensitive errors

Two behaviors are worth calling out because they can look like bugs:

  1. 404 instead of 403 for cross-tenant access. If you request an artifact, session, or webhook that exists but belongs to another business, the platform returns 404 not_found rather than 403 forbidden. This prevents probing, so a caller cannot distinguish "does not exist" from "exists but not yours". If you are confident the id is yours and you get a 404, check that your API key is bound to the correct business, not that the resource was deleted.
  2. Masked secrets in error details. When a validation error touches a field that the platform classifies as sensitive (tokens, API keys, refresh credentials), details will show [MASKED] in place of the offending value along with a <field>_masked: true sentinel. The error message is still useful; the value is just never echoed back.

Recovery matrix

The client behavior that is right for each code class:

Code classRetry?Action
invalid_input, invalid_argument, validation_errorNoFix the payload. The details field names the offending field. Do not retry the same request.
unauthenticated, invalid_token, expired_tokenYes, after re-authRun the auth flow to obtain a fresh credential, then retry. See Authentication.
forbidden, scope_denied, insufficient_roleNoThe token is valid but lacks authority. Do not retry; surface to a human to grant the scope or role.
not_foundNo (usually)Validate the id. For cross-tenant 404s, verify the API key is bound to the correct business.
conflict, duplicate, version_conflictYes, after mergeRead the current state, reconcile, resubmit with the updated version number or dedupe key.
rate_limitedYes, with backoffHonor Retry-After or details.retry_after_seconds. Exponential backoff with jitter, capped at 60s. See Rate limits.
internal, server_errorYes, with backoffRetry up to 3 times with backoff. If the correlation id repeats, file an issue.
service_unavailable, dependency_unavailableYes, with backoffSame as internal. These indicate transient upstream problems.

Request id

Every response carries an X-Request-Id header. When you surface errors to a user or log them for ops, capture it. On our side it lands in platform_audit_log.correlation_id, which joins the request to the audit log and any upstream traces. Support requests that include the request id resolve significantly faster.

You can also send X-Request-Id on the way in, and the platform will use your value rather than minting one — which lets you correlate on an id your own system already assigned. The header is echoed back on the response either way, so there is always a value to log, and it is exposed via CORS so browser clients can read it too. Values are treated as opaque correlation tokens: they are truncated to 128 characters and stripped of anything that cannot legally sit in a header, so send something you generated rather than anything user-supplied.

Note: earlier revisions of this page named an X-Correlation-Id response header. No such header was ever set. Read X-Request-Id.

See also