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
- RFC 9457: Problem Details for HTTP APIs
- RFC 7807: Problem Details for HTTP APIs (now obsolete)
- IANA HTTP Problem Types Registry
- Accept
- Content-Type
- Content negotiation
- HTTP media types
- HTTP status codes
- 429 Too Many Requests
- 422 Unprocessable Content
- 530 Site Frozen
- Retry-After
- HTTP headers