larsggu.me › Reference › error envelope
error envelope
{"error":{"type","message","detail","request_id"}}
A single response shape for every failure, so that a caller can write one handler rather than one per endpoint.
Description
Failure responses are the part of an interface most often left to whatever each handler happened to return, and the cost falls entirely on the caller. If one endpoint returns a string, another an array and a third an HTML page from the proxy, every call site needs its own parsing, and the parsing is written against whatever was observed rather than anything promised.
One envelope for every failure removes that. The status code carries the class of problem for anything sitting between the two parties. A stable machine-readable type carries the specific condition, and it is the field a caller branches on, so it must come from a fixed vocabulary rather than being derived from a message. A human-readable message explains the problem to whoever reads the log. Field-level detail identifies exactly what was rejected. A request identifier ties the caller's log line to the service's.
The distinction between a retryable and a final failure has to be visible in the payload rather than inferred from the status. A 409 might be a conflict worth retrying after a read or a duplicate submission that must not be repeated, and the caller cannot tell the difference from the number alone.
Messages are read by people, which makes them a disclosure surface. A message that explains which internal component refused, or quotes the failing query, is useful for about a day and available to everyone who can make a request thereafter.
Fields
| Field | Form | Meaning |
|---|---|---|
| error.type | stable string | The machine-readable condition, from a fixed vocabulary. The field callers branch on. |
| error.message | human-readable string | For logs and operators. Never parsed by callers. |
| error.detail | array | Field-level rejections, each naming the field and the rule. |
| error.request_id | opaque string | Ties this failure to the service's own record of it. |
| error.retryable | boolean | Whether repeating the request unchanged could succeed. |
Example
A rejected field
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{"error":{
"type":"validation_failed",
"message":"The request could not be applied as written.",
"detail":[{"field":"currency","rule":"unsupported_value"}],
"request_id":"req_5c2",
"retryable":false}}One shape, a stable type to branch on, and an identifier that ties the two sides' logs together.
Failure modes
- Branching on the message text, which changes without notice.
- Returning the proxy's HTML error page for failures the application never handled.
- Placing internal component names or query text in the message.
- Leaving retryability to be inferred from the status code alone.
Related entries
Topic: Data shape. Last modified 2026-09-06.