Problem Details

Problem details is a standard format for describing an API error in the response body, defined by RFC 9457 (which replaces RFC 7807). It exists because the status code is not enough on its own: status codes “cannot always convey enough information about errors to be helpful,” and while a person can read an HTML error page, “non-human consumers of HTTP APIs have difficulty doing so.”

The specification’s own example makes the division of labour clear. For an account without enough credit, the designer uses “403 Forbidden status code to inform generic HTTP software (such as client libraries, caches, and proxies) of the response’s general semantics,” while the reason and the balance are “carried in the response content so that the client can act upon them appropriately.” The status code is for the infrastructure; the body is for your consumer’s code.

The members, read as instructions to client code

MemberWhat it isWhat a client should do with it
type“A JSON string containing a URI reference” that identifies the problem type. Absent, it is treated as about:blankBranch on it. The specification is normative here: consumers “MUST use the ‘type’ URI (after resolution, if necessary) as the problem type’s primary identifier.” Do not fetch it — consumers “SHOULD NOT automatically dereference the type URI,” except when showing information to a developer
statusThe HTTP status code, repeated in the bodyUse the real response status; the copy is there for when the body has been separated from its response
title“A short, human-readable summary of the problem type,” which “SHOULD NOT change from occurrence to occurrence of the problem, except for localization”Display or log it. It is “advisory,” included for readers who cannot resolve the type URI
detail“A human-readable explanation specific to this occurrence,” which “ought to focus on helping the client correct the problem, rather than giving debugging information”Show it to a human. Do not parse it — “consumers SHOULD NOT parse the ‘detail’ member for information; extensions are more suitable and less error-prone”
instance“A URI reference that identifies the specific occurrence of the problem”Record it. This is the value a support conversation is about

The separation the format is built around is not machine versus human across the board — it is that one member identifies which problem this is, and the free text identifies nothing. type is the primary identifier of the problem type, normatively. title and detail are prose for people, and detail explicitly must not be parsed. But status is a status code, instance is an identifier meant to be recorded and correlated, and extension members exist precisely so that a program can read structured specifics — a balance, a list of field errors.

So the rule to carry is narrower and more useful than one field for machines: identify the problem by type, then decide what to do using status and whatever structured members that type defines — and never take a decision from title or detail.

Primary identifier is not the same as sole input, and treating it as such breaks in two ordinary situations. Where type is absent it defaults to about:blank, meaning the problem has no semantics beyond the status code — so a 404 and a 503 can arrive with an identical type and obviously warrant different handling, one of them retryable and one not. And within a single known type, the extension members are there to be acted on: a declined-payment type that carries the available balance, or a validation type that carries a list of field errors, is telling the client what to do next in those members rather than in its name. The discipline is about where a decision may come from: identifiers and structured values, yes; prose, never. Which is why an API whose errors differ only in prose has no machine-readable error interface at all, whatever its documentation says — and why a client matching on message text has built a dependency on your copywriting that will break the next time someone improves it.

Extensions are how an error format evolves

Problem types “MAY extend the problem details object with additional members that are specific to that problem type” — the out-of-credit example adds a balance, a validation failure adds a list of field errors.

What makes that safe is a requirement on consumers rather than a promise from producers: “clients consuming problem details MUST ignore any such extensions that they don’t recognize; this allows problem types to evolve and include additional information in the future.” So adding structured detail to an error is a compatible change, in the same way and for the same reason that adding an optional field to a response is.

Introducing a new problem type is a different question, and it deserves a stated expectation. A client that treats an unknown type as a failure of the class the status code indicates keeps working; one that switches exhaustively on known types does not. Saying which behaviour you expect is part of the contract, and it is the difference between a new error type being routine and being a breaking change.

What not to put in it

  • Debugging information. The specification directs detail at helping the client correct the problem “rather than giving debugging information.” Stack traces and internal identifiers belong in your logs, correlated by the occurrence identifier.
  • Text you intend the end user to read verbatim. Your caller’s application decides what its users see, in its own wording and language; an error body that assumes otherwise gets displayed badly or not at all.
  • Anything an unauthenticated caller should not learn. An error is a response to whoever asked, including someone probing, so distinguishing no such record from you may not see this record is a disclosure decision rather than a helpfulness one.

Three rules are worth taking even if you never adopt the format. One stable machine-readable identifier per failure type, chosen so it never changes. Human text kept out of client logic by construction. And structured extras that unknown-to-the-client members can be added to, so the error surface can grow without a version. The complementary half — telling a client whether and when to try again — is covered under Retry-After and backoff, and how errors sit inside the wider interface obligation is worked through in You Cannot Un-publish an API.

Reference: RFC 9457, Problem Details for HTTP APIs.


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.