Errors
Every failure — domain, framework, routing — carries the same envelope. A client that has to parse two error shapes will get one of them wrong.
{ "error": { "code": "budget_exceeded", "message": "this workspace has reached its spend limit for the current period", "details": { "workspace_id": "0190a2f4-…", "spend": "512.40", "limit": "500.00", "currency": "USD", "period": "month", "period_start": "2026-08-01T00:00:00+00:00", "period_end": "2026-09-01T00:00:00+00:00" }, "field": "input.model_ref", "correlation_id": "0190a2f4-…", "retryable": false }}Branch on code, never on message. The code is a stable slug from the
closed catalogue; the message is one human-readable sentence and is free
to change.
| Field | |
|---|---|
code | Stable. This is the one you switch on. |
message | One sentence, for a human. Not stable. |
details | Present when there is machine-usable data. |
field | Present on validation failures; a dotted JSON path. |
correlation_id | Quote this in a support request. |
retryable | Derived from the error class — a client does not guess it. |
Validation failures carry the whole list
Section titled “Validation failures carry the whole list”Not the first bad field. All of them:
{"error": {"code": "validation_failed", "message": "3 fields are invalid", "details": {"errors": [{"field": "…", "code": "…", "message": "…"}]}}}Four decisions worth knowing before you write the handling
Section titled “Four decisions worth knowing before you write the handling”404, not 403, for a resource in another tenant. A 403 would confirm the id exists.
The exception is a resource you can see but may not modify, which is a real 403.
Budget exhaustion is 402 and the run is never enqueued. Treat it as terminal until an
operator raises the limit; retrying cannot succeed, and there is no run row to go and look at.
A synchronous wait that times out is 504 and the run keeps running. The response carries
the run id — poll it or stream it. Do not treat a 504 as a failed run.
A missing platform dependency is not a 503 on a business endpoint. 503 means the
database or the queue is down, and it is the one class where retrying is straightforwardly the
right move.
Retryability
Section titled “Retryability”The catalogue lists a retryable_by_default per code, which is the
class default. provider_error takes its retryability per instance, so for that one read
the value in the envelope rather than the table.
One known divergence in the OpenAPI document
Section titled “One known divergence in the OpenAPI document”Operations that validate a body declare a 422 typed as FastAPI’s HTTPValidationError. That
is the framework’s default and not what the platform returns: the body is the envelope
above, with code: "validation_failed" and the per-field list under details.errors.
Generate against the envelope, not against that schema. Every generated operation page in the reference repeats the warning for exactly this reason.
Where to next
Section titled “Where to next”- Error catalogue — every code, its status and its default retryability.
- Event catalogue — the stream has its own error vocabulary, spelled differently.