| |

REST API 10 ๐ŸŒ Standardized Error Formats (RFC 7807 Problem Details)

โฑ๏ธ Estimated read: 13 min | |

An API’s error format is not an afterthought. It is the interface that every client integrates against, and an inconsistent error shape forces every client to write its own parser for every endpoint. RFC 7807, “Problem Details for HTTP APIs,” defines a standard JSON object for exactly this purpose: a machine-readable error body that any client can parse without prior knowledge of the API’s internal conventions . The specification was published in March 2016 and has been obsoleted by RFC 9457 (July 2023), which retains the same core fields and adds a registry of common problem types . The media type is application/problem+json.

The design goal is precise. HTTP status codes tell the client what category of error occurred (400, 403, 404, 500), but they cannot tell the client why it occurred or how to fix it. A 403 Forbidden response might mean “not logged in,” “logged in but not authorized,” “account suspended,” or “out of credit.” RFC 7807 provides the structure that distinguishes these cases without inventing a new status code for each one . The object has five standard membersโ€”type, title, status, detail, and instanceโ€”plus any number of extension members for problem-specific data .

Key point: A problem details object is a JSON body with the media type application/problem+json. The type member is a URI that identifies the problem class (defaulting to about:blank if omitted), title is a human-readable summary of that class, status is the HTTP status code, detail is a human-readable explanation specific to this occurrence, and instance is a URI identifying this specific occurrence. Extension members carry problem-specific data, and the standard explicitly warns against including stack traces or internal implementation details .


Why a standardized error format exists

The client integration problem. Every API that defines its own error formatโ€”{"error": "Not found"}, {"message": "Invalid input", "code": 42}, {"errors": [{"field": "email", "msg": "invalid"}]}โ€”forces every client to write a parser specific to that API. When the API changes its error shape, every client breaks. RFC 7807 solves this by defining a common structure that clients can parse generically, while extension members allow the API to carry problem-specific data without breaking the common shape . The client reads title and detail for the human message, status for the HTTP code, and the extension fields for the machine-readable details it actually needs.

The status code redefinition problem. Without a standard format, APIs are tempted to invent new status codes or overload existing ones to carry problem-specific meaning. RFC 7807 exists to prevent this: the status code remains the HTTP status code, and the problem details object carries the extra information . A 403 is still a 403; the problem details explain which 403 it is. This preserves the semantics that proxies, caches, and generic HTTP libraries depend on.

The multiple problems problem. A validation failure often involves several fields, each with its own error. The standard allows a single problem details object to carry an array of sub-problems as an extension member. The RFC’s own example uses invalid-params (which an erratum corrected to invalid_params to comply with the naming rules) to list the field-level errors . Each sub-problem names the field and the reason, and the client can map them to the corresponding form fields.

The information leakage problem. Error responses are a common source of information disclosure. A stack trace in a 500 response reveals the framework, the file paths, and sometimes the database schema. RFC 9457 explicitly warns that problem details must be vetted for information that could compromise the system, and that generators should avoid exposing stack dumps or other implementation details . The detail member should help the client correct the problem, not describe the server’s internals.


a. The five standard members

The problem details object has five members defined by the specification. Only title is effectively required in practice, but the others carry the information that makes the object useful.

// A problem details response for an out-of-credit error
// The HTTP status is 403, and the problem details explain why
{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "status": 403,
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc",
  "balance": 30,
  "accounts": ["/account/12345", "/account/67890"]
}

The type member is a URI reference that identifies the problem type. It is the primary identifier: two responses with the same type represent the same class of problem, even if the detail differs . The URI should resolve to human-readable documentation when dereferenced, but the client should not assume it doesโ€”the URI is an identifier, not necessarily a link . If type is omitted, the default value is about:blank, which means the problem has no semantics beyond the HTTP status code .

The title member is a short, human-readable summary of the problem type. It should be the same for every occurrence of that type, except for localization . It is not the place for occurrence-specific detailsโ€”that is what detail is for.

The status member is the HTTP status code. It duplicates the information in the HTTP status line, and the specification acknowledges the potential for disagreement if an intermediary modifies the status code in transit . The member is included so that the problem details object is self-contained and can be logged or stored without the surrounding HTTP context.

The detail member is a human-readable explanation specific to this occurrence. It should help the client correct the problem, not provide debugging information . The instance member is a URI that identifies this specific occurrence, which is useful for support and forensic purposesโ€”it allows a user to report “error instance /account/12345/msgs/abc” and the support team to look it up .


b. Extension members and the validation pattern

The extension members are where the API carries its problem-specific data. The standard allows any additional members, with the constraint that their names should start with a letter and consist of letters, digits, and underscores . The validation pattern is the most common use: the top-level problem describes the category, and an extension member lists the field-level errors.

// A validation error with field-level details
// The "invalid_params" extension is an array of sub-problems
{
  "type": "https://example.net/validation-error",
  "title": "Your request parameters didn't validate.",
  "status": 400,
  "invalid_params": [
    {
      "name": "age",
      "reason": "must be a positive integer"
    },
    {
      "name": "color",
      "reason": "must be 'green', 'red' or 'blue'"
    }
  ]
}

The invalid_params member is not part of the standard; it is an extension defined by the API. The client that understands the extension can map the errors to the form fields. The client that does not understand the extension can still display the title and detail as a generic error message. This is the layering that the extension mechanism enables: a common structure that every client understands, and problem-specific extensions that some clients use.

The standard notes that multiple sub-problems can be conveyed this way only if they are similar enough to use the same HTTP status code. If the sub-problems require different status codes, the 207 Multi-Status code can be used to encapsulate multiple status messages . This is rarely necessary for validation errors, which are all 400 or 422, but it is the escape hatch for cases where the status codes genuinely differ.


c. The about:blank default and RFC 9457 changes

When the type member is omitted, it defaults to about:blank. This means the problem has no additional semantics beyond the HTTP status code, and the title should be the standard HTTP status phrase (e.g., “Not Found” for 404) . The about:blank default is useful for cases where the status code is self-explanatory and no extra detail is needed. A 404 with about:blank and the title “Not Found” tells the client everything it needs to know.

RFC 9457, which obsoletes RFC 7807, makes three changes that matter for implementers. First, it introduces a registry of common problem type URIs, so that widely used problems (authentication required, insufficient permissions, resource not found) can share a standard URI instead of each API defining its own . Second, it clarifies how multiple problems should be treated when they share a status code . Third, it provides guidance for using type URIs that cannot be dereferencedโ€”the URI is an identifier first, a link second . The core object structure is unchanged, so an implementation that follows RFC 7807 is compatible with RFC 9457.

The type URI should be stable over time. If the API changes the meaning of a problem, it should mint a new URI, because clients may cache the old one. The URI does not need to be a working link, but it should be under the API’s control and documented .


Complete Example Session

The following session traces a validation failure, a not-found error, and a server error, showing the problem details object for each.

// ============================================
// PART 1: VALIDATION FAILURE (400)
// ============================================
POST /api/users HTTP/1.1
Content-Type: application/json

{ "email": "not-an-email", "age": -5 }

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Request validation failed",
  "status": 400,
  "detail": "One or more fields are invalid.",
  "instance": "/api/users/requests/req_abc123",
  "invalid_params": [
    { "name": "email", "reason": "must be a valid email address" },
    { "name": "age", "reason": "must be a positive integer" }
  ]
}

// ============================================
// PART 2: NOT FOUND (404)
// ============================================
GET /api/users/999 HTTP/1.1

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/user-not-found",
  "title": "User not found",
  "status": 404,
  "detail": "No user exists with ID 999.",
  "instance": "/api/users/999"
}

// ============================================
// PART 3: SERVER ERROR (500)
// ============================================
GET /api/reports/daily HTTP/1.1

HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred. Please try again later.",
  "instance": "/api/reports/daily"
}

// ============================================
// PART 4: THE CLIENT'S PARSER
// ============================================
// The client reads the Content-Type
// If it is application/problem+json, parse the standard fields
// title and detail for the human message
// status for the HTTP code
// extension members for the machine-readable details
// If it is not problem+json, fall back to a generic error

// ============================================
// PART 5: THE SERVER'S CONSTRUCTION
// ============================================
// Set Content-Type: application/problem+json
// Include type, title, status, detail
// Add instance with a request ID for support
// Add extension members for field-level details
// Never include stack traces or internal paths in detail

The five parts cover the validation failure, the not-found error, the server error, the client’s parsing strategy, and the server’s construction rules. What the session demonstrates is that the same object structure covers every error category, and the extension members carry the problem-specific details without breaking the common shape.


Quick Reference

Standard Members

MemberTypeRequiredPurpose
typeURI referenceNo (default about:blank)Identifies the problem class
titleStringEffectively yesHuman-readable summary of the class
statusIntegerNoHTTP status code
detailStringNoOccurrence-specific explanation
instanceURI referenceNoIdentifies this occurrence

Media Type

TypeValue
JSONapplication/problem+json
XMLapplication/problem+xml

Common Extension Patterns

ExtensionPurpose
invalid_paramsField-level validation errors
balanceAccount balance (out-of-credit example)
accountsLinks to remediation resources
retry_afterSeconds to wait before retry

RFC 9457 Changes

ChangeDetail
Problem type registryCommon URIs for widely used problems
Multiple problemsClarified treatment
Non-dereferenceable URIsGuidance for identifiers that are not links

Best Practices

โœ… Do This:

// Use the standard fields
{ "type": "...", "title": "Validation failed", "status": 400, "detail": "..." }

// Add extension members for problem-specific data
{ "invalid_params": [{ "name": "email", "reason": "..." }] }

// Use about:blank for self-explanatory status codes
{ "type": "about:blank", "title": "Not Found", "status": 404 }

โŒ Don’t Do This:

// Return a bare string
{ "error": "Not found" }

// Include stack traces
{ "detail": "at com.example.Service.method(Service.java:42)" }

// Use a non-standard media type
Content-Type: application/json // with a problem body

Common Pitfalls

PitfallWhy It HappensFix
Client does not recognize the errorWrong Content-TypeSet application/problem+json
Error not parseableMissing title or statusInclude the standard fields
Information leakStack trace in detailGeneric message, log details
Type URI unstableChanged between versionsMint a new URI for new semantics
Extension name invalidUsed hyphensUse underscores (invalid_params)

Real-World Examples

1. Basic problem

{ "type": "about:blank", "title": "Not Found", "status": 404 }

2. Validation with extension

{ "type": "...", "title": "Validation failed", "status": 400, "invalid_params": [...] }

3. Out of credit

{ "type": "https://example.com/probs/out-of-credit", "title": "You do not have enough credit.", "status": 403, "balance": 30 }

4. Instance for support

{ "instance": "/api/users/requests/req_abc123" }

5. Server error with no details

{ "type": "about:blank", "title": "Internal Server Error", "status": 500, "detail": "An unexpected error occurred." }

6. Content-Type header

Content-Type: application/problem+json

7. Multiple problems with 207

{ "status": 207, "problems": [...] }

8. Authentication required

{ "type": "https://api.example.com/problems/authentication-required", "title": "Authentication required", "status": 401 }

9. Rate limited

{ "type": "https://api.example.com/problems/rate-limited", "title": "Rate limit exceeded", "status": 429, "retry_after": 60 }

10. Express handler

res.status(status).type('application/problem+json').json({ type, title, status, detail });

Visual

The Problem Details Object

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  {                                           โ”‚
โ”‚    "type": "https://.../problems/...",       โ”‚
โ”‚    "title": "Short summary of the class",    โ”‚
โ”‚    "status": 400,                            โ”‚
โ”‚    "detail": "Occurrence-specific message",  โ”‚
โ”‚    "instance": "/api/.../req_abc",           โ”‚
โ”‚    "extension": { ... }                      โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  Content-Type: application/problem+json      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Type URI as Identifier

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  type: "https://api.example.com/problems/..."โ”‚
โ”‚         โ”‚                                    โ”‚
โ”‚         โ–ผ                                    โ”‚
โ”‚  Identifies the problem class                โ”‚
โ”‚  Two responses with the same type            โ”‚
โ”‚  are the same kind of problem                โ”‚
โ”‚                                              โ”‚
โ”‚  It should resolve to documentation          โ”‚
โ”‚  but the client should not assume it does    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Extension Members

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Standard fields:                            โ”‚
โ”‚  type, title, status, detail, instance       โ”‚
โ”‚                                              โ”‚
โ”‚  Extensions:                                 โ”‚
โ”‚  invalid_params: [{ name, reason }]          โ”‚
โ”‚  balance: 30                                 โ”‚
โ”‚  retry_after: 60                             โ”‚
โ”‚                                              โ”‚
โ”‚  Clients that understand the extension       โ”‚
โ”‚  use it; clients that do not still get       โ”‚
โ”‚  title and detail                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Information Leakage

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  BAD:                                        โ”‚
โ”‚  "detail": "at Service.java:42"              โ”‚
โ”‚                                              โ”‚
โ”‚  GOOD:                                       โ”‚
โ”‚  "detail": "An unexpected error occurred."   โ”‚
โ”‚  (details in the server log, not the response)โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
StandardRFC 7807, obsoleted by RFC 9457
Media typeapplication/problem+json
Standard memberstype, title, status, detail, instance
Default typeabout:blank
Extension membersProblem-specific data
Validation patterninvalid_params extension
Security ruleNo stack traces or internal paths
Multiple problemsExtension array or 207 Multi-Status

Key takeaways:

  • RFC 7807 defines a common error structure. The five standard membersโ€”type, title, status, detail, instanceโ€”give every client a predictable shape to parse, regardless of the API .
  • The type URI is the primary identifier. Two responses with the same type are the same class of problem. The URI should be stable and under the API’s control, but the client should not assume it resolves to a working link .
  • about:blank is the default. When the problem has no semantics beyond the HTTP status code, omit type and use the standard status phrase as title .
  • Extension members carry problem-specific data. The invalid_params pattern is the standard way to convey field-level validation errors. Clients that understand the extension use it; clients that do not still get the title and detail .
  • RFC 9457 obsoletes RFC 7807 with the same core structure. The changes are a problem type registry, clarified multiple-problem handling, and guidance for non-dereferenceable URIs .
  • Error responses must not leak internals. No stack traces, no SQL errors, no file paths. The detail member helps the client correct the problem; the server log holds the debugging information .

Remember: An API’s error format is part of its contract, and a contract that changes from endpoint to endpoint is not a contract. RFC 7807 gives the contract a standard shape: a type that identifies the problem class, a title that summarizes it, a status that matches the HTTP code, a detail that explains this occurrence, and an instance that identifies it for support. The extension members carry the data that the specific API needs, and the application/problem+json media type tells the client that the body follows the standard. The result is an error response that any client can parse, any developer can debug, and no attacker can mine for internal details.



Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!