Loading documentation

Access required

Your account doesn't have access to the documentation. Contact your administrator.

Sign out
Skip to main content

Error responses

Status: stable Category: reference Added: 2026-04-20 Last reviewed: 2026-04-20

Summary

Every non-2xx response uses a uniform envelope:

{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "ValidationError",
"message": "Field 'depth' must be one of basic, contextual, full",
"request_id": "8f2c3b1d-5e4a-4a9c-9b1e-2a7f1d3e4c5b",
"field": "depth"
}
  • error is a stable machine-readable code. Branch your client on this.
  • message is human-readable. Display it; do not parse it.
  • request_id is the correlation ID. Quote it in support tickets.
  • Extra keys (field, retry_after, etc.) appear on specific error codes and are documented below.

401 responses are the one exception — they use a smaller envelope shape. See authentication.md.

Envelope fields

FieldTypeAlways presentDescription
timestampstring (ISO 8601 UTC)YesServer time when the response was emitted. Millisecond precision.
errorstringYesStable machine-readable error code. See the taxonomy below.
messagestringYesHuman-readable description. May change wording between releases; do not parse it.
request_idstring (UUID or client-supplied)Almost alwaysCorrelation ID. Present for every /api/* response. Absent only for errors raised before per-request logging begins — in practice, the unauthenticated /health probe is the only such path.
extrasvariesPer error codeAdditional fields documented per error below.

Error taxonomy

The table lists every value error can take, the HTTP status that accompanies it, and the extras that may appear.

errorStatusExtrasMeaning
ValidationError400field (when known)Request was syntactically malformed or violated a field-level rule.
DeIdentificationViolation400field (always)Request body contained an identifier-shaped value or blocked field name.
Unauthenticated401noneMissing or invalid apikey. Uses the 401 envelope shape described in authentication.md.
PaymentRequired402noneOut of credits, or a per-key / account budget is exhausted. See credits.md.
NotFound404nonePath does not exist or method is wrong.
PayloadTooLarge413noneRequest body exceeded the size limit (default 512KB).
UnsupportedMediaType415noneContent-Type is not application/json.
UnprocessableEntity422variesBody parsed but a business rule rejected it.
RateLimited429retry_after (seconds)Rate limit exceeded. Retry-After response header is also set.
InternalError500noneUnexpected server-side failure. Quote request_id in a support ticket.
UpstreamError502noneAn upstream dependency returned an unusable response.
ServiceUnavailable503noneA required dependency is temporarily unhealthy. Retry with backoff.
UpstreamTimeout504noneAn upstream dependency exceeded its timeout.

Per-error detail

ValidationError — 400

Fires when the request body or parameters fail a shape or value check: malformed JSON, unknown fields (on endpoints that reject unknowns), out-of-range values, missing required fields. Examples:

{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "ValidationError",
"message": "Field 'depth' must be one of basic, contextual, full",
"request_id": "...",
"field": "depth"
}
{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "ValidationError",
"message": "Request body is not valid JSON",
"request_id": "..."
}

When the offending field can be identified, field names it using a dotted path (demographics.age_bracket) or a bracketed index (responses.item_3). field is absent when the error applies to the request as a whole (e.g. malformed JSON at the root).

DeIdentificationViolation — 400

Fires before any business logic. The request body is walked and rejected on the first hit of:

  • a blocked field name: name, first_name, last_name, full_name, ssn, dob, date_of_birth, email, phone, mrn, patient_id, member_id.
  • a value matching an identifier pattern: SSN shape (\d{3}-\d{2}-\d{4}), email shape, phone shape, or a past-dated ISO date (day-resolution date of birth).

Example:

{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "DeIdentificationViolation",
"message": "demographics.email appears to contain an email address",
"request_id": "...",
"field": "demographics.email"
}

field is always present and names the offending path. The offending value is never echoed. It is not logged and not returned — surfacing the value anywhere would defeat the purpose of the check.

Strip the identifier client-side and retry. For age, send demographics.age_bracket (for example "35-44") instead of a date of birth. See conventions.md for the full de-identification contract.

Unauthenticated — 401

The apikey header is missing, empty, or not a known key. 401 bodies use a smaller envelope than the standard error shape — see authentication.md.

PaymentRequired — 402

The call could not be funded. Fires before the endpoint does any work, so an unfunded call never partially executes:

{
"timestamp": "2026-06-22T19:04:20.118Z",
"error": "PaymentRequired",
"message": "Insufficient credits",
"request_id": "..."
}

The message distinguishes a depleted balance (Insufficient credits) from an exhausted per-key allocation (Key credit allocation exhausted) or account-wide cap (Account credit allocation exhausted). A 402 is not retriable — it clears only when you add funds or raise a budget in the portal, not by retrying. See credits.md for the credit model, the X-Credits-* headers, and budgets.

NotFound — 404

The path and method combination has no handler. This covers unknown paths (/api/does-not-exist) and wrong methods on known paths (GET /api/cdri/compute when only POST is registered).

{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "NotFound",
"message": "Route not found",
"request_id": "..."
}

PayloadTooLarge — 413

The request body exceeds the server-side limit. The default is 512KB. No endpoint requires a body close to this limit; hitting 413 almost always indicates an accidental upload of raw data that should have been summarized client-side.

UnsupportedMediaType — 415

The Content-Type header is not application/json. Always set Content-Type: application/json on any request with a body.

UnprocessableEntity — 422

The body parsed as valid JSON but a higher-level rule rejected it. Used for semantically-invalid input that passed the first-pass shape check. A concrete example:

  • POST /api/cdri/compute body that references an instrument name not registered in the catalog. The body was valid JSON and the top-level shape was correct, but the named instrument does not exist, so the request cannot be processed as written.

RateLimited — 429

Too many requests for your consumer within the active window. The envelope carries a retry_after extra (seconds to wait) and the response sets a Retry-After header:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{
"timestamp": "2026-04-20T17:32:10.812Z",
"error": "RateLimited",
"message": "Too many requests",
"request_id": "...",
"retry_after": 12
}

See rate-limits.md for the response headers and backoff guidance.

InternalError — 500

An unexpected server-side failure. The client did nothing wrong. The response intentionally carries a generic message ("An unexpected error occurred") — details are logged server-side, keyed by request_id, and never leaked to the envelope.

What to do:

  1. Retry once with a short delay (1–2 seconds).
  2. If it persists, open a support ticket quoting the request_id.

UpstreamError — 502

An upstream dependency returned an unusable response. Your request could not be completed because one of its downstream services is misbehaving. Retry with backoff.

ServiceUnavailable — 503

A required dependency is temporarily unhealthy. This is also the shape GET /api/health/check uses when a probe fails. For non-health endpoints, a 503 means the request cannot be serviced right now — retry with backoff. If it persists for more than a few minutes, check the status page.

UpstreamTimeout — 504

An upstream dependency exceeded its timeout. Distinct from UpstreamError in that the upstream may still be processing; the request was abandoned. Retry with backoff; if the same request reliably times out, contact support with a minimal reproducer.

Debugging flow

  1. Capture request_id. Present on every error response. Quote it in tickets.
  2. Branch on error, not message. Messages are human-readable and may be reworded in a release.
  3. Inspect the extras. field on ValidationError and DeIdentificationViolation pinpoints the path. retry_after on RateLimited is the wait in seconds.
  4. Retry on the retriable statuses. 429, 500, 502, 503, 504 are retriable with exponential backoff. 400, 401, 402, 404, 413, 415, 422 are not — retrying without changing the request (or, for 402, your funding) is a waste.
  5. Check the request again at the boundary. DeIdentificationViolation is always a client-side fix — a field name or value in the body needs to be stripped or restructured.

Change history

DateChange
2026-04-20Initial publication.
2026-06-22Added 402 PaymentRequired (credit metering).