An API error that contains only {"error":"bad request"} forces clients to parse prose or guess which field failed. An error that includes a stack trace, SQL message, or internal path gives clients more detail than they should receive. A useful error contract sits between those failures: machine-identifiable, human-readable, safe to expose, and stable enough for client behavior.
RFC 9457 defines Problem Details for HTTP APIs. The format is a starting point, not a substitute for modeling domain errors, authorization boundaries, and recovery actions.
Give each problem a stable identity
The application/problem+json representation can include type, title, status, detail, and instance. The RFC 9457 specification defines their semantics and allows extension members.
Use type as the stable machine identity for a class of problem. A resolvable documentation URI is useful when it can remain durable, but a client should not need to fetch documentation to handle the response. Do not generate a new type URI for every occurrence.
title is a short summary associated with the type. Clients should not branch on it because wording and localization can change. detail explains this occurrence and can mention safe, request-specific context. instance can identify the occurrence, but it must not expose internal storage keys or provide unauthorized access to diagnostics.
The HTTP status code remains the transport-level classification. Keep it consistent with the actual response status. A problem body should refine the meaning, not contradict the protocol.
Separate domain errors from infrastructure failures
Create a bounded catalog around actions clients can take. Examples include invalid input, resource state conflict, quota exceeded, dependency unavailable, and operation still in progress. Avoid mapping every internal exception class to a public type; implementation details are not a stable API.
For each type, define:
- applicable HTTP status;
- safe title and detail policy;
- retry eligibility;
- extensions and their schemas;
- authorization behavior;
- documentation and ownership;
- compatibility expectations.
Unexpected failures should map to a generic server problem and an internal correlation identifier. Return no stack trace, database statement, absolute path, secret, token, or private endpoint. Keep detailed diagnostics in access-controlled telemetry with appropriate retention.
Represent validation errors structurally
Field validation often needs more than one issue. Add a documented extension such as errors containing bounded entries with a field location, stable reason code, and safe message.
{
"type": "https://api.example.com/problems/invalid-request",
"title": "The request is invalid",
"status": 400,
"detail": "Two fields require correction.",
"errors": [
{ "field": "email", "code": "invalid_format" },
{ "field": "items[0].quantity", "code": "out_of_range" }
]
}
The domain is reserved for documentation examples. Do not echo the submitted values, especially credentials, personal data, or large payload fragments. Bound the number of errors and the length of paths and messages so malicious input cannot produce an oversized response.
Use a path notation with an explicit contract. Clients should not need to guess whether items.0.quantity, JSON Pointer, or another syntax is in use.
Preserve authorization boundaries
Error detail can reveal whether an account, document, or email address exists. Decide when unauthorized callers should receive the same external response for “missing” and “not permitted.” Keep the internal reason available for audited diagnosis without exposing it to the caller.
Authentication failure and authorization failure have different HTTP semantics, but deployment context matters. Follow the API's documented security model and applicable HTTP authentication scheme. Never compensate for vague errors by returning confidential resource attributes.
Rate-limit repeated failures where they can be used for enumeration, and avoid placing attacker-controlled values directly into logs. Encode log fields structurally to prevent injection and ambiguous multi-line records.
Make retry behavior explicit
Clients need to know whether retrying the same request can help. Validation and state-conflict errors usually require the client to change input or refresh state. A temporary dependency problem may be retryable within a deadline and server policy. An ambiguous write outcome requires idempotency and reconciliation rather than a blind repeat.
Use the HTTP Retry-After field where its defined semantics fit, and document whether the operation is idempotent. An extension such as retryable can be convenient, but it must not override method semantics, authorization, or idempotency requirements.
Keep human detail separate from machine decisions. A client that searches for “try again” in prose will break when wording changes.
Version extensions conservatively
RFC 9457 permits extension members, but every extension becomes part of the public response contract once clients rely on it. Prefer additive optional fields. Do not change a field from string to object or reuse one reason code for a different condition within a compatible API version.
Clients should ignore unknown extensions unless the API explicitly says otherwise. Servers should not require a new client to understand an extension before it can interpret the base status and type.
Publish examples for each problem type, including minimal and extended forms. Keep the documentation aligned with runtime behavior through contract tests generated from the same catalog where practical.
Test errors at the authoritative boundary
Tests should assert status, content type, stable type, required members, safe extensions, and absence of sensitive detail. Exercise malformed JSON, wrong types, missing fields, oversized input, authorization differences, state races, dependency failures, and unexpected exceptions.
Also test observability: the response's correlation identifier should locate the internal event without being a credential, and logs should preserve the domain reason without copying the entire request.
An effective error response tells an authorized client what class of problem occurred and what kind of next action is valid. It tells operators enough to find the protected diagnostic record. It does neither by exposing the internals that failed.
Was this guide useful?
Your rating helps us prioritize clearer, more practical technical content.
Review diffs, run checks, and prepare the release in Arezgit.
Keep Git review, security scanning, API checks, database inspection, and release preparation together in one local desktop application.
Explore Arezgit