Chapter 03 of 10
Designing API Errors
One shape for every failure, why an error code is a contract and a message is not, what belongs in a problem document, and what has to stay out of it.
Error responses are designed last and met first. A caller integrating with your API spends their first afternoon on the failures, because the success path worked on the second attempt.
One shape, every failure
The decision that pays for itself is that every failure in the API has the same body shape. Everything else in this chapter is detail on top of it.
The reason is that a caller writes error handling once. When the shape is uniform, adding an endpoint costs them nothing, because their existing handler already parses the response and finds the fields it expects. When the shape varies, every new endpoint is a new special case, and the special cases accumulate in a client codebase you will never see.
RFC 9457 is the standard worth adopting for that shape, and it defines five members. A type identifies the kind of problem, a title names it briefly, status repeats the HTTP status code, detail explains this specific occurrence and instance points at the occurrence itself. The media type is application/problem+json, and the specification explicitly allows extension members alongside the five.
RFC 9457 obsoletes RFC 7807, which described the same format under the same media type. An API that already emits problem documents is therefore already compliant, and the newer document mainly clarifies and adds guidance.
The code is the contract, the message is not
The most useful field in an error body is the one that never changes. Callers have to branch on something, and if you do not give them a stable identifier they will branch on your prose.
That is a real failure mode rather than a hypothetical one. A team rewrites Invalid email to The email address is not valid for clarity, and somewhere a client that matched on the old string stops recognising the error. The wording change was correct and it was still breaking. The string had quietly become an interface.
So the type member should be a stable identifier, and it should mean the same thing forever. Whether you use a URI, as the RFC suggests, or a short code in an extension member, the property that matters is stability rather than format. The full treatment, covering extension members, type registries and how to evolve a catalogue, is in Consistent error handling with RFC 9457.
A way out of every error
An error that a caller cannot act on is a support ticket with extra steps. The design goal is that each failure tells the caller either what to change or what to wait for.
Validation failures need field-level detail, because a caller with three bad fields wants three messages rather than one. That is what extension members are for. A list of field and message pairs alongside the five standard fields is the common shape.
Conflicts and state errors need to say what the current state is, since the caller has to decide whether to refetch or give up. Rate limiting needs to say when to come back, which is a header rather than a body field and is covered in rate limits. Server errors need a correlation identifier the caller can quote. That is the only thing they can usefully do with a 500.
What has to stay out
An error body is readable by everyone who can reach the endpoint, including people you did not intend to serve.
Stack traces, database messages and query fragments describe your internals to an attacker at no cost to them. Internal hostnames and file paths do the same. Personal data echoed back from a failing request lands in the caller's logs, which is a place you have no control over and no visibility into.
The workable test is whether a field helps the caller fix their request. A correlation identifier passes, because it lets them quote something to your support. A serialised exception fails, because the caller cannot act on it and somebody else can.
The catalogue is part of the contract
Once error codes are stable identifiers, the set of them is an interface, and it needs the same care as the endpoints.
Adding a code is safe, since a caller that does not recognise it falls back to the status code. Removing one or changing what it means is a breaking change, even though nothing about the request or response schema moved. That is worth writing down where breaking changes are defined, which for most teams is next to the versioning policy.
The other half of the catalogue is coverage. Every operation should declare the errors it can actually return, because an undeclared 409 is a surprise no generated client handles.
In Routebase
An error response is defined once as a reusable component and referenced from every operation that returns it, which is what stops forty copies of the same body from drifting apart.

An inline response that already exists can be extracted into a component in one action, and the endpoint is relinked automatically rather than left pointing at a copy. Above the specification there is an organisation-level shared library, which is where a single ErrorResponse shape belongs when several projects should answer the same way. A specification keeps the library version it linked, so an edit in the library never changes a consuming specification silently, and an update shows as an indicator you accept when you are ready.
Three style guide rules cover this ground. One asks that 4xx and 5xx responses use application/problem+json. Two separate rules then ask that endpoints declare their error responses at all, where one accepts any 4xx, 5xx or default response and the other asks specifically for a 4xx. All three ship below error severity, so they inform rather than block until you decide otherwise, and severity is set per organisation and per project. The Components and Shared Library guides cover the two levels of reuse, and the Style Guide covers the rules.
Frequently asked questions
What is RFC 9457 Problem Details?
RFC 9457 defines a standard JSON body for HTTP error responses, carried under the media type application/problem+json. It specifies five members, namely a type identifier, a short title, the status code, a human readable detail and an instance reference, and it allows you to add your own members alongside them. It obsoletes RFC 7807, which described the same format under the same media type.
Should every API endpoint use the same error format?
Yes, and this is the single highest value decision in error design. A caller writes error handling once against one shape rather than once per endpoint, which means new endpoints inherit working error handling instead of needing new code. Mixed shapes also defeat generated clients, because the generator has no single type to deserialise a failure into.
Should API errors include a machine readable error code?
They should, because a caller has to branch on something and a human readable message is the worst available candidate. Messages get rewritten for clarity, translated and shortened, and every one of those changes silently breaks a client that matched on the string. A stable identifier lets you improve the wording freely.
What should not be in an API error response?
Anything that helps an attacker more than it helps a caller. That means no stack traces, no database errors or query fragments, no internal hostnames or file paths, and no personal data echoed back from the request. The test is whether the field helps the caller fix their request, because an error body is read by everyone who can reach the endpoint.
Last reviewed by The Routebase Team.