Error responses with problem details
One error format for the whole API — RFC 9457 problem details — what each member means, how to report several invalid fields, and what never belongs in an error body.
After this lesson you can
- Write an RFC 9457 problem details response with the right media type and members
- Report several validation errors with JSON Pointers to the offending fields
- Separate what the client needs from what belongs only in your logs
A status code tells a client which kind of failure happened. It cannot say which field was wrong, whether the card was declined or expired, or what to do next. That needs a body, and an API is far easier to use when every error body has the same shape.
RFC 9457, Problem Details for HTTP APIs (it replaced RFC 7807 in
2023), defines that shape. It is served with the media type
application/problem+json:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/insufficient-funds",
"title": "Not enough funds in the account",
"status": 422,
"detail": "The transfer needs 30.00 EUR but the balance is 12.50 EUR.",
"instance": "/transfers/7f3c",
"balance": "12.50"
}
The members
type— a URI reference identifying the kind of problem. It is the primary identifier: clients branch ontype, not on the wording oftitle. Ideally it resolves to human-readable documentation. When it is absent, its value is taken to be"about:blank".title— a short summary of the problem type. It should not change from one occurrence to the next (except for translation).status— the HTTP status code, repeated for convenience. It is advisory; the real status is the one on the response.detail— an explanation of this occurrence, aimed at helping the client fix it.instance— a URI reference identifying this specific occurrence.
Anything else is an extension member, like balance above. Clients
must ignore extensions they do not understand, which is what lets you add
them later without breaking anyone.
With "type": "about:blank" the problem carries no meaning beyond the
status code, and RFC 9457 says the title should then be the standard
phrase for that status ("Not Found"). A generic API gateway error is
exactly that case:
const PHRASES = { 404: "Not Found", 429: "Too Many Requests", 503: "Service Unavailable" }; function problem(status, extra = {}) { const body = { type: "about:blank", title: PHRASES[status], status, ...extra }; return { status, headers: { "Content-Type": "application/problem+json" }, body };} export function run() { return [problem(404), problem(503, { detail: "Maintenance until 10:00 UTC." })];}Several errors at once
A form with three invalid fields should get three errors in one
response, not one per round trip. RFC 9457's own example does this with
an extension member: an errors array where each entry names the
field with a JSON Pointer (RFC 6901) — written there in its
URI-fragment form, #/age; this course uses the plain form, /age.
A pointer is a path of
reference tokens, each prefixed with /: /items/0/quantity. Because
/ separates tokens, a key that contains / or ~ must be escaped:
~ becomes ~0 and / becomes ~1, in that order.
const right = (t) => String(t).replaceAll("~", "~0").replaceAll("/", "~1");const wrong = (t) => String(t).replaceAll("/", "~1").replaceAll("~", "~0");const pointer = (path, esc) => path.map((t) => "/" + esc(t)).join(""); export function run() { const path = ["shipping", "notes/gift"]; return { right: pointer(path, right), wrong: pointer(path, wrong), arrayIndex: pointer(["items", 0, "quantity"], right), };}What does not belong in an error
detail is for the client, so it must not carry stack traces, SQL,
internal hostnames or library versions — those help an attacker far more
than a legitimate caller. Log them server-side with a correlation id, and
give the client that id (an instance URI or an extension) so support
can find the log line.
Common mistakes
- A different error shape on every endpoint, so clients write a parser per endpoint and still miss cases.
- Clients matching on
titleordetailtext. Those are for humans and may be reworded or translated;typeis the stable identifier. - Stopping at the first invalid field, forcing the user through one round trip per mistake.
- Escaping
/to~1before escaping~, which turnsa/bintoa~01b— the example shows the difference.
What interviewers ask
- What should an API error response contain? — the right status code, plus a consistent machine-readable body: a stable type identifier, a human summary and detail, and field-level errors for validation. RFC 9457 is the standard shape.
- How do you return several validation errors? — one 400/422 with a list of errors, each pointing at its field (a JSON Pointer) with a message.
- What must never be in an error body? — stack traces, queries, internal names; log them and return a correlation id instead.
Exercise · Pro
A validation problem
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade