Problem Details for HTTP APIs

Problem details is a standard format for machine-readable error information in HTTP API responses. Instead of returning a plain status code or an HTML error page, an API sends a structured JSON or XML object describing the error type, the HTTP status, a human-readable summary, and details specific to the occurrence.

The format defines two media types: application/problem+json for JSON serialization and application/problem+xml for XML. JSON is the dominant format in practice.

Usage

HTTP status codes carry limited information. A 403 tells the client access is denied but does not explain why. Missing Authentication, an expired token, an IP block, and an account suspension all produce the same status code. Error bodies historically varied between APIs, forcing clients to write custom parsing logic for each service.

Problem details solve this by defining a consistent envelope for error responses. A single parser handles errors from any API using the format. Five standard members cover the fields every error response needs, and extension members allow APIs to include domain-specific data like retry timers, request IDs, or validation errors.

Adoption is broad. Spring Framework and ASP.NET Core include native support. Cloudflare returns problem details to AI agents and API clients across the entire network. The Zalando RESTful API Guidelines mandate the format for all error responses. Libraries exist for Python, Node.js, Go, PHP, Ruby, Rust, and Kotlin.

AI agents and error handling

When an AI agent receives an HTML error page, the entire page consumes thousands of tokens to parse. A problem details response conveys the same information in a few hundred bytes, reducing token consumption by over 95%. Agents requesting Accept: application/problem+json receive structured, actionable error data instead.

Members

Five standard members form the core of every problem details object. All five are optional. APIs include whichever members apply to the error at hand.

type

A URI reference identifying the problem category. The type member acts as a stable identifier for the class of error, not the specific occurrence. When the URI is web-locatable, the target document provides human-readable documentation about the problem.

When absent or set to "about:blank", the problem has no additional semantics beyond the HTTP status code. The title member mirrors the status code reason phrase in this case.

Absolute URIs are recommended. Relative URIs resolve per standard URI resolution rules. Non-resolvable URI schemes (like tag:) are permitted for cases where dereferenceable documentation is impractical.

"type": "https://api.example.re/errors/rate-limit"

Cloudflare uses dereferenceable documentation URLs as type values, pointing to the specific error code reference page:

"type": "https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1015/"

status

The HTTP status code for the error, expressed as an integer in the 100-599 range. This member is advisory. The actual HTTP status code in the response header is the authoritative value. Including status in the body makes the problem details object self-contained when logged, queued, or forwarded through systems where the HTTP status line is lost.

Generators must use the same status code in both the HTTP response header and the status member. A mismatch between the two indicates an intermediary (proxy, load balancer, or firewall) modified the response in transit.

"status": 429

title

A short, human-readable summary of the problem type. The title value stays consistent across occurrences of the same problem type. Clients display the title as a label, not as a detailed explanation. The title changes only for localization purposes, driven by Accept-Language content negotiation.

"title": "Rate limit exceeded"

detail

A human-readable explanation specific to this occurrence. Unlike title, the detail value changes per occurrence and describes what went wrong in this particular request. The value targets human readers. Clients are not expected to parse detail for machine-readable information. Machine-readable data belongs in extension members.

"detail": "Request quota of 1000/hour exhausted. Resets at 2026-03-11T15:00:00Z."

Security

The detail member and extension members carry information visible to the client. Stack traces, internal hostnames, database error messages, and filesystem paths expose implementation details. Problem details responses limit their content to information the client needs to act on the error, not debug the server.

instance

A URI reference identifying the specific occurrence. The instance member enables correlation between the error response and server-side logs or tracing systems. The URI is either dereferenceable (returning additional details about the occurrence) or opaque (serving as a unique identifier only). When dereferenceable, the endpoint implements the same access controls as any other API endpoint.

"instance": "/logs/errors/abc123-def456"

Cloudflare sets instance to the Ray ID, connecting the problem details response to the specific request trace:

"instance": "9d99a4434fz2d168"

Extension members

APIs add custom members beyond the five standard fields to carry domain-specific data. Clients ignore unrecognized extensions, allowing problem types to add fields over time without breaking existing consumers.

Extension member names conventionally start with a letter and consist of alphanumeric characters plus underscores, with a minimum length of three characters. These are recommendations, not strict requirements. For XML serialization compatibility, names also conform to the XML Name production rule.

Common patterns

Extension Purpose
retryable Whether the request is safe to retry
retry_after Seconds to wait before retrying
errors Array of field-level validation failures
trace_id Distributed tracing identifier
error_code Vendor-specific numeric error code
balance Remaining quota or account balance

Validation errors commonly use an errors array where each entry identifies the invalid field and a human-readable message:

{
  "type": "https://api.example.re/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "Two fields failed validation.",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    },
    {
      "field": "age",
      "message": "Must be a positive integer"
    }
  ]
}

An account balance problem using extensions to communicate both the current balance and the cost of the attempted operation:

{
  "type": "https://api.example.re/errors/out-of-credit",
  "title": "Insufficient credit",
  "status": 403,
  "detail": "Account balance is 30, cost is 50.",
  "instance": "/account/12345/msgs/abc",
  "balance": 30,
  "accounts": [
    "/account/12345",
    "/account/67890"
  ]
}

Extension compatibility

Clients must handle unknown extensions gracefully. A client written for one API encounters different extensions when calling another. Ignoring unrecognized members is the correct behavior. Extensions are additive and never change the meaning of the five standard members.

The about:blank default

When the type member is absent or set to "about:blank", the problem type carries no additional semantics beyond the HTTP status code. The title member mirrors the standard HTTP reason phrase for the given status code (e.g., "Not Found" for 404, "Too Many Requests" for 429).

The about:blank default is useful for APIs returning generic HTTP errors without defining custom problem types. A minimal problem details response for an unauthorized request:

{
  "type": "about:blank",
  "title": "Unauthorized",
  "status": 401
}

This is functionally equivalent to omitting the type member entirely. The IANA Problem Types Registry lists about:blank with the title "See HTTP Status Code" and no recommended HTTP status code. ASP.NET Core and Spring Boot both default to about:blank when generating problem details from built-in exceptions.

Content negotiation

Clients signal preference for problem details using the Accept header. An API client requesting JSON error responses sends:

Accept: application/problem+json

The server returns problem details for error responses and the regular media type for success responses. This pattern integrates naturally with content negotiation, where the Accept header already drives format selection for successful responses. Problem details extend the same mechanism to errors.

Both application/problem+json and application/json are valid Accept values for requesting JSON problem details. The application/problem+json type is more specific and signals explicit awareness of the format. The +json structured syntax suffix means any JSON parser handles the response body even without understanding the problem details semantics.

XML serialization

The XML serialization uses application/problem+xml with the XML namespace urn:ietf:rfc:7807 (preserved from the earlier specification for backward compatibility). Each standard member maps to an XML element of the same name. Extension members map to additional elements within the same namespace. Extension arrays use <i> child elements.

<?xml version="1.0" encoding="UTF-8"?>
<problem xmlns="urn:ietf:rfc:7807">
  <type>https://api.example.re/errors/out-of-credit</type>
  <title>Insufficient credit</title>
  <status>403</status>
  <detail>Account balance is 30, cost is 50.</detail>
  <instance>/account/12345/msgs/abc</instance>
  <balance>30</balance>
  <accounts>
    <i>https://api.example.re/account/12345</i>
    <i>https://api.example.re/account/67890</i>
  </accounts>
</problem>

JSON is the dominant serialization on the web. XML serialization is more common in enterprise and SOAP-adjacent environments.

Cloudflare error responses

Cloudflare uses content negotiation to serve structured error responses to API clients and AI agents while continuing to serve HTML error pages to browsers. The Accept header determines the format:

Accept value Response format
application/problem+json Problem details JSON
application/json JSON
text/markdown Markdown with YAML frontmatter
text/html or */* HTML error page (default)

A wildcard */* alone always returns HTML, maintaining backward compatibility with browsers. Agents must explicitly request a structured format.

When multiple types appear in the Accept header, quality values control priority. At equal priority, the first listed type wins:

Accept: application/problem+json, text/markdown;q=0.9

This request prefers problem details JSON, with Markdown as a fallback.

Vary header

Servers returning different error formats based on the Accept header include Vary: Accept in the response. This ensures caches store separate entries for HTML and structured error responses.

Framework support

The format has native support in the two largest server-side web frameworks and library support across every major language.

Spring Framework

Spring Framework 6.0 and Spring Boot 3.0 introduced the ProblemDetail class. All built-in Spring MVC exceptions implement the ErrorResponse interface, producing problem details responses automatically. Enabling the feature in Spring Boot:

spring.mvc.problemdetails.enabled=true

The ResponseEntityExceptionHandler base class maps all Spring MVC exceptions to problem details with application/problem+json as the response content type. Custom exceptions extend ErrorResponseException to carry domain-specific problem types and extensions. The framework supports internationalization of title and detail through MessageSource integration.

ASP.NET Core

ASP.NET Core provides the ProblemDetails and ValidationProblemDetails classes. The AddProblemDetails() service registration enables automatic problem details generation across exception handling, status code pages, and developer exception page middleware.

builder.Services.AddProblemDetails();
app.UseExceptionHandler();
app.UseStatusCodePages();

The IProblemDetailsService and ProblemDetailsFactory allow customization of the generated responses, including adding extension members and custom type URIs. The [ApiController] attribute enables automatic problem details for error status codes.

Other frameworks

Framework Language Support type
Quarkus Java quarkus-resteasy-problem extension
Micronaut Java micronaut-problem-json module
Huma Go Built-in ErrorModel struct
NestJS TypeScript nest-problem-details filter
Rails Ruby problem_details-rails gem
Symfony PHP problem-details-symfony-bundle
FastAPI Python fastapi-problem-details plugin
Axum Rust problem_details crate
Ktor Kotlin kotlin-rfc9457-problem-details

FastAPI

FastAPI has no native support yet. The fastapi-problem-details plugin provides problem details formatting as middleware, converting validation errors, HTTP exceptions, and unhandled exceptions to problem details responses.

Real-world adoption

Cloudflare agent error pages

Cloudflare returns problem details for all 1XXX error codes when the request includes Accept: application/problem+json or Accept: application/json. The implementation maps Cloudflare's internal error codes to standardized problem details with custom extensions for automated error handling.

The 530 status code demonstrates the pattern. Cloudflare pairs a 530 with a secondary 1XXX error code in the HTML response body. The secondary code is necessary because the HTTP status code alone does not identify the specific problem. Problem details replace this pattern with structured data. The error_code extension carries the same 1XXX value in a machine-readable field.

Cloudflare groups all 1XXX errors into ten categories:

Category Description Agent action
access_denied IP, ASN, geo, or firewall block Escalate to site owner
rate_limit Request rate exceeded Back off per retry_after
dns Origin DNS resolution failure Escalate to site owner
config CNAME, tunnel, or host routing error Escalate to site owner
tls TLS version or cipher mismatch Adjust TLS settings
legal DMCA or regulatory block Do not retry
worker Cloudflare Workers runtime error Escalate to site owner
rewrite Invalid URL rewrite output Escalate to site owner
snippet Cloudflare Snippets config error Escalate to site owner
unsupported Unsupported method or feature Change the request

Each category signals whether the agent is expected to retry, change the request, or escalate to the site owner. The retryable and owner_action_required extension fields encode this logic directly in the response.

Size comparison for error 1015 (rate limiting):

Format Bytes Tokens
HTML 46,645 14,252
Markdown 798 221
JSON 970 256

The structured formats reduce payload size by as much as 98% compared to the HTML error page.

Zalando RESTful API Guidelines

Zalando's RESTful API Guidelines mandate problem details for all error responses across the organization. Every API endpoint returns application/problem+json for 4xx and 5xx responses. Zalando extends the standard with a flow ID extension for distributed tracing across microservices. Notably, Zalando avoids making type a resolvable URI, preferring OpenAPI documentation to define problem types instead.

IANA Problem Types Registry

IANA maintains a registry of common problem type URIs available for reuse across APIs. The registry uses a Specification Required registration policy, meaning each entry references a stable, freely available specification. Vendor-specific and deployment-specific values are not eligible.

The registry currently contains three entries:

Type URI Title Status
about:blank See HTTP Status Code N/A
...#date Date Not Acceptable 400
...#ohttp-key Oblivious HTTP key configuration not acceptable 400

The small number of registered types reflects the reality of API error handling. Most problem types are specific to individual APIs or organizations. The registry targets problems common across multiple independent specifications.

History

The original specification defining problem details was published in 2016, establishing the application/problem+json and application/problem+xml media types along with the five standard members. The 2023 revision obsoleted the original with three additions:

  • An IANA Problem Types Registry for common, reusable problem type URIs
  • Guidance for handling multiple problems of different types in a single response (recommend the most relevant or urgent)
  • Guidance for using non-dereferenceable type URIs (e.g., tag: scheme URIs)

The revision introduced no breaking changes. The XML namespace (urn:ietf:rfc:7807) was deliberately preserved for backward compatibility. Existing implementations continue to work without modification.

Example

API validation error

A POST request to an API endpoint fails input validation. The server returns 422 Unprocessable Content with problem details describing each invalid field.

Request

POST /api/users HTTP/1.1
Host: api.example.re
Content-Type: application/json
Accept: application/problem+json

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

Response

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
  "type": "https://api.example.re/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "Two fields failed validation.",
  "instance": "/logs/errors/7f8a9b0c",
  "errors": [
    {
      "field": "email",
      "message": "Invalid email format"
    },
    {
      "field": "age",
      "message": "Must be a positive integer"
    }
  ]
}

The type URI identifies this as a validation error. The errors extension array lists each invalid field with a specific message, enabling the client to highlight individual form fields.

Rate limiting

A client exceeds the API rate limit. The server returns 429 Too Many Requests with extension members indicating retry timing.

Request

GET /api/data HTTP/1.1
Host: api.example.re
Accept: application/problem+json

Response

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 30
{
  "type": "https://api.example.re/errors/rate-limit",
  "title": "Rate limit exceeded",
  "status": 429,
  "detail": "Request quota of 1000/hour exhausted.",
  "retryable": true,
  "retry_after": 30
}

The retryable extension tells the client the request is safe to retry. The retry_after value mirrors the Retry-After response header, making the problem details object self-contained for logging and asynchronous processing.

Cloudflare structured error response

A rate-limited request to a Cloudflare-proxied site. The agent requests problem details via the Accept header and receives a structured response with Cloudflare-specific extensions.

Request

GET /api/data HTTP/1.1
Host: www.example.re
Accept: application/problem+json

Response

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json; charset=utf-8
CF-RAY: 9d99a4434fz2d168-MIA
{
  "type": "https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1015/",
  "title": "Error 1015: You are being rate limited",
  "status": 429,
  "detail": "You are being rate-limited by the website owner's configuration.",
  "instance": "9d99a4434fz2d168",
  "error_code": 1015,
  "error_name": "rate_limited",
  "error_category": "rate_limit",
  "ray_id": "9d99a4434fz2d168",
  "timestamp": "2026-03-11T14:30:00Z",
  "zone": "example.re",
  "cloudflare_error": true,
  "retryable": true,
  "retry_after": 30,
  "owner_action_required": false
}

The error_code extension carries the Cloudflare 1XXX code. The error_category classifies the error for automated triage. The owner_action_required flag tells the agent whether the issue requires site owner intervention or is transient.

Cloudflare 1XXX errors and 530

Cloudflare's HTML error pages pair an HTTP status code with a secondary 1XXX error code (e.g., 530 / Error 1016 for DNS resolution failures). Problem details replace this ad-hoc pattern with structured data, carrying the same 1XXX value in the error_code extension field.

Cloudflare Markdown error response

Cloudflare also serves structured errors as Markdown with YAML frontmatter when the agent sends Accept: text/markdown. The frontmatter carries the same extension fields as the JSON response, and the prose sections provide human-readable context.

Request

GET /api/data HTTP/1.1
Host: www.example.re
Accept: text/markdown

Response

HTTP/1.1 429 Too Many Requests
Content-Type: text/markdown; charset=utf-8
CF-RAY: 9d99a4434fz2d168-MIA
---
error_code: 1015
error_name: rate_limited
error_category: rate_limit
status: 429
ray_id: 9d99a4434fz2d168
timestamp: 2026-03-11T14:30:00Z
zone: example.re
cloudflare_error: true
retryable: true
retry_after: 30
owner_action_required: false
---

# Error 1015: You are being rate limited

## What Happened
You are being rate-limited by the website
owner's configuration.

## What You Should Do
**Wait and retry.** This block is transient.
Wait at least 30 seconds, then retry with
exponential backoff.

The YAML frontmatter is machine-parseable. The prose sections (What Happened, What You Should Do) provide actionable guidance. Agents parse the frontmatter for retry logic and fall back to the prose sections when encountering unfamiliar fields.

Minimal about:blank response

A server returns a minimal problem details response using the about:blank default when no custom problem type applies to the error.

Response

HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404
}

The about:blank type signals no additional semantics beyond the 404 status code. The title mirrors the standard HTTP reason phrase.

Takeaway

Problem details standardize the format for HTTP API error responses. The five core members (type, status, title, detail, instance) provide a consistent structure, and extension members add domain-specific data like retry timers, validation errors, and vendor-specific error codes. The application/problem+json media type integrates with content negotiation through the Accept header, enabling clients to request structured errors instead of HTML error pages. Native support in Spring Framework and ASP.NET Core, combined with library support across every major language, makes problem details the standard error format for HTTP APIs.

See also

Last updated: March 11, 2026