How do you add API-specific data to an RFC 9457 problem details response, and what rules govern those extension members so the contract can evolve safely?
answer
- extensions = extra top-level members, no wrapper
- consumers ignore unknown members
- add = safe; rename/remove/retype = breaking
- meaning scoped to the type URI
- no internals in extensions either
basics
~20 sAdd them as extra top-level members of the problem JSON object — no nested envelope needed. Consumers must ignore unrecognised members, so adding one is non-breaking; renaming, removing or changing a member's type is breaking.
solid answer
~60 sExtension members are simply additional top-level fields alongside `type`, `title`, `status`, `detail` and `instance`. If a client needs to know which limit was exceeded or which balance was short, you add `limit` or `balance` at the top level — the spec deliberately does not require a nested `extensions` object. Evolution rules: - **Consumers must ignore unknown members.** State this in your contract; it is what makes adding fields safe and lets one client version tolerate many server versions. - **Adding is non-breaking; renaming, removing or retyping is breaking.** - **Meaning is scoped to the `type` URI.** The same member name may carry a different shape under a different problem type, so a client should only read extensions after matching the type. Avoid relying on that ambiguity — keep names consistent where the meaning is the same. - **Extensions are still a public contract**: document them, and apply the same no-internals discipline as any error body — no stack frames, no internal ids. Because of the type-scoping, prefer names unlikely to collide with future registered members.
code
http · 12 linesHTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/quota-exceeded",
"title": "Quota exceeded",
"status": 429,
"detail": "You have used 1000 of 1000 requests in the current window.",
"limit": 1000,
"remaining": 0,
"resetAt": "2026-08-12T14:00:00Z"
}go deeper
Know that extra fields go at the top level of the same JSON object and that clients should ignore ones they don't recognise.
Add which changes are breaking versus additive and give a concrete example such as a quota limit and reset time.
Cover type-scoped semantics, strict-deserializer pitfalls, multiple-problem representation, and documenting extensions per problem type.
Treat the extension vocabulary as a governed contract surface across services: naming policy, collision avoidance with future registered members, and how additions are rolled out and tested.
## What an extension member is RFC 9457 defines five members and then says: everything else you need is yours to add. Those additional fields are **extension members**, and they sit as ordinary top-level keys of the same JSON object. There is no wrapper. A problem document reporting a rate limit might carry `limit`, `remaining` and `resetAt` right next to `title` and `status`. This is a deliberate design choice. A nested envelope would force every consumer to reach one level deeper for the interesting data, and the flat shape makes the document readable as a single record in a log. ## The rules that make it safe **Ignore what you don't recognise.** This is the load-bearing rule. Consumers must not fail on unrecognised members, and you should state it explicitly in your API documentation so it becomes contractual rather than assumed. Without it, no server can add a field without risking a client that rejects the payload — a strict deserializer configured to fail on unknown properties is a common way teams accidentally break this. **Additive changes are safe; destructive ones are not.** Adding a new member is backwards compatible. Renaming it, removing it, or changing its type (string to object, number to string) breaks any client that read it. Treat extension members with the same discipline as fields in a success representation — they are contract. **Meaning is scoped by `type`.** The specification frames extension semantics as belonging to the problem type. Practically, this means a client should match `type` first and only then interpret extensions. It also means you *may* legitimately have `limit` mean something different under two different types. Do this sparingly: consistent names for consistent meanings across your API make clients simpler, and a name whose shape changes by context is a maintenance trap. **Avoid collisions with future standard members.** RFC 9457 registers problem types and can, in principle, see further standard members over time. Distinctive names for domain-specific data reduce the chance of a future clash; some teams prefix domain extensions for the same reason. ## What belongs in an extension Good candidates are values a client can *act* on programmatically and cannot derive from `type` alone: - The concrete limit and current usage for a quota problem. - The identifier of the conflicting resource. - A machine-readable hint about when to try again. - Structured per-item breakdown when one request contains many failing parts. Bad candidates are things that belong elsewhere or nowhere: a per-occurrence id (that is `instance`), a restatement of the human message, and — critically — internal diagnostics. An extension member is as public as any other part of the body, so stack traces, SQL, internal primary keys and infrastructure hostnames are just as forbidden here as in `detail`. ## Multiple problems in one response A frequent question is what to do when one request produces several distinct failures. RFC 9457 discusses this and the pragmatic answer is: pick the single most relevant problem for `type`/`title`/`status`, and carry the individual sub-problems in an extension member — an array of objects, each of which may itself be problem-shaped. Do not try to return several top-level problem documents; a response has one status and one body. ## Framework realities Frameworks with built-in problem-details support expose extensions through a map-like or dictionary API rather than typed properties, which means extensions are usually less type-safe than the core members in server code. That is worth accounting for: put the extension names in constants, and cover them with tests, because a typo in an extension key is a silent contract break that no compiler catches. ## Documentation Extensions are invisible to anyone reading only the RFC — they are your API's private vocabulary. Document each problem type together with the extension members it carries, and keep that in the same place as the type URI's documentation page. If the type URI resolves to a doc page, that page is the natural home for the extension schema.
- A client deserializer is configured to fail on unknown JSON properties. What happens when the server adds an extension member, and whose bug is it?The client starts rejecting valid problem documents, turning a graceful error into a parse failure — often a crash in the error-handling path itself. It is the client's bug: RFC 9457 expects consumers to ignore unrecognised members, which is precisely what makes server-side additions non-breaking. Configure lenient deserialization for problem documents and cover it with a test that feeds an unknown field.
- How do you represent several distinct failures from one request in a single problem document?Choose the single most relevant problem for the top-level type, title and status, then carry the individual failures in an extension member holding an array of objects, each of which can itself be problem-shaped. A response can only have one status and one body, so multiple top-level documents are not an option. Document the array member as part of that problem type's contract.
saying these in an interview costs you the question
- Wrapping extensions in a nested object because the spec supposedly requires it
- Assuming extension members are informal and need no documentation or versioning discipline
- Deserializing problem documents strictly so that any new server-side member breaks the client
- Putting internal diagnostics such as SQL or primary keys into extension members because they are non-standard
- Changing an extension member's type in place rather than adding a new member