rfc-9457problem-detailserror-handlingresthttpapi

Problem Details for HTTP APIs: The RFC 9457 JSON Error Format

·9 min read·APIs

Every API invents its own error shape — and that's the problem

Ask five backends how they report an error and you'll get five JSON shapes: { "error": "..." }, { "message": "...", "code": 42 }, { "errors": [ ... ] }, a bare string, or an HTML page where JSON was promised. Clients then write bespoke parsing for every service they call. Problem Details, defined by RFC 9457, fixes this with one standard, machine-readable JSON error format for HTTP APIs.

RFC 9457 was published in 2023 and supersedes the older RFC 7807. By 2026 it is the default recommendation in most REST style guides — the argument over "how should errors look" now has a published answer. If you already follow JSON API best practices and HTTP status codes, this is the missing piece: the *body* that accompanies a 4xx or 5xx.

The media type and the five base fields

A Problem Details response is served with the content type application/problem+json (there is an XML variant, application/problem+xml, but JSON is what everyone uses). The body is a JSON object with five standard, all-optional members:

json
{
  "type": "https://api.example.com/problems/insufficient-funds",
  "title": "Insufficient funds",
  "status": 403,
  "detail": "Your account balance is 30 but the transfer requires 50.",
  "instance": "/accounts/12345/transfers/98765"
}
  • type — a URI that identifies the *kind* of problem. It is the primary key clients switch on. When dereferenceable, it should point to human-readable documentation for that error type. Defaults to "about:blank" when omitted.
  • title — a short, human-readable summary of the problem type. It should stay the same for a given type and not change from occurrence to occurrence.
  • status — the HTTP status code, duplicated in the body so it survives proxies and logs that drop the response line.
  • detail — a human-readable explanation *specific to this occurrence*. This is where the changing specifics go (the actual balance, the offending field).
  • instance — a URI identifying the specific occurrence, useful for correlating with logs or a support ticket.

The golden rule: `type` and `title` describe the class of error; `detail` and `instance` describe this one event. Clients branch on type; humans read detail.

Extension members: where your domain data goes

The five fields are rarely enough — a validation error needs to say *which fields* failed. RFC 9457 explicitly allows extension members: any additional top-level keys you like. This is how you carry structured, actionable detail without breaking the standard.

json
{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Your request body is invalid",
  "status": 422,
  "detail": "2 fields failed validation.",
  "instance": "/orders",
  "errors": [
    { "field": "email", "message": "must be a valid email address" },
    { "field": "quantity", "message": "must be >= 1" }
  ],
  "traceId": "0af7651916cd43dd8448eb211c80319c"
}

A client that understands your API reads errors and highlights form fields; a generic client still gets a sensible title and status. Adding a traceId extension is one of the highest-leverage habits in API design — it lets a user paste one value into a support request and lets you find the exact log line.

What RFC 9457 changed over RFC 7807

If you already emit RFC 7807, you barely have to move — 9457 is a compatible clarification, not a rewrite. The meaningful additions:

  • A registry for common problem type URIs, so unrelated APIs can share well-known types instead of each minting their own.
  • Clarified guidance for reporting multiple problems in one response (typically an extension array like errors above).
  • Expanded advice for non-dereferenceable type URIs — a type is an identifier first; it does not have to resolve to a live page.

Producing and consuming it

On the server, most frameworks now ship first-class support — ASP.NET Core, Spring, and many Node frameworks emit application/problem+json from a single exception handler, so you map an exception type to a problem type once and every error endpoint is consistent. The key discipline is to route *all* errors through that one handler so you never leak a stack trace or a raw framework error page.

On the client, detection is a content-type check:

ts
async function call(url: string, init?: RequestInit) {
  const res = await fetch(url, init);
  if (res.ok) return res.json();

  const isProblem = res.headers
    .get("content-type")
    ?.startsWith("application/problem+json");

  const body = isProblem ? await res.json() : { title: res.statusText };
  throw new ApiError(body.type ?? "about:blank", body.title, res.status, body);
}

Because the shape is predictable, one ApiError class handles every endpoint of every RFC 9457 service you call.

Design tips that keep it clean

  • Never put secrets or stack traces in `detail`. It is user-facing. Put internal diagnostics behind the traceId in your logs.
  • Keep `title` stable per `type`. If title changes per request, you've put occurrence data in the wrong field — move it to detail.
  • Version your `type` URIs` like documentation, not like code. They are stable identifiers; don't churn them.
  • Validate your own error bodies. Generate a JSON Schema for your Problem Details shape and validate responses in tests, so a malformed error can't slip out. When debugging, format the payload to read nested errors arrays.

For neighboring topics see JSON API best practices, HTTP status codes explained, and the 429 rate-limit response, which pairs naturally with a rate-limit-exceeded problem type.

Frequently asked questions

RFC 9457, "Problem Details for HTTP APIs," is an IETF standard that defines a common JSON (and XML) format for returning error details from an HTTP API. It was published in 2023 and supersedes RFC 7807, giving APIs one consistent, machine-readable error shape instead of a custom one per service.

application/problem+json for JSON responses (or application/problem+xml for XML). Clients can detect a Problem Details body by checking the response's Content-Type header.

All five base fields — type, title, status, detail, and instance — are technically optional. In practice you should always send type, title, and status; detail and instance carry occurrence-specific information.

Yes. RFC 9457 allows extension members — any extra top-level keys such as an errors array for field-level validation messages or a traceId for correlation. Generic clients ignore them; clients that know your API use them.

RFC 9457 is a backward-compatible update. It adds a registry of common problem types, clarifies how to report multiple problems at once, and expands guidance on non-dereferenceable type URIs. Existing RFC 7807 responses remain valid.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.