Skip to content

Error format

Typed Z4JError failures and unexpected server failures use the small z4j envelope below. The brain does not emit RFC 7807 Problem Details. FastAPI owns some responses before z4j's middleware can normalize them: request-schema validation, unknown routes, and a directly raised HTTPException retain FastAPI's {"detail": ...} shape.

{
"error": "validation_failed",
"message": "request body failed validation (2 field(s))",
"request_id": "01H...",
"details": {
"errors": [
{"loc": "body.email", "type": "value_error.email"},
{"loc": "body.role", "type": "enum"}
]
}
}
Field Meaning
error Stable short identifier. Branch on this from API clients.
message Human-readable summary. Operator-friendly English; do not parse.
request_id The same value as the X-Request-Id response header. Quote it when filing a bug.
details Per-error context. A Pydantic ValidationError raised inside application code is redacted to {"errors": [{"loc": "...", "type": "..."}, ...]}. FastAPI request validation uses its own detail list and may echo the rejected input; do not assume that path is secret-redacted.

The brain raises typed Z4JError subclasses; the error middleware maps them to HTTP status codes:

Status Exception class Typical error codes
401 AuthenticationError, SignatureError unauthenticated, invalid_signature
403 AuthorizationError forbidden, plus mfa_reverify_required and mfa_enrollment_required for MFA gates
404 NotFoundError not_found
409 ConflictError conflict
422 ValidationError, InvalidFrameError validation_error, invalid_frame; application-raised Pydantic validation uses validation_failed, while FastAPI request validation uses detail
426 ProtocolError protocol_incompatible or protocol_version_mismatch; mapped but not currently emitted by an HTTP route
429 RateLimitExceeded rate_limited
502 AdapterError adapter_error; mapped but normal adapter failures arrive asynchronously as a failed command result
503 AgentOfflineError agent_offline; local-registry command dispatch can emit this, while PostgreSQL coordination can instead leave a command pending until its deadline; schedule trigger reports no online agent as 404
504 CommandTimeoutError command_timeout; mapped but no HTTP endpoint emits it -- the sweeper marks the command timed out for later polling
500 everything else internal_error (with request_id for log correlation; the exception class name is deliberately not surfaced)

Responses that pass through the request-id middleware include an X-Request-Id header, and z4j error envelopes repeat it as request_id. An early body-size rejection is an exception: it can return 413 before that middleware runs, with no header and a null body field. FastAPI detail responses do not add a request_id body field. Pass the header when it is present when filing a bug or scanning logs.

Authentication, authorization, not-found, and validation failures on mutating schedule routes (POST, PUT, PATCH, DELETE) are offered to a bounded best-effort audit queue so IDOR-enumeration attempts can leave a forensic breadcrumb. Reads are not covered, nor are other API families. Queue overflow or a persistence failure can drop the breadcrumb without changing the client's error response. Rows that are written participate in the HMAC chain; structured log lines alone are not tamper-evident.