Skip to content

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
codeStable. This is the one you switch on.
messageOne sentence, for a human. Not stable.
detailsPresent when there is machine-usable data.
fieldPresent on validation failures; a dotted JSON path.
correlation_idQuote this in a support request.
retryableDerived from the error class — a client does not guess it.

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.

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.

  • Error catalogue — every code, its status and its default retryability.
  • Event catalogue — the stream has its own error vocabulary, spelled differently.