A system that talks to a payment API, a Postgres database and a vendor SDK ends up with three error vocabularies. The HTTP client raises an exception with a status code, the driver raises an exception with a numeric code and a message string, and the SDK raises its own typed exception that may or may not wrap a readable HTTP response. The caller, usually an orchestration loop or an AI agent, then has to decide: retry, fix the request, refresh credentials, or stop.
The argument for a single envelope is that this is the same decision every time, and it depends on a small number of facts. Is this error about the request (fix it), about identity (get new credentials), about volume (wait), about the world being temporarily unwell (wait and retry), or about the world being permanently in a state that will not change (stop and replan)? Five classes cover those cases.
This matters more in agent systems than in plain services, because the model reading the error will act on whatever you put in the tool result. Practitioners writing about tool failures note that structured error strings returned as tool results, associated with the original call id, are what let the model reason about what went wrong instead of crashing the run in the n8n guide to tool calling errors. A consistent envelope is that structured string, generalized across every source.
The counterargument, that normalization throws away information, is real but solvable: keep the untouched original in a raw field. You lose nothing, and the caller gets a stable contract.



