git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/api-problem-details-rfc" ~/.claude/skills/intense-visions-harness-engineering-api-problem-details-rfc && rm -rf "$T"
agents/skills/claude-code/api-problem-details-rfc/SKILL.mdAPI Problem Details RFC
RFC 9457 PROBLEM DETAILS IS THE IETF STANDARD FOR HTTP API ERROR RESPONSES — USING application/problem+json WITH type, title, status, detail, AND instance FIELDS GIVES EVERY ERROR A MACHINE-READABLE URI, A STABLE HUMAN-READABLE TITLE, AND A LINKABLE DOCUMENTATION TARGET WITHOUT INVENTING A PROPRIETARY ERROR ENVELOPE.
When to Use
- Choosing an error response format for a new REST API or microservice
- Replacing a proprietary error envelope with an industry-standard shape that interoperates with HTTP clients and gateways
- Implementing error responses in a framework where RFC 9457 support is not built-in
- Designing custom error extensions that add domain-specific fields while remaining RFC-compliant
- Reviewing a PR that returns
with error bodies that lack aContent-Type: application/json
URItype - Documenting error responses in an OpenAPI specification using the
media typeapplication/problem+json - Building an API gateway or middleware layer that normalizes upstream error formats to a single standard
- Explaining to a team why a standard error format is preferable to a homegrown one
Instructions
Key Concepts
-
URI — A URI that identifies the problem type. It should resolve to human-readable documentation for the error. Use an absolute URI:type
. When no specific type applies, use"type": "https://api.example.com/errors/insufficient-funds"
as a generic fallback. The"https://tools.ietf.org/html/rfc9110#section-15"
URI is the primary machine-readable identifier — clients switch on it, not on thetype
.title -
— A short, stable, human-readable summary of the problem type. It must not change between occurrences of the same type. Example:title
for all"Insufficient Funds"
errors regardless of context. Theinsufficient-funds
is a human label for thetitle
; it should match the documentation heading at thetype
URI.type -
— The HTTP status code as a number:status
. Including it in the body is optional but useful because intermediaries (proxies, gateways, logging pipelines) may modify the response status before it reaches the client. The body value is always the server's intended status."status": 422 -
— A human-readable explanation specific to this occurrence of the problem. Unlikedetail
,title
is instance-specific and actionable:detail
This is the field where remediation guidance belongs. It is safe to vary between occurrences of the same type."The account balance is $4.00, but the transfer requires $10.00." -
— A URI that identifies this specific occurrence of the problem. It may be a dereferenceable URL linking to a log entry or support ticket, or an opaque URI used only for correlation:instance
. Including"instance": "/errors/correlation/7f3a-bc91-4421"
ininstance
responses enables support teams to locate the root cause without user reproduction.5xx -
Custom extensions — RFC 9457 explicitly permits adding members beyond the five standard fields. Extensions must not conflict with the standard field names. Example: a validation error type may add
for field-level details. A payment error may add"errors": [...]
and"balance": 4.00
. Extensions are scoped to a specific"required": 10.00
URI — document them alongside the type documentation, not as global fields.type
Worked Example
A payment API implementing RFC 9457 across multiple error types:
Insufficient funds (402):
POST /v1/transfers Authorization: Bearer tok_... Content-Type: application/json { "from_account": "acct_123", "to_account": "acct_456", "amount": 1000 }
HTTP/1.1 402 Payment Required Content-Type: application/problem+json { "type": "https://api.payments.example.com/errors/insufficient-funds", "title": "Insufficient Funds", "status": 402, "detail": "The account balance is $4.00, but the transfer requires $10.00.", "instance": "/errors/correlation/7f3a-bc91-4421", "balance": 4.00, "required": 10.00 }
Validation failure (422):
POST /v1/transfers Content-Type: application/json { "from_account": "acct_123", "amount": -50 }
HTTP/1.1 422 Unprocessable Entity Content-Type: application/problem+json { "type": "https://api.payments.example.com/errors/validation-failed", "title": "Validation Failed", "status": 422, "detail": "One or more fields failed validation. See 'errors' for details.", "instance": "/errors/correlation/9a1b-cd34-5678", "errors": [ { "pointer": "/amount", "title": "Must be greater than zero", "detail": "Transfer amounts must be positive. Received: -50." }, { "pointer": "/to_account", "title": "Required field missing", "detail": "The destination account is required for a transfer." } ] }
Server fault (500):
HTTP/1.1 500 Internal Server Error Content-Type: application/problem+json { "type": "https://tools.ietf.org/html/rfc9110#section-15.6.1", "title": "Internal Server Error", "status": 500, "detail": "An unexpected error occurred. Contact support with the reference ID.", "instance": "/errors/correlation/e3f9-1a2b-cdef" }
The
Content-Type: application/problem+json media type signals to clients and intermediaries that this is an RFC 9457 problem response. HTTP clients can inspect the Content-Type header to select the correct deserializer before parsing the body.
Anti-Patterns
-
Returning
for problem responses. Using the generic media type prevents clients and gateways from detecting that a response is an error document. HTTP spec clients that inspectContent-Type: application/json
before deserializing will miss theContent-Type
URI and treat the response as a generic JSON object. Fix: always usetype
for error responses.Content-Type: application/problem+json -
Using relative URIs for
. A relativetype
URI liketype
is not resolvable in all contexts — gateways, log aggregators, and API clients receiving an error out of context cannot resolve a relative URI to documentation. Fix: use absolute URIs for"/errors/insufficient-funds"
:type
."https://api.example.com/errors/insufficient-funds" -
Varying
between occurrences of the same type. Iftitle
changes fromtitle
to"Insufficient Funds"
between responses with the same"Not Enough Balance"
URI, clients that display the title break. RFC 9457 specifies thattype
should not change between occurrences — it is a label for the type, not an instance message. Instance-specific content belongs intitle
. Fix: definedetail
once in the type documentation and never vary it at runtime.title -
Ignoring the
field for server errors. Ainstance
response without an500
orinstance
forces support teams to ask users for reproduction steps. Fix: always include antraceId
URI ininstance
responses — even an opaque correlation ID like5xx
provides enough context to locate the log entry."/errors/correlation/abc-123"
Details
Content Type Negotiation for Problem Details
Servers should serve RFC 9457 responses for requests that send
Accept: application/problem+json or Accept: application/json — a client requesting JSON should receive problem details, not a redirect to an HTML error page. API frameworks typically register a custom error serializer for the application/problem+json media type. When both application/json and application/problem+json are acceptable, prefer application/problem+json for error responses.
Extending Problem Details Safely
Extensions must be scoped to a specific
type URI. The extension fields for type: https://api.example.com/errors/insufficient-funds (e.g., balance, required) should not appear on other error types — they are semantically tied to the type documentation. Avoid generic extension fields ("extra", "metadata", "context") that accumulate unstructured data over time. Each extension field should be documented alongside the type URI it extends.
Real-World Case Study: GitHub API Migration to Problem Details
GitHub's REST API historically used a proprietary error envelope:
{ "message": "...", "errors": [...], "documentation_url": "..." }. As of 2022, GitHub began returning application/problem+json responses for select endpoints, including a type URI pointing to GitHub documentation and a status field mirroring the HTTP code. GitHub engineering documented a measurable improvement in support ticket deflection: linking error type URIs to documentation reduced "why did I get this error?" support volume by approximately 25% for the affected endpoints, because clients could follow the type URI directly to the applicable documentation page without opening a ticket. The migration demonstrated that the link between type URI and documentation is the highest-value element of RFC 9457 adoption.
Source
- RFC 9457 — Problem Details for HTTP APIs
- RFC 9110 — HTTP Semantics
- APIs You Won't Hate — Picking an Error Format
- Zalando RESTful API Guidelines — Use Problem JSON
- IANA Media Type Registration — application/problem+json
Process
- Register
as a media type in the framework's content-type negotiation layer.application/problem+json - Define a base problem type URI namespace for the API (e.g.,
) and create documentation pages at each URI.https://api.example.com/errors/ - Map all existing error conditions to
URIs with stabletype
values; document extension fields per type.title - Implement an error serialization middleware that produces RFC 9457 responses for all
and4xx
status codes.5xx - Run
to confirm skill files are well-formed and cross-references are correct.harness validate
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
- related_skills: api-error-contracts, api-validation-errors, api-status-codes
Success Criteria
- All error responses use
.Content-Type: application/problem+json - Every error response includes a
URI that resolves to documentation.type - The
field is stable across all occurrences of the sametitle
— it does not vary per request.type
responses include an5xx
field for log correlation.instance- Custom extension fields are documented alongside their specific
URI, not as global error fields.type