Your API returns errors like this:
{
"error": "Something went wrong",
"code": "ERR_001"
}
Or maybe like this:
{
"success": false,
"message": "Validation failed",
"errors": ["Name is required"]
}
Or perhaps like this:
{
"status": "error",
"data": null,
"errorMessage": "Invalid request"
}
Every API invents its own error format. Clients must parse different structures for different APIs. There's no consistency.
RFC 9457 solves this. It defines a standard format for HTTP API errors that's machine-readable, extensible, and widely supported.
What Is RFC 9457?
RFC 9457 (Problem Details for HTTP APIs) is an IETF standard published in July 2023. It defines a JSON (and XML) format for describing errors in HTTP APIs.
The format includes:
- type: A URI identifying the error type
- title: A short, human-readable summary
- status: The HTTP status code
- detail: A human-readable explanation specific to this occurrence
- instance: A URI identifying the specific occurrence
Here's a basic example:
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains validation errors",
"instance": "/v1/pets"
}
This format is consistent, self-documenting, and machine-readable. Clients can parse it reliably across different APIs.
Why Use RFC 9457?
Reason 1: Standardization
When you use RFC 9457, clients know what to expect. They can write generic error handling code that works across multiple APIs.
Without a standard, every API is different:
// Custom error format A
if (response.error) {
console.error(response.error.message);
}
// Custom error format B
if (!response.success) {
console.error(response.errorMessage);
}
// Custom error format C
if (response.status === 'error') {
console.error(response.data.error);
}
With RFC 9457, error handling is consistent:
if (response.status >= 400) {
const problem = await response.json();
console.error(`${problem.title}: ${problem.detail}`);
// Machine-readable error type
if (problem.type === 'https://petstoreapi.com/errors/validation-error') {
// Handle validation errors specifically
}
}
Reason 2: Machine-Readable Error Types
The type field is a URI that uniquely identifies the error. This enables programmatic error handling.
{
"type": "https://petstoreapi.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded the rate limit of 1000 requests per hour",
"retryAfter": 3600
}
Clients can check the type and handle specific errors:
if (problem.type === 'https://petstoreapi.com/errors/rate-limit-exceeded') {
const retryAfter = problem.retryAfter || 60;
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
setTimeout(() => retryRequest(), retryAfter * 1000);
}
Reason 3: Extensibility
RFC 9457 allows custom fields. You can add context-specific information while maintaining the standard structure.
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains validation errors",
"instance": "/v1/pets",
"errors": [
{
"field": "name",
"message": "Name is required",
"code": "REQUIRED_FIELD"
},
{
"field": "age",
"message": "Age must be between 0 and 30",
"code": "OUT_OF_RANGE"
}
]
}
The standard fields (type, title, status, detail, instance) are always present. Custom fields (errors) provide additional context.
Reason 4: Tool Support
Many tools and libraries support RFC 9457:
- API gateways can transform errors into Problem Details format
- Client libraries can parse Problem Details automatically
- Monitoring tools can extract structured error information
- Documentation tools can generate error documentation from Problem Details schemas
Using a standard format makes your API easier to integrate with existing tools.
The Five Standard Fields
type (URI)
A URI that identifies the error type. This should be a stable identifier that doesn't change.
{
"type": "https://petstoreapi.com/errors/not-found"
}
The URI doesn't need to resolve to a real page, but it can. Many APIs host error documentation at these URLs:
https://petstoreapi.com/errors/not-found
→ Returns HTML documentation about 404 errors
If you don't provide a type, it defaults to about:blank, which means "the error is explained by the HTTP status code alone."
title (String)
A short, human-readable summary of the error type. This should be the same for all occurrences of this error type.
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error"
}
The title is generic. It describes the error type, not the specific occurrence.
status (Integer)
The HTTP status code. This duplicates the HTTP response status but makes the error self-contained.
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422
}
Including status in the body is useful when errors are logged, queued, or stored. You don't need to track the HTTP status separately.
detail (String)
A human-readable explanation specific to this occurrence of the error.
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The 'name' field is required but was not provided"
}
The detail is specific. It explains what went wrong in this particular request.
instance (URI)
A URI identifying the specific occurrence of the error. This is often the request path.
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The 'name' field is required but was not provided",
"instance": "/v1/pets"
}
The instance helps with debugging. When you see an error in logs, you know which endpoint caused it.
Common Error Patterns
Validation Errors
Validation errors need field-level details. Add a custom errors array:
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains 2 validation errors",
"instance": "/v1/pets",
"errors": [
{
"field": "name",
"message": "Name is required",
"code": "REQUIRED_FIELD"
},
{
"field": "age",
"message": "Age must be a positive integer",
"code": "INVALID_TYPE"
}
]
}
Clients can iterate over errors and display field-specific messages.
Not Found Errors
404 errors are straightforward:
{
"type": "https://petstoreapi.com/errors/not-found",
"title": "Not Found",
"status": 404,
"detail": "Pet with ID '019b4999-9999-9999-9999-999999999999' not found",
"instance": "/v1/pets/019b4999-9999-9999-9999-999999999999"
}
The detail includes the specific ID that wasn't found.
Authentication Errors
401 errors indicate missing or invalid authentication:
{
"type": "https://petstoreapi.com/errors/unauthorized",
"title": "Unauthorized",
"status": 401,
"detail": "Access token is missing or invalid",
"instance": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24"
}
Include the WWW-Authenticate header to tell clients how to authenticate:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="PetStore API"
Content-Type: application/problem+json
Authorization Errors
403 errors indicate insufficient permissions:
{
"type": "https://petstoreapi.com/errors/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "You don't have permission to delete this pet",
"instance": "/v1/pets/019b4132-70aa-764f-b315-e2803d882a24",
"requiredScope": "write:pets"
}
The custom requiredScope field tells clients what permission they need.
Rate Limit Errors
429 errors indicate rate limiting:
{
"type": "https://petstoreapi.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded the rate limit of 1000 requests per hour",
"instance": "/v1/pets",
"limit": 1000,
"remaining": 0,
"resetAt": "2026-03-13T12:00:00Z"
}
Include rate limit details so clients know when they can retry.
Server Errors
500 errors indicate server failures:
{
"type": "https://petstoreapi.com/errors/internal-server-error",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred while processing your request",
"instance": "/v1/pets",
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Include a traceId for debugging. Don't expose internal error details to clients.
Implementation Tips
Set the Content-Type
Use application/problem+json as the content type:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/validation-error",
...
}
This tells clients the response follows RFC 9457.
Use Consistent Error Types
Define your error types upfront and document them:
https://petstoreapi.com/errors/validation-error
https://petstoreapi.com/errors/not-found
https://petstoreapi.com/errors/unauthorized
https://petstoreapi.com/errors/forbidden
https://petstoreapi.com/errors/rate-limit-exceeded
https://petstoreapi.com/errors/internal-server-error
Don't create new error types for every situation. Reuse existing types and vary the detail field.
Document Your Error Types
Host documentation at your error type URLs:
GET https://petstoreapi.com/errors/validation-error
Returns:
<!DOCTYPE html>
<html>
<head><title>Validation Error</title></head>
<body>
<h1>Validation Error (422)</h1>
<p>This error occurs when the request body fails validation...</p>
</body>
</html>
This makes your API self-documenting.
Include Helpful Context
Add custom fields that help clients handle errors:
{
"type": "https://petstoreapi.com/errors/payment-failed",
"title": "Payment Failed",
"status": 402,
"detail": "The payment method was declined",
"instance": "/v1/orders/019b4132-70aa-764f-b315-e2803d882a24/payment",
"paymentMethod": "card_1234",
"declineCode": "insufficient_funds",
"retryable": true
}
The retryable field tells clients whether they should retry.
Don't Expose Internal Details
Never include stack traces, database queries, or internal paths in error responses:
Bad:
{
"type": "https://petstoreapi.com/errors/internal-server-error",
"title": "Internal Server Error",
"status": 500,
"detail": "NullPointerException at com.example.PetService.getPet(PetService.java:42)",
"stackTrace": "..."
}
Good:
{
"type": "https://petstoreapi.com/errors/internal-server-error",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred. Please contact support with trace ID a1b2c3d4",
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Log internal details server-side. Return only safe information to clients.
Real-World Example: Modern PetStore API
The Modern PetStore API uses RFC 9457 throughout. Here's a validation error from creating a pet:
POST /v1/pets
Content-Type: application/json
{
"name": "",
"species": "INVALID",
"age": -5
}
Response:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains 3 validation errors",
"instance": "/v1/pets",
"errors": [
{
"field": "name",
"message": "Name cannot be empty",
"code": "EMPTY_STRING"
},
{
"field": "species",
"message": "Species must be one of: DOG, CAT, BIRD, RABBIT, REPTILE, FISH, OTHER",
"code": "INVALID_ENUM"
},
{
"field": "age",
"message": "Age must be between 0 and 30",
"code": "OUT_OF_RANGE"
}
]
}
The error is structured, machine-readable, and includes all the context needed to fix the request.
Start Using RFC 9457 Today
Implementing RFC 9457 is straightforward:
- Define your error types (validation-error, not-found, unauthorized, etc.)
- Create a Problem Details response structure with the five standard fields
- Add custom fields for context-specific information
- Set Content-Type to application/problem+json
- Document your error types at their URIs
Your API will be more consistent, easier to debug, and better integrated with modern tools.
Stop inventing custom error formats. Use the standard.
Try It Yourself: The Modern PetStore API uses RFC 9457 for all errors. Explore the live API at petstoreapi