AlgoMaster Logo

Error Response Design

High Priority15 min readUpdated September 13, 2026
Listen to this chapter
Unlock Audio

Knowing that a request failed is only the first step toward recovering from it. If an order-cancellation request returns 409 Conflict, the client still has several questions. Has the order shipped? Is another cancellation already running? Should the application refresh the order, show a correction, or give the customer a reference for support?

An error response turns a failed request into an understandable outcome. Its body should give software stable information to act on and people enough context to decide what to do.

This chapter uses a fictional bookstore's JSON API over HTTPS to explain error formats, machine-readable identities, useful messages, diagnostic references, and compatibility.

1. The Error Contract

Design error responses alongside successful responses. Clients need to know what shape to expect when a request fails, which values are stable, and which information may be absent. A collection of unrelated exception messages does not provide that contract.

An error response serves three audiences. HTTP software uses the status and headers. Application code needs a stable problem identity and any structured information it needs to handle the problem. A person needs a clear explanation and, when necessary, a support reference.

The diagram separates these responsibilities:

These parts should agree. A body describing a rejected cancellation does not belong under 200 OK, and a JSON field saying the caller must authenticate does not replace the relevant authentication challenge header.

Consistency means shared rules, not identical detail for every failure. A validation problem may identify an input field. An unexpected server failure may provide only a general explanation and a reference. Clients should recognize both without assuming that every error includes field-level information.

Keep successful resources and failed requests distinguishable. A successful GET of an import job can return a resource whose state is failed; that is still a successful resource retrieval. The failure the resource records does not automatically turn its representation into an HTTP error document.

2. A Common Error Format

A custom JSON envelope can be a sound design when you document it and use it consistently. For example, an API may use {"error":{"code":"order_not_cancellable","message":"The order has already shipped."}}. Its field names and nesting are application choices, so each client must learn them.

For a new error contract, consider Problem Details, the standard format RFC 9457 defines. It supplies a common JSON object and the media type application/problem+json. It does not select your business rules or define every possible error your application can report.

The examples below describe a proposed bookstore contract that uses Problem Details. They are not a recommendation to silently replace an established error.code envelope. An existing API needs an explicit compatibility plan before changing its response shape.

Assume the caller owns order ord_410 and may know its fulfillment state. The order has shipped, so its cancellation endpoint rejects the request:

The fictional type address identifies a category of failure. The instance address identifies this particular occurrence. The additional order_status member is an extension the bookstore defines, not a standard Problem Details field. Here, the API's business policy permits return requests after delivery; a different return policy needs different wording.

For a cancellable order, the same endpoint could instead create a cancellation resource and return 201 Created with Content-Type: application/json, a Location identifying that resource, and its representation. Clients advertise both response formats in Accept because success and failure have different media types.

Keep the problem object at the top level when sending application/problem+json. Wrapping it inside {"error": ...} changes the shape a standard Problem Details consumer expects. An error body also does not remove the need for headers such as WWW-Authenticate, Allow, or an appropriate Retry-After when those apply.

All HTTP examples use the exact single-line JSON bodies these examples show, without trailing newlines. Credentials are placeholders. The example URLs describe fictional API identifiers, not external reading material.

3. Standard Members and API Requirements

Problem Details defines five standard members. Their meanings are distinct even when several appear to describe the same failure:

Scroll
MemberValue typePurposeExample contract rule
typeString containing a URI referenceIdentifies the problem categoryUse a stable absolute URI for a domain-specific problem
titleStringShort human-readable summary of the categoryKeep wording consistent apart from translation
statusNumberRecords the HTTP status the server generated for this occurrenceGenerate it from the same value as the HTTP response status
detailStringExplains this occurrence to a personInclude safe, relevant context and a useful next step when the server knows one
instanceString containing a URI referenceIdentifies this occurrenceUse an opaque occurrence identifier under an API-controlled prefix

The standard does not require every problem to contain all five members. This bookstore contract requires type, title, and status for application-generated problems. It normally includes detail and includes instance when it can assign an occurrence reference. Those presence rules belong to the API, not to RFC 9457.

If the server omits type, its default is about:blank. That value adds no error meaning beyond the HTTP status. It is suitable for a generic failure without a more specific public category. Its title should follow the status phrase, such as Forbidden or Internal Server Error, and may use a translation.

The status member is advisory, but the generator must use the same code in the actual HTTP response. It is useful when a system stores a problem body separately from the HTTP message. An intermediary can nevertheless create disagreement by changing the status in transit. Clients should preserve that discrepancy for diagnostics and avoid letting a body value silently override their HTTP handling.

A type URI identifies a category; it is not a network request the client must make to understand every failure. For an HTTPS type URI, the API should provide human-readable documentation at that address. Runtime clients should match known identities without automatically fetching the address on each error.

Prefer absolute type URIs. A relative value such as order-not-cancellable can resolve to different identities depending on which endpoint returns it. Changing the URI later changes the category's identity even if the new URL displays the same documentation.

An instance URI need not be retrievable. In these examples, occurrence URIs are identifiers that support staff can look up internally; the API does not expose a public GET endpoint for them. If an API does expose occurrence records, the API must separately check who may access them.

4. Stable Identities and Useful Messages

Choose problem categories around distinctions a client can use. order-not-cancellable expresses a business restriction. OrderStateException exposes an implementation class whose name may change during a refactor. Both might describe the same internal event, but only the first serves as an intentional public contract.

Do not create a separate problem type for every sentence variation. Two shipped orders can share one type even though their occurrence references differ. Conversely, an order that has shipped and a cancellation already in progress may need different types if clients should present different actions.

For each domain-specific type, document its URI, title, associated HTTP status, meaning, extension fields, and expected client handling. State which fields the API requires and whether it may add new allowed values later. Handlers, client libraries, and documentation should all use this catalog.

Problem Details consumers use type as the primary problem identifier. A custom format may instead use a stable code. If you add a short code extension to Problem Details for compatibility, define its relationship to type explicitly; do not allow two independent identifiers to disagree about the failure.

Messages have a different purpose. Keep title short and stable for the category. Use detail to explain the particular occurrence without requiring the reader to understand your storage model.

For an order that has shipped, “This order has already shipped. You can request a return after delivery.” gives context and a relevant next step under the example policy. “Invalid state” gives too little information. A database constraint name gives the wrong information. “Try again” is misleading when waiting will not make the order cancellable.

Clients should not parse detail to extract the order state or choose a workflow. If they need the state, provide a documented field such as order_status. Treat messages as display text, not executable HTML, and let applications use their own wording when a stable problem type supports a more suitable interface.

If the API translates messages, the problem identity and structured values stay unchanged. Content-Language can indicate the chosen language; Accept-Language can express the client's preference. Define a default language and fallback behavior so an unsupported preference does not prevent reporting the original failure.

5. Structured Extensions

Extensions add information a particular problem type needs. They should answer a concrete client question that the standard members cannot answer reliably.

For the cancellation problem, order_status lets an application recognize the reported state without extracting a word from detail. The type definition should specify that it is a string, which states can appear, and what clients should do with an unfamiliar value. It describes the state the server observed during this request. The state may change afterward.

Validation errors also benefit from structure. Assume reading-list names must contain a character after trimming spaces. The client submits valid JSON that fails this rule:

The bookstore defines errors as an array of field issues. In this example, field names a top-level request-body property, code identifies the field rule, and detail explains the issue. Neither that array nor its members are a universal validation schema that Problem Details supplies. A richer API needs a documented way to locate nested fields, query parameters, and other input locations.

Keep extension shapes predictable. If errors is an array, use an array even when the API reports only one issue. Document omission instead of alternating between an absent member, null, a string, and an array without clear meaning. Bound the number and size of reported issues so producing an error does not create an excessive response.

Clients consuming Problem Details must ignore extensions they do not recognize. That supports adding optional information over time. It does not make arbitrary changes to recognized fields safe: changing errors from an array to an object still breaks consumers that use it.

Avoid returning a dump of every internal problem the server discovers during processing. Select the relevant public failure, and group related input issues only when the type defines that grouping. The error contract should explain the rejected request without becoming a record of the application's entire execution.

6. Safe Disclosure and Support References

Useful information depends on what the caller may know. The order owner may see that an order has shipped. The API may forbid another customer from learning that the order exists. Apply the same rules about what the caller may learn to every field, including the type URI, detail text, extensions, and any occurrence record.

For an authenticated support user who can see reading lists but cannot create them, a generic authorization problem is sufficient:

There is no need to include the caller's roles, internal permission expressions, or account details to explain this refusal. If the API conceals an inaccessible resource with 404, its problem body must preserve that concealment too.

Unexpected failures need a different level of detail. A generic 500 problem can say that the server could not complete the request and provide an occurrence reference. It should not expose stack traces, SQL statements, filesystem paths, service credentials, or raw dependency responses. Do not automatically echo submitted values; those values may include passwords, tokens, or personal information.

Use the occurrence reference to connect the public failure to restricted diagnostics. The diagram shows one failure producing two records with different contents:

The reference makes a terse public error useful without making private diagnostics public. Generate opaque identifiers that contain no customer data. A reference is not proof of authorization and should not grant access to logs or occurrence records.

A request identifier and a problem occurrence identifier serve related but different purposes. A request can succeed and still have a request identifier. The instance member identifies a problem occurrence. If the API also exposes a request_id extension or a request-ID header, document it as an API convention and define how it maps to internal diagnostics. Do not trust an arbitrary client-supplied identifier as a unique internal key.

The examples use Cache-Control: no-store because caches should not reuse errors specific to an occurrence or customer. A generic public error may have a different deliberate cache policy. Restricted diagnostics also need access controls, retention rules, and redaction; moving a secret from the response to a log does not make it harmless.

7. Error Generation Across the Request Path

Use one shared error-mapping layer to produce a consistent format. Domain code can report a known business condition, while an error mapper selects its public type, status, allowed extensions, and wording. A serializer then produces the response with the correct media type and headers.

Keep this path simple. Reporting a failure should not require another call to the dependency that just failed. Maintain a minimal fallback for unexpected exceptions, and construct it from known-safe values rather than serializing an exception object.

An API gateway, authentication layer, body parser, or application handler can reject a request. Configure components you control to produce compatible public errors where practical. Preserve meaningful status codes and required headers when translating upstream failures. An HTML proxy response or a dependency's private error schema should not accidentally become the application's documented contract.

Even with that coordination, clients cannot assume every failure will contain a valid problem object. A network connection can fail before a response arrives. An intermediary can return HTML. The connection can close before the full response arrives. A HEAD response carries no content, so its error details cannot depend on a body.

This diagram shows a client's error-handling path after ordinary success handling:

Successful JSON parsing is only the beginning. Validate the types of members the client uses. Under Problem Details, clients must ignore a standard member whose value has the wrong type, treating it as absent. An unknown problem type or extension should lead to a controlled fallback, not a second application failure while trying to display the first.

Do not show arbitrary HTML or raw response bodies to users as a fallback. Preserve bounded, appropriately redacted diagnostic information and display a safe message. Similarly, a failure response should not promise that a write did not happen unless the server can establish that fact. A lost response or gateway failure can leave the operation's outcome uncertain.

8. Compatibility and Verification

Treat problem identities and structured fields as public API behavior. Renaming a type URI, changing a field's data type, removing a documented member, or changing what an existing category means can break clients. Message wording can evolve more freely when clients rely on identities and fields instead of text matching.

Adding an optional extension is generally compatible with Problem Details consumers because they must ignore unknown extensions. Adding a new problem type still requires generic fallback behavior. Keep existing meanings intact and document new outcomes rather than repurposing an old type for a different condition.

An existing custom error format does not become obsolete just because a standard format is available. If clients depend on error.code, preserve that representation until a supported migration is in place. Possible approaches include an explicit API version or negotiated support for application/problem+json. Define the default when Accept is absent and test older clients; do not unexpectedly change every error response after a framework upgrade.

Verify the full response contract with representative cases. A business conflict should have the documented type and allowed extensions. A validation failure should retain its array shape with one issue. A forbidden request should reveal no protected data. An unexpected failure should produce a safe fallback with a usable support reference when available.

Check the response's status, media type, required headers, and body together. Also exercise unknown extensions, unknown types, missing optional members, and malformed intermediary responses in client handling. These checks test whether a caller can recover useful information from failure, rather than merely whether the server returned some JSON.

Summary

Design error responses as a stable contract for HTTP software, application code, and people. Use a consistent format, separate machine-readable identities from messages, and add structured fields only when they support a clear client decision.

Problem Details provides standard members and extension rules, while the API defines its domain-specific types and compatibility promises. Keep public explanations safe, connect failures to restricted diagnostics through opaque references, and support clients that encounter unfamiliar or incomplete error responses.