Machine-Readable Error Responses for AI Agents (RFC 9457)¶
Request structured errors from HTTP APIs using
Acceptheaders — and emit them from your own agent-facing services — to replace brittle HTML parsing with deterministic control flow.
The Problem: Errors Designed for Humans¶
When an agent calls an HTTP API and receives an error, it typically gets an HTML page designed for a browser. A Cloudflare 1015 rate-limit page is ~14,252 tokens as HTML. The agent must pattern-match through markup to extract the status, then guess at retry behaviour from heuristics. At scale, this burns context budget and produces unreliable recovery logic.
The same information as Markdown is 221 tokens (~64.5x reduction). As RFC 9457 JSON: 256 tokens (~55.7x).
RFC 9457: The Standard¶
RFC 9457 defines application/problem+json — a standard structure for HTTP error details:
{
"type": "https://errors.example.com/rate-limit",
"status": 429,
"title": "Rate Limit Exceeded",
"detail": "You have exceeded the 100 req/min limit for this endpoint.",
"instance": "/api/v1/completions/req-abc123"
}
Five base fields: type, status, title, detail, instance. Servers may add extension fields for operational metadata.
Operational Extension Fields (Cloudflare Pattern)¶
Cloudflare's agent-facing error implementation adds extension fields that map directly to agent control-flow branches:
| Field | Type | Purpose |
|---|---|---|
retryable |
boolean | Whether retrying the same request can succeed |
retry_after |
integer (seconds) | Minimum wait before retrying |
owner_action_required |
boolean | Whether a human must intervene |
error_code |
string | Machine-readable code (e.g., 1015) |
error_category |
string | Broad category for routing logic |
Error Category Taxonomy¶
Five error category groups map to three agent actions:
graph TD
E[Error Response] --> cat{error_category}
cat --> RL[rate_limit]
cat --> AD[access_denied]
cat --> C[config / tls / dns]
cat --> W[worker / snippet / rewrite]
cat --> L[legal / unsupported]
RL -->|retryable: true| R[Retry with backoff]
AD -->|owner_action_required: true| H[Escalate to human]
C -->|retryable: false| F[Fail fast, surface error]
W --> F
L --> F
The agent branches on explicit signals rather than inferring intent from status codes or HTML content.
Requesting Structured Errors¶
Set the Accept header on outbound API calls:
Accept: application/problem+json
For a token-efficient prose format with YAML frontmatter (no Content-Type negotiation overhead):
Accept: text/markdown
The text/markdown response embeds operational fields in YAML frontmatter, costs the same as a standard request, and is already supported by Cloudflare's edge.
Example Markdown response for a rate-limit error:
---
retryable: true
retry_after: 60
error_code: "1015"
error_category: rate_limit
owner_action_required: false
---
# Rate Limited
You have been rate limited. Wait 60 seconds before retrying.
Integration with Claude Tool Results¶
Claude's tool-use API forwards structured error content to the model via is_error: true in tool_result blocks:
{
"type": "tool_result",
"tool_use_id": "toolu_abc123",
"is_error": true,
"content": [
{
"type": "text",
"text": "{\"retryable\": true, \"retry_after\": 60, \"error_category\": \"rate_limit\", \"title\": \"Rate Limit Exceeded\"}"
}
]
}
Claude receives the structured fields and can branch deterministically: if retryable is true and retry_after is set, wait and retry; if owner_action_required is true, surface to the user; otherwise fail fast.
Emitting RFC 9457 Errors from Agent-Facing Services¶
When building services that agents will call, emit RFC 9457 responses rather than generic HTTP errors:
from flask import jsonify
def rate_limit_error(retry_after: int):
return jsonify({
"type": "https://api.example.com/errors/rate-limit",
"status": 429,
"title": "Rate Limit Exceeded",
"detail": f"Retry after {retry_after} seconds.",
"retryable": True,
"retry_after": retry_after,
"error_category": "rate_limit",
"owner_action_required": False,
}), 429, {"Content-Type": "application/problem+json"}
This pattern connects three cost and reliability concerns under a single design decision: token spend, retry waste, and escalation routing.
When This Backfires¶
RFC 9457 adoption is uneven. Most third-party APIs do not support application/problem+json and silently ignore the Accept header, returning HTML regardless. Three conditions make this pattern unreliable:
- Third-party APIs without RFC 9457 support — the agent still receives an HTML error body. Parse defensively: always attempt structured extraction first, then fall back to plain-text error extraction.
- Middleware that rewrites Accept headers — some proxies, API gateways, or WAFs strip or replace
Acceptbefore the request reaches the origin. Verify that theAcceptheader survives the full request path. - First-party services that haven't adopted the format — emitting RFC 9457 requires active implementation work. The pattern pays off once agents make enough API calls to justify the engineering cost; for low-volume integrations, generic error handling may be sufficient.
Key Takeaways¶
- Request structured errors by setting
Accept: application/problem+jsonorAccept: text/markdownon outbound API calls. - RFC 9457 defines five base fields —
type,status,title,detail,instance— that any compliant server returns. - Cloudflare's extension fields (
retryable,retry_after,owner_action_required,error_code,error_category) map errors directly to agent control-flow branches. - Emit RFC 9457 responses from first-party agent-facing services so agents can replace HTML-parsing heuristics with explicit field checks.
- Parse defensively: many third-party APIs ignore the
Acceptheader, and middleware can strip it before the request reaches the origin.