RFC 9457 Problem Details for HTTP APIs

5 min read Original article ↗

RFC 9457 defines a standard error response body for HTTP APIs. JSON responses use the IANA-registered application/problem+json media type.

I use Problem Details after choosing the correct HTTP status code. The body explains the API-specific problem; it does not replace the status code understood by clients, proxies, caches, and monitoring tools. If the status is still unclear, start with the REST API status code chooser.

The producer and client contract

All five standard members are optional. Producers can send only the members that help, and an omitted type resolves to about:blank. The actual HTTP status is authoritative for the exchange; a body status is advisory and, when present, must match the status sent by the origin server.

  • type is the resolved URI that clients use as the primary machine-readable identifier.
  • title is a stable human-readable summary of the type. detail explains one occurrence.
  • instance identifies one occurrence and can be a dereferenceable URI or an opaque URI.
  • Clients branch on the resolved type, not on title or detail.
  • Clients ignore unrecognized extensions and any standard member whose JSON type is wrong. They continue processing the rest of the object.
  • Clients should not automatically fetch type URIs during normal error handling. Developer tools can offer that as an explicit debugging action.

about:blank adds no semantics beyond the status code. A custom type is useful when clients need stable application-specific semantics, such as distinguishing an order-state conflict from other conflicts.

Problem Details keeps HTTP semantics and headers

The body does not replace status-specific headers. A 401 response still requires WWW-Authenticate. A 429 response can send Retry-After to tell the client how long to wait.

401 with an authentication challenge

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer realm="orders"

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

429 with retry timing

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60

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

500 without implementation secrets

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

{
  "type": "https://api.example.com/problems/internal-error",
  "title": "Internal server error",
  "status": 500,
  "instance": "urn:example:problem:01K0ABCDEF8Z7R6W5V4T3S2Q1P"
}

The 500 response returns only a non-sensitive occurrence reference. Keep the stack trace, query text, credentials, internal hostnames, and debugging context in protected server logs.

Validation errors as an extension

A 422 Unprocessable Content response can add an errors extension with one entry per invalid field. This follows the extension pattern shown in RFC 9457 without turning each field error into a separate top-level problem.

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/problems/validation-error",
  "title": "Request validation failed",
  "status": 422,
  "errors": [
    { "detail": "must be a valid email address", "pointer": "/email" },
    { "detail": "must be 18 or greater", "pointer": "/age" }
  ]
}

An errors collection represents multiple occurrences of one problem type. When several problems have disparate types, return the most relevant or urgent problem. Avoid a generic envelope that mixes unrelated types because it does not map cleanly to HTTP semantics. Use 400 Bad Request when the request itself is malformed; use 422 when validly encoded content cannot be processed.

OpenAPI 3.1 schemas

The base schema keeps all standard members optional and allows problem-specific extensions. The validation subtype fixes its identity and defines the errors extension. Use schemas to check producers; RFC 9457 still requires tolerant clients to ignore wrong-typed standard members.

openapi: 3.1.0
info:
  title: Example API
  version: 1.0.0
paths: {}
components:
  schemas:
    Problem:
      type: object
      properties:
        type:
          type: string
          format: uri-reference
          default: about:blank
        title:
          type: string
        status:
          type: integer
          minimum: 100
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri-reference
      additionalProperties: true
    ValidationProblem:
      allOf:
        - $ref: '#/components/schemas/Problem'
        - type: object
          required: [type, errors]
          properties:
            type:
              type: string
              format: uri-reference
              const: https://api.example.com/problems/validation-error
            errors:
              type: array
              minItems: 1
              items:
                type: object
                required: [detail, pointer]
                properties:
                  detail:
                    type: string
                  pointer:
                    type: string
                    format: json-pointer
                additionalProperties: false

Problem type document template

Publish a stable type URI with the type's title, recommended status, meaning, remediation, and extension schema. This template documents a type; it is not a Problem Details response body.

typeURI: https://api.example.com/problems/order-conflict
title: Order state conflict
recommendedStatus: 409
meaning: The requested transition conflicts with the current order state.
remediation: Fetch the latest order state and choose a valid transition.
extensions:
  currentState:
    type: string
    description: The order state that blocked the transition.

Keep the URI under your control and stable over time. Changing the type URI changes the problem's identity and is a breaking change for clients that branch on it. Check the IANA HTTP Problem Types registry before defining a type intended for broad reuse.

Contract-test matrix

Run these checks at the HTTP boundary. The assertion column is intentionally close to test code so the cases can be moved into the API's existing test runner.

Security and privacy

  • Do not expose stack traces, SQL, internal hostnames, secrets, or authorization rules that help an attacker.
  • Do not put personal data or sensitive identifiers in detail, extensions, or a public instance URI.
  • Protect dereferenceable instance resources with the same authorization checks as the underlying data.
  • Log private debugging details server-side and return a safe occurrence identifier to the client.

RFC 9457's security considerations specifically warn about implementation leaks, privacy risks, and disagreement between the actual response status and the advisory member.

Primary references