Error Format — problem+json
The status code says which kind of error; the body should say what specifically went wrong, in a shape that is identical across every endpoint. Inventing a new error shape per endpoint is a tax clients pay forever.
RFC 9457 Problem Details
Section titled “RFC 9457 Problem Details”There is a standard for exactly this: Problem Details for HTTP APIs (RFC 9457), served as application/problem+json. Its members:
type— a URI identifying the problem kind (a stable, documentable key).title— a short, human-readable summary (stable pertype).status— the HTTP status code, repeated in the body.detail— a human-readable explanation specific to this occurrence.instance— a URI for this particular occurrence (often the request path).- plus any extension members you need (e.g. an
errorsarray).
{ "type": "https://api.example.com/problems/out-of-stock", "title": "Item is out of stock", "status": 409, "detail": "Only 2 units of SKU-123 remain; you requested 5.", "instance": "/orders/8821", "available": 2}Producing one in code
Section titled “Producing one in code”| Advantages (Standard Error Format) | Trade-offs |
|---|---|
| Clients can handle errors programmatically | Requires designing an error schema and sticking to it |
| A machine-readable error code drives retry logic | Too much detail can leak internal information |
The type URI in Problem Details points to documentation | More verbose than a plain { "error": "..." } |
| A stack trace in the error helps debugging in development | A stack trace in production is a security risk |
Common mistakes
Section titled “Common mistakes”An error message that isn’t machine-readable Symptom:
{ "error": "The email address you entered is already in use" }- The client has to parse a string to know the error type
- Add a
codefield:{ "code": "EMAIL_ALREADY_EXISTS", "message": "..." }
Exposing internal detail in a production error Symptom:
{ "error": "Column 'users.email' cannot be null (SQL Error 1048)" }- This leaks the database schema to an attacker
- Filter internal detail out in production:
{ "code": "VALIDATION_ERROR", "message": "Email is required" }
💡 Real-world examples
Stripe API:
- Error shape:
{ "error": { "type": "card_error", "code": "card_declined", "message": "...", "param": "number" } }typegives the category,codethe specific error,paramthe field at faultGitHub API:
- Validation error:
{ "message": "Validation Failed", "errors": [{ "resource": "Issue", "field": "title", "code": "missing_field" }] }- A machine-readable
codelets the client render the error message in its own languageSources: Stripe API — errors, GitHub REST API errors