Skip to content

Error reference

One envelope, one code table, every surface

Every REST error is the same JSON shape, every gRPC error maps onto the same taxonomy, and every SDK turns both into the same typed exception hierarchy. This page is the single table to check when a call fails.

Normative sources

core-engine/src/api/error.rs (the envelope), core-engine/src/api/v1.rs and core-engine/src/api/auth.rs (where each code is raised), core-engine/src/grpc/mod.rs (gRPC status mapping), and .ai/specs/integration/2026-07-02-grpc-contracts.md §6 (the normative gRPC mapping).

The envelope

Every non-2xx REST response is:

ErrorBody
{
  "code": "QUERY_INVALID",
  "message": "human-readable description",
  "request_id": "0197c1a2-...-..."
}
  • code is a stable, machine-checkable string (&'static str in the server).
  • message is human-readable; for validation failures it carries a JSON pointer to the offending field (e.g. /where/severity: expected an operator object like {"$eq": ...}).
  • request_id correlates with server logs; it is echoed from the request-id middleware and is the same UUID as the x-request-id response header. It is omitted only in the rare case no request id was available.

Every response, error or not, carries the x-request-id header, minted fresh (UUIDv7) by the outermost middleware layer for every request.

Error codes

Code HTTP status Typical cause
UNAUTHORIZED 401 Missing or invalid x-api-key (or bearer token where accepted)
NOT_FOUND 404 No record, edge, job, trace, clarification, or person with that id/scope
INTERNAL 500 Unhandled engine failure; details are logged server-side, never leaked in the message
QUERY_INVALID 400 Query-DSL validation failure (/v1/query, /v1/snapshot); message carries a JSON pointer
BAD_CURSOR 400 Cursor invalid, tampered, foreign-tenant, or plan-mismatched
COMPARE_INVALID 400 /v1/compare request validation failure; message carries a JSON pointer
INVALID_ID 400 A client-supplied logical id was nil
INVALID_PROPERTY 400 A property value failed conversion (e.g. malformed {"$bytes": ...} base64)
INVALID_INTERVAL 400 A validTime interval was malformed (e.g. end <= start)
EMPTY_BATCH 400 POST /v1/records called with an empty records array
VALIDATION 400 Miscellaneous request-shape failures (e.g. upsert without id; unrecognized clarification state/severity; missing connector for an API-key externalId lookup)
INVALID_CONTENT 400 /v1/ingest: neither/both of content/contentBase64, or malformed content
PLAN_INVALID 400 /v1/generate: empty question, or temperature outside [0, 2]
MODEL_NOT_ALLOWED 400 /v1/generate model is not in the operator allowlist
UNKNOWN_MODEL 400 Named embedding model is not registered
VECTOR_INVALID 400 Evidence-planner vector clause failed validation
ALREADY_EXISTS 409 A client-supplied id already has a version
TOMBSTONED 409 The target record/edge/entity is already tombstoned
TYPE_COLLISION 409 An edge type collides with an existing edge between the same nodes
INGEST_ERASED_CONTENT 409 Content digest was retired by GDPR erasure; re-ingestion is permanently refused
CONNECTOR_NAMESPACE_MISMATCH 403 A connector token acted outside the namespace it synced
INSUFFICIENT_EVIDENCE 422 /v1/generate recall produced no evidence spans; pass allowUngrounded to proceed anyway
TENANT_BUDGET_EXCEEDED 429 The tenant's token cap (daily/monthly) is exhausted
UNIMPLEMENTED 501 Feature not configured (e.g. vector.text without an embedding endpoint, or /v1/generate with no [llm] configured)
PROVIDER_ERROR 502 The LLM or embedding provider returned a permanent failure
EMBEDDING_FAILED 502 The embedding provider failed or returned no vector
PROVIDER_UNAVAILABLE 503 Transient provider failure, retries exhausted
CLARIFY_UNPARSEABLE_ANSWER 422 A free-form clarification answer did not normalize
CLARIFY_ALREADY_ANSWERED 409 Clarification is already bound and policy disallows further responses
CLARIFY_INVALID_STATE 409 Answer attempted against a clarification in an incompatible state
RESPONDER_ASSERTION_FORBIDDEN 403 A non-clarify-audience caller tried to assert a third-party responder or rights
ALIAS_ATTACH_FORBIDDEN 403 Caller is not the clarify audience (only path allowed to attach verified aliases)
ALIAS_AMBIGUOUS 409 The verified email matches zero or more than one Entra-verified person
ALIAS_CONFLICT 409 The person already carries a different value for that alias kind
ALIAS_EVIDENCE_INVALID 422 Alias evidence failed re-verification (email mismatch, no aad_oid, alias not verified)

Where each surface raises them

Endpoint group Codes you'll see
/v1/query, /v1/snapshot QUERY_INVALID, BAD_CURSOR, UNAUTHORIZED, UNIMPLEMENTED
/v1/compare COMPARE_INVALID, BAD_CURSOR, UNAUTHORIZED
/v1/records (POST/PUT/GET/DELETE), /v1/relationships EMPTY_BATCH, INVALID_PROPERTY, INVALID_INTERVAL, INVALID_ID, VALIDATION, UNAUTHORIZED, NOT_FOUND, ALREADY_EXISTS, TOMBSTONED, TYPE_COLLISION, CONNECTOR_NAMESPACE_MISMATCH
/v1/ingest INVALID_CONTENT, UNAUTHORIZED, INGEST_ERASED_CONTENT
/v1/generate PLAN_INVALID, MODEL_NOT_ALLOWED, QUERY_INVALID, UNKNOWN_MODEL, VECTOR_INVALID, INSUFFICIENT_EVIDENCE, TENANT_BUDGET_EXCEEDED, UNIMPLEMENTED, PROVIDER_ERROR, EMBEDDING_FAILED, PROVIDER_UNAVAILABLE
/v1/trace/{id} UNAUTHORIZED, NOT_FOUND
/v1/clarifications* VALIDATION, UNAUTHORIZED, NOT_FOUND, RESPONDER_ASSERTION_FORBIDDEN, CLARIFY_ALREADY_ANSWERED, CLARIFY_INVALID_STATE, CLARIFY_UNPARSEABLE_ANSWER
/v1/person-aliases/verified-match VALIDATION, UNAUTHORIZED, ALIAS_ATTACH_FORBIDDEN, NOT_FOUND, ALIAS_AMBIGUOUS, ALIAS_CONFLICT, ALIAS_EVIDENCE_INVALID
Any handler's catch-all INTERNAL (details logged server-side only)

Full per-endpoint status/code tables are in REST API.

Validation errors carry a JSON pointer

QUERY_INVALID and COMPARE_INVALID messages are not free text, they name the exact field:

A rejected query
{
  "code": "QUERY_INVALID",
  "message": "/where/severity: expected an operator object like {\"$eq\": ...}",
  "request_id": "..."
}

This is the same pointer format the query language fixture corpus pins, and the same format MCP tools echo back for their findRecords/generate/compareSnapshots validation failures (as a tool-level {"error": "query_invalid", "message": "..."}, not a transport-level error, see MCP tools).

gRPC status mapping

gRPC has no envelope; it maps the same taxonomy onto standard Status codes (grpc-contracts spec §6):

Condition gRPC status
QUERY_INVALID / COMPARE_INVALID (validation failure) INVALID_ARGUMENT
Tenant/token failures (missing, malformed, expired, wrong audience) UNAUTHENTICATED
Scope violations (e.g. cross-tenant job access) PERMISSION_DENIED
Budget-partial results OK, with a partial flag on the response body, never an error
Lease conflicts (stale seq, superseded lease) ABORTED
Record not found NOT_FOUND
Record already exists ALREADY_EXISTS
Update against a tombstoned record FAILED_PRECONDITION
Feature not configured UNIMPLEMENTED
Engine internal failure INTERNAL (request id in server logs)
Malformed worker_id/id fields, bad UTF-8 in query_json INVALID_ARGUMENT

See gRPC API › Error mapping for how this plays out per-RPC.

How the SDKs surface errors

Both SDKs parse the envelope into a typed exception hierarchy rooted at a common base carrying {code, status, requestId} (or request_id in Python):

Class Trigger
QueryInvalidError 400 QUERY_INVALID (message carries the JSON pointer)
ValidationError other 4xx (BAD_CURSOR, INVALID_INTERVAL, EMPTY_BATCH, COMPARE_INVALID, ...)
AuthError 401 UNAUTHORIZED
NotFoundError 404
ConflictError 409 (TOMBSTONED, TYPE_COLLISION, ALREADY_EXISTS, INGEST_ERASED_CONTENT, ...)
UnimplementedError 501
ServerError 5xx INTERNAL and transport failures (status 0, code TRANSPORT)

The TypeScript SDK additionally maps gRPC status codes onto this same hierarchy via fromGrpcError: UNAUTHENTICATEDAuthError, INVALID_ARGUMENTQueryInvalidError/ValidationError, NOT_FOUND/ALREADY_EXISTS/ABORTEDNotFoundError/ConflictError, UNIMPLEMENTEDUnimplementedError, DEADLINE_EXCEEDED/UNAVAILABLEServerError.

See TypeScript SDK › Errors and Python SDK › Errors for the exact class shapes and retry semantics (only idempotent calls retry, and only on 5xx/transport failures, never on any 4xx, including all of the codes above).