ON THIS PAGE (8 SECTIONS)
- ✓Use the HTTP status for the class of failure. Put the specific reason in a stable, machine-readable body.
- ✓RFC 9457 defines a problem-details document so every API does not invent a new error shape.
- ✓Tell the client whether a retry is useful. A validation error retried unchanged will fail again.
- ✓Do not put stack traces, SQL, or secrets in the body a caller can read.
A client calls your API, receives 500, and the body says "Something went wrong." The caller cannot tell a bad input from a dependency that timed out, so it either retries forever or shows the user a dead end. The status code did half a job. The body did none.
Treat the error as a contract. The status classifies the failure for intermediaries and generic clients. The body names the problem in a field the caller can branch on. RFC 9457, which obsoletes RFC 7807, defines a problem-details document for exactly this: a type, a title, and room for API-specific fields, carried as application/problem+json. You do not have to adopt that media type on day one. You do have to stop changing the shape of errors between releases.
What the caller is allowed to assume
| Status | Meaning for the caller | Retry the same request? |
| 400 or 422 | The request is invalid as sent | No, until the caller changes it |
| 401 or 403 | The caller is unauthenticated or not allowed | No, unless credentials or permissions change |
| 404 | This identifier is not visible to the caller | No, unless the resource is created later |
| 409 | The request conflicts with current state | Only after the caller reads the new state |
| 429 | The caller is over a limit | Yes, after the indicated wait, if you send one |
| 5xx | The server or a dependency failed | Yes, if the operation is safe to repeat |
A 409 that hides which version conflicted is still a surprise. Include the field or the version the client must reconcile. A 422 that says "invalid" without the field name forces the client to guess. Name the field.
Keep the type stable
The human sentence can change. The problem type should not. A client that switches on the string "Email already registered" will break when you edit the copy. A client that switches on a type such as email-taken will not. Log the internal exception, with a correlation id, on the server. Return the correlation id in the problem document so support can find the log. Do not return the exception text.
Model failures are a different contract. A language model can return HTTP 200 and still be unusable. That case is covered in AI Failure Contracts. This article is about the HTTP API around it: when the model call fails as a dependency, the API still owes the client a status and a type, not a stack trace.
Illustrative scenario
This scenario is illustrative. A billing API rejects a charge because the account is past a limit the client could not see. The response is 403, type account-limit, and a field saying which limit. The client shows that limit. It does not retry the identical charge. A timeout from the card network is 503, type dependency-timeout, and the client retries only because the charge endpoint requires an idempotency key. Without that key, a retry would be a second charge, which is the subject of Retry a Call Only When the Call Is Safe.
Checklist
- Every error response has a status and a stable type.
- Field-level failures name the field.
- The body says whether the same request may be retried.
- Internal exceptions stay in logs, keyed by a correlation id the client can quote.
- Secrets, queries, and file paths are not in the response.
- A contract test fails the build if a documented type disappears.
Questions that usually come next
Is a single 400 for every mistake enough?
It is enough for a prototype. It is not enough once more than one client exists. Callers need to tell a bad field from a conflict from a limit. Use the status for the class and a type for the specific case.
Should browsers and API clients share one error page?
People can read HTML. Programs need fields. If the same route serves both, negotiate the representation. Do not make the program parse a sentence you wrote for a person.
Direct answers
Is a single 400 for every mistake enough?
Only in a prototype. Callers need the class of failure from the status and the specific case from a stable type.
Should browsers and API clients share one error page?
People can read HTML. Programs need fields. Negotiate the representation rather than forcing a client to parse prose.
References & Prior Art
RFC 9457: Problem Details for HTTP APIs
M. Nottingham, E. Wilde, S. Dalal • IETF (2023)
Barath
Product EngineerBarath builds production internal tools, double-entry financial ledgers, audit trail integrity, and supervised engineering workflows at Vasiva.