| name | error-handling-rfc9457 |
| description | Structured error responses using RFC 9457 Problem Details for HTTP APIs. Use when standardizing error responses, implementing error registries, or improving API error handling. |
| context | fork |
| agent | backend-system-architect |
| version | 1.0.0 |
| tags | error-handling, rfc9457, problem-details, fastapi, api, 2026 |
RFC 9457 Problem Details
Standardize API error responses with machine-readable problem details.
When to Use
- Standardizing error responses across APIs
- Building public APIs with clear error contracts
- Implementing error type registries
- Replacing ad-hoc error formats
- Improving client error handling
RFC 9457 vs RFC 7807
| Feature | RFC 7807 (Old) | RFC 9457 (Current) |
|---|---|---|
| Status | Obsolete | Active Standard |
| Multiple problems | Not specified | Explicitly supported |
| Error registry | No | Yes (IANA registry) |
| Extension fields | Implicit | Explicitly allowed |
Problem Details Schema
from pydantic import BaseModel, Field, HttpUrl
from typing import Any
class ProblemDetail(BaseModel):
"""RFC 9457 Problem Details for HTTP APIs."""
type: HttpUrl = Field(
default="about:blank",
description="URI identifying the problem type"
)
title: str = Field(
description="Short, human-readable summary"
)
status: int = Field(
ge=400, le=599,
description="HTTP status code"
)
detail: str | None = Field(
default=None,
description="Human-readable explanation specific to this occurrence"
)
instance: str | None = Field(
default=None,
description="URI reference identifying the specific occurrence"
)
model_config = {"extra": "allow"} # Allow extension fields
FastAPI Integration
Exception Classes
from fastapi import HTTPException
from typing import Any
class ProblemException(HTTPException):
"""Base exception for RFC 9457 problem details."""
def __init__(
self,
status_code: int,
problem_type: str,
title: str,
detail: str | None = None,
instance: str | None = None,
**extensions: Any,
):
self.problem_type = problem_type
self.title = title
self.detail = detail
self.instance = instance
self.extensions = extensions
super().__init__(status_code=status_code, detail=detail)
def to_problem_detail(self) -> dict[str, Any]:
result = {
"type": self.problem_type,
"title": self.title,
"status": self.status_code,
}
if self.detail:
result["detail"] = self.detail
if self.instance:
result["instance"] = self.instance
result.update(self.extensions)
return result
Specific Problem Types
class ValidationProblem(ProblemException):
def __init__(self, errors: list[dict], instance: str | None = None):
super().__init__(
status_code=422,
problem_type="https://api.example.com/problems/validation-error",
title="Validation Error",
detail="One or more fields failed validation",
instance=instance,
errors=errors, # Extension field
)
class NotFoundProblem(ProblemException):
def __init__(self, resource: str, resource_id: str, instance: str | None = None):
super().__init__(
status_code=404,
problem_type="https://api.example.com/problems/resource-not-found",
title="Resource Not Found",
detail=f"{resource} with ID '{resource_id}' was not found",
instance=instance,
resource=resource,
resource_id=resource_id,
)
class RateLimitProblem(ProblemException):
def __init__(self, retry_after: int, instance: str | None = None):
super().__init__(
status_code=429,
problem_type="https://api.example.com/problems/rate-limit-exceeded",
title="Too Many Requests",
detail="Rate limit exceeded. Please retry later.",
instance=instance,
retry_after=retry_after,
)
Exception Handler
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
app = FastAPI()
@app.exception_handler(ProblemException)
async def problem_exception_handler(request: Request, exc: ProblemException):
return JSONResponse(
status_code=exc.status_code,
content=exc.to_problem_detail(),
media_type="application/problem+json",
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
errors = [
{"field": ".".join(str(loc) for loc in err["loc"]), "message": err["msg"]}
for err in exc.errors()
]
problem = ValidationProblem(errors=errors, instance=str(request.url))
return JSONResponse(
status_code=422,
content=problem.to_problem_detail(),
media_type="application/problem+json",
)
@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={
"type": "https://api.example.com/problems/internal-error",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred",
"instance": str(request.url),
},
media_type="application/problem+json",
)
Usage in Endpoints
@router.get("/api/v1/analyses/{analysis_id}")
async def get_analysis(
analysis_id: str,
request: Request,
service: AnalysisService = Depends(get_analysis_service),
):
analysis = await service.get_by_id(analysis_id)
if not analysis:
raise NotFoundProblem(
resource="Analysis",
resource_id=analysis_id,
instance=str(request.url),
)
return analysis
Response Examples
404 Not Found
{
"type": "https://api.example.com/problems/resource-not-found",
"title": "Resource Not Found",
"status": 404,
"detail": "Analysis with ID 'abc123' was not found",
"instance": "/api/v1/analyses/abc123",
"resource": "Analysis",
"resource_id": "abc123"
}
422 Validation Error
{
"type": "https://api.example.com/problems/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "One or more fields failed validation",
"instance": "/api/v1/analyses",
"errors": [
{"field": "source_url", "message": "Invalid URL format"},
{"field": "depth", "message": "Must be between 1 and 3"}
]
}
429 Rate Limited
{
"type": "https://api.example.com/problems/rate-limit-exceeded",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Please retry later.",
"instance": "/api/v1/analyses",
"retry_after": 60
}
Error Type Registry
# app/core/problem_types.py
PROBLEM_TYPES = {
"validation-error": {
"uri": "https://api.example.com/problems/validation-error",
"title": "Validation Error",
"status": 422,
},
"resource-not-found": {
"uri": "https://api.example.com/problems/resource-not-found",
"title": "Resource Not Found",
"status": 404,
},
"rate-limit-exceeded": {
"uri": "https://api.example.com/problems/rate-limit-exceeded",
"title": "Too Many Requests",
"status": 429,
},
"unauthorized": {
"uri": "https://api.example.com/problems/unauthorized",
"title": "Unauthorized",
"status": 401,
},
"forbidden": {
"uri": "https://api.example.com/problems/forbidden",
"title": "Forbidden",
"status": 403,
},
"conflict": {
"uri": "https://api.example.com/problems/conflict",
"title": "Conflict",
"status": 409,
},
"internal-error": {
"uri": "https://api.example.com/problems/internal-error",
"title": "Internal Server Error",
"status": 500,
},
}
Anti-Patterns (FORBIDDEN)
# NEVER return plain text errors
return Response("Not found", status_code=404)
# NEVER use inconsistent error formats
return {"error": "Not found"} # Different from other errors
return {"message": "Validation failed", "errors": [...]}
# NEVER expose internal details in production
return {"detail": str(exc), "traceback": traceback.format_exc()}
# NEVER use generic 500 for everything
except Exception:
raise HTTPException(500, "Something went wrong")
Key Decisions
| Decision | Recommendation |
|---|---|
| Media type | application/problem+json |
| Type URI | Use your API domain + /problems/ |
| Detail | Include only for user-actionable info |
| Extensions | Use for machine-readable context |
| Logging | Log problem types for monitoring |
Related Skills
api-design-framework- REST API patternsobservability-monitoring- Error trackinginput-validation- Validation patterns
Capability Details
problem-details
Keywords: problem details, RFC 9457, RFC 7807, structured error Solves:
- How to standardize API error responses?
- What format for API errors?
fastapi-errors
Keywords: fastapi exception, error handler, HTTPException Solves:
- How to handle errors in FastAPI?
- Custom exception handlers
error-registry
Keywords: error registry, problem types, error catalog Solves:
- How to document all API errors?
- Error type management