problem-details
RFC 7807 / RFC 9457 Problem Details for HTTP APIs — standard error response format, Content-Type application/problem+json, extension fields, and per-language implementation patterns.
Best use case
problem-details is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
RFC 7807 / RFC 9457 Problem Details for HTTP APIs — standard error response format, Content-Type application/problem+json, extension fields, and per-language implementation patterns.
Teams using problem-details should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/problem-details/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How problem-details Compares
| Feature / Agent | problem-details | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
RFC 7807 / RFC 9457 Problem Details for HTTP APIs — standard error response format, Content-Type application/problem+json, extension fields, and per-language implementation patterns.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Problem Details for HTTP APIs (RFC 7807 / RFC 9457)
## When to Use
Use this skill whenever you need to implement HTTP error responses. Apply it to every REST/HTTP API regardless of language or framework.
- Adding or reviewing error handling in any REST or HTTP API endpoint
- Replacing ad-hoc `{ "error": "..." }` JSON responses with the RFC 7807 standard format
- Implementing validation error aggregation using the RFC 9457 `errors` array extension
- Setting up a global error handler or middleware in Express, FastAPI, Spring Boot, or Go
- Ensuring the `Content-Type: application/problem+json` header is used consistently for all error responses
## Standard Fields
RFC 9457 defines these fields (all optional, `status` is strongly recommended):
| Field | Type | Description |
|---|---|---|
| `type` | URI string | Identifies the problem type. SHOULD be dereferenceable (docs page). Use `about:blank` when no specific type exists. |
| `title` | string | Short, human-readable summary. SHOULD NOT change between occurrences of the same type. |
| `status` | integer | HTTP status code — mirrors the response status for diagnostic convenience. |
| `detail` | string | Human-readable explanation specific to this occurrence. |
| `instance` | URI string | URI reference identifying this specific occurrence (e.g., a request ID path). |
RFC 9457 additions over RFC 7807:
- **`errors` array**: For responses that aggregate multiple sub-problems (e.g., validation errors).
- **Extension fields**: Any additional members are valid and SHOULD be documented in the problem type.
## Content-Type
```
Content-Type: application/problem+json
```
Never use `application/json` for error responses. The problem content type signals that consumers should parse the RFC 7807 fields.
## Minimal Example
```json
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/invalid-market-name",
"title": "Invalid market name",
"status": 422,
"detail": "Market name must not be blank.",
"instance": "/requests/7a8f3c12"
}
```
## Validation Error Example (RFC 9457 `errors` array)
```json
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-failed",
"title": "Validation Failed",
"status": 400,
"detail": "One or more fields failed validation.",
"instance": "/requests/abc123",
"errors": [
{ "field": "name", "detail": "must not be blank" },
{ "field": "slug", "detail": "must match ^[a-z0-9-]+$" }
]
}
```
## HTTP Status → Problem Type Mapping
| Status | Typical `type` suffix | `title` |
|---|---|---|
| 400 | `/problems/bad-request` or `/problems/validation-failed` | "Bad Request" / "Validation Failed" |
| 401 | `/problems/unauthorized` | "Unauthorized" |
| 403 | `/problems/forbidden` | "Forbidden" |
| 404 | `/problems/not-found` | "Not Found" |
| 409 | `/problems/conflict` | "Conflict" |
| 422 | `/problems/unprocessable-content` | "Unprocessable Content" |
| 429 | `/problems/too-many-requests` | "Too Many Requests" |
| 500 | `/problems/internal-server-error` | "Internal Server Error" |
Use `about:blank` as `type` when no problem-type documentation exists yet — the `title` then SHOULD match the HTTP status phrase.
---
## Implementation by Language
### Spring Boot 4 (Java/Kotlin)
Spring Boot 4.x has native RFC 7807 support — no custom classes needed.
**Enable in `application.yml`:**
```yaml
spring:
mvc:
problemdetails:
enabled: true
```
**Custom exception mapping:**
```java
@RestControllerAdvice
public class ProblemDetailsAdvice {
@ExceptionHandler(MarketNotFoundException.class)
ProblemDetail handleNotFound(MarketNotFoundException ex, HttpServletRequest req) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
pd.setType(URI.create("https://api.example.com/problems/not-found"));
pd.setTitle("Not Found");
pd.setProperty("instance", req.getRequestURI());
return pd;
}
@ExceptionHandler(ConstraintViolationException.class)
ProblemDetail handleValidation(ConstraintViolationException ex) {
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.UNPROCESSABLE_ENTITY);
pd.setType(URI.create("https://api.example.com/problems/validation-failed"));
pd.setTitle("Validation Failed");
pd.setDetail("One or more fields failed validation.");
pd.setProperty("errors", ex.getConstraintViolations().stream()
.map(v -> Map.of("field", v.getPropertyPath().toString(), "detail", v.getMessage()))
.toList());
return pd;
}
}
```
`spring.mvc.problemdetails.enabled=true` automatically maps Spring's built-in exceptions (e.g., `MethodArgumentNotValidException`, `NoResourceFoundException`) to `ProblemDetail`.
---
### TypeScript (Express + Zod)
```typescript
// src/adapter/in/http/problem.ts
export interface ProblemDetails {
type: string;
title: string;
status: number;
detail?: string;
instance?: string;
[key: string]: unknown; // extension fields
}
export function sendProblem(
res: Response,
status: number,
type: string,
title: string,
detail?: string,
extensions?: Record<string, unknown>,
): void {
const body: ProblemDetails = {
type,
title,
status,
...(detail && { detail }),
...(extensions ?? {}),
};
res.status(status).contentType("application/problem+json").json(body);
}
// Global error handler — register LAST in Express
export function problemDetailsMiddleware(
err: unknown,
req: Request,
res: Response,
_next: NextFunction,
): void {
if (err instanceof ZodError) {
return sendProblem(res, 400,
"https://api.example.com/problems/validation-failed",
"Validation Failed",
"One or more fields failed validation.",
{ errors: err.errors.map(e => ({ field: e.path.join("."), detail: e.message })) },
);
}
if (err instanceof MarketNotFoundError) {
return sendProblem(res, 404,
"https://api.example.com/problems/not-found",
"Not Found",
err.message,
);
}
// fallback
sendProblem(res, 500,
"about:blank",
"Internal Server Error",
"An unexpected error occurred.",
);
}
```
---
### Go (net/http)
```go
// internal/handler/problem.go
package handler
import (
"encoding/json"
"net/http"
)
type ProblemDetails struct {
Type string `json:"type"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail,omitempty"`
Instance string `json:"instance,omitempty"`
}
func writeProblem(w http.ResponseWriter, r *http.Request, status int, problemType, title, detail string) {
p := ProblemDetails{
Type: problemType,
Title: title,
Status: status,
Detail: detail,
Instance: r.RequestURI,
}
w.Header().Set("Content-Type", "application/problem+json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(p)
}
// Usage in handler:
func (h *MarketHandler) GetMarket(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
market, err := h.useCase.GetMarket(r.Context(), id)
if errors.Is(err, domain.ErrNotFound) {
writeProblem(w, r, http.StatusNotFound,
"https://api.example.com/problems/not-found",
"Not Found",
fmt.Sprintf("market %q not found", id),
)
return
}
if err != nil {
writeProblem(w, r, http.StatusInternalServerError,
"about:blank", "Internal Server Error", "")
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(market)
}
```
---
### Python (FastAPI)
```python
# app/adapter/in_/http/problem.py
from fastapi import Request
from fastapi.responses import JSONResponse
def problem_response(
status: int,
type_: str,
title: str,
detail: str | None = None,
instance: str | None = None,
**extensions,
) -> JSONResponse:
body = {
"type": type_,
"title": title,
"status": status,
**({"detail": detail} if detail else {}),
**({"instance": instance} if instance else {}),
**extensions,
}
return JSONResponse(content=body, status_code=status,
headers={"Content-Type": "application/problem+json"})
# Register on app startup:
def register_exception_handlers(app: FastAPI) -> None:
@app.exception_handler(MarketNotFoundError)
async def handle_not_found(request: Request, exc: MarketNotFoundError) -> JSONResponse:
return problem_response(
404,
"https://api.example.com/problems/not-found",
"Not Found",
str(exc),
instance=str(request.url),
)
@app.exception_handler(RequestValidationError)
async def handle_validation(request: Request, exc: RequestValidationError) -> JSONResponse:
return problem_response(
400,
"https://api.example.com/problems/validation-failed",
"Validation Failed",
"One or more fields failed validation.",
instance=str(request.url),
errors=[{"field": ".".join(str(l) for l in e["loc"]), "detail": e["msg"]}
for e in exc.errors()],
)
@app.exception_handler(Exception)
async def handle_generic(request: Request, exc: Exception) -> JSONResponse:
return problem_response(500, "about:blank", "Internal Server Error")
```
---
### Swift (Vapor)
```swift
// Sources/App/Middleware/ProblemDetailsMiddleware.swift
import Vapor
struct ProblemDetails: Content {
var type: String
var title: String
var status: Int
var detail: String?
var instance: String?
}
struct ProblemDetailsMiddleware: AsyncMiddleware {
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
do {
return try await next.respond(to: request)
} catch let error as MarketError {
let (status, type_, title, detail) = error.problemDetails
var headers = HTTPHeaders()
headers.contentType = HTTPMediaType(type: "application", subType: "problem+json")
let body = ProblemDetails(type: type_, title: title, status: status.code,
detail: detail, instance: request.url.string)
let res = Response(status: status, headers: headers)
try res.content.encode(body)
return res
}
}
}
extension MarketError {
var problemDetails: (HTTPStatus, String, String, String) {
switch self {
case .invalidName:
return (.unprocessableContent,
"https://api.example.com/problems/invalid-market-name",
"Invalid Market Name",
"Market name must not be blank.")
case .alreadyPublished(let slug):
return (.conflict,
"https://api.example.com/problems/already-published",
"Already Published",
"Market \(slug) is already published.")
}
}
}
```
---
## Design Rules
1. **Always use `application/problem+json`** — never `application/json` for errors.
2. **`type` SHOULD be a URI** — link to documentation. Use `about:blank` only as a last resort.
3. **`title` is stable** — same problem type → same title. Don't interpolate dynamic data into `title`; use `detail` instead.
4. **`status` mirrors HTTP status** — include it in the body for diagnostic convenience (client logs often lose the status line).
5. **Extend freely** — `errors` array for validation, `traceId` for distributed tracing, `retryAfter` for rate limiting.
6. **Don't leak internals** — `detail` is for users/clients, not stack traces. Log stack traces server-side only.Related Skills
zero-trust-patterns
Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening.
wireframing
Wireframing and prototyping workflow: fidelity levels (lo-fi sketch → mid-fi wireframe → hi-fi prototype), tool selection (Figma, Excalidraw, Balsamiq), user flow diagrams, wireframe annotation standards, information architecture (IA) mapping, and the handoff from wireframe to visual design. For developers who need to communicate UI structure before writing code.
webrtc-patterns
WebRTC patterns — peer connection setup, ICE/STUN/TURN configuration, signaling server design, SFU vs mesh topology, screen sharing, media track management, and reconnect/ICE restart handling.
webhook-patterns
Webhook patterns for receiving, verifying (HMAC), and idempotently processing third-party events. Covers Stripe, GitHub, and generic webhook patterns, delivery guarantees, retry handling, and testing.
web-performance
Web performance optimization: Core Web Vitals (LCP, CLS, INP), Lighthouse CI with budget configuration, bundle analysis (webpack-bundle-analyzer, vite-bundle-visualizer), hydration performance, network waterfall reading, image optimization (WebP/AVIF, srcset), and font performance.
wasm-performance
WebAssembly performance: wasm-opt binary optimization, size reduction (panic=abort, LTO, strip), profiling WASM in Chrome DevTools, memory management (linear memory, avoiding GC pressure), SIMD, and multi-threading with SharedArrayBuffer.
wasm-patterns
WebAssembly patterns: wasm-pack, wasm-bindgen (JS↔Wasm interop), WASI, Component Model, wasm-opt, Rust-to-WASM compilation, JS integration (web workers, streaming instantiation), and production deployment (CDN, Content-Type headers).
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
visual-identity
Brand identity development: color palette construction (primary/secondary/semantic/neutral), logo concept brief writing, typeface pairings, brand voice definition, mood board direction, and Brand Guidelines document structure. Use when establishing or evolving a visual brand — not for implementing existing tokens.
ux-micro-patterns
UX micro-patterns for every product state: Empty States, Loading States (skeleton screens, spinners, optimistic UI), Error States, Success States, Confirmation Dialogs, Onboarding Flows, and Progressive Disclosure. These patterns apply to every feature — done wrong, they're the biggest source of user confusion.
typography-design
Typography as a creative discipline: typeface selection criteria, type pairing (serif + sans, display + body), modular scale systems, line-height and tracking ratios, hierarchy construction, and web/mobile rendering considerations. The decisions behind design tokens, not the tokens themselves.
typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.