Skip to main content

Error Formats and Problem Details

Guide API consumers with clear, actionable error responses

TL;DR

Errors are inevitable. When they occur, your API response should help clients understand what went wrong and how to recover. Use RFC 7807 Problem Details format—a standardized structure with type, title, detail, status, and optionally instance fields. Include field-level validation errors. Provide actionable guidance. Never leak sensitive information. A well-designed error response is more valuable than a 200 OK with silent failures.

Learning Objectives

  • Understand standardized error response formats
  • Design field-level error information
  • Apply RFC 7807 Problem Details specification
  • Provide guidance for error recovery
  • Avoid security pitfalls in error responses

Motivating Scenario

Your API returns { "error": "Invalid request" } with HTTP 400. Your client's developer:

  • Can't determine which field caused the error
  • Doesn't know if it's a validation error or API state violation
  • Has no guidance on how to fix it
  • Spends hours debugging instead of using your API

Compare with a structured response: { "type": "https://example.com/errors/validation-failed", "title": "Validation Failed", "status": 422, "detail": "Request body validation failed", "validation_errors": [{ "field": "email", "code": "invalid-format", "message": "Email must be valid format" }] }. The client immediately knows what's wrong and fixes it.

Core Concepts

Problem Details (RFC 7807)

A standardized error response format with these fields:

  • type (URI): Machine-readable error identifier. References documentation about this error class.
  • title (string): Short, human-readable summary
  • status (number): HTTP status code (duplicates the HTTP header for convenience)
  • detail (string): Detailed explanation specific to this occurrence
  • instance (URI): Identifies the specific problem occurrence
  • Additional fields: Custom fields for your API domain

Validation Errors

Clients need to understand which fields failed validation and why:

{
"status": 422,
"type": "https://api.example.com/errors/validation-failed",
"title": "Validation Failed",
"validation_errors": [
{
"field": "email",
"code": "invalid-format",
"message": "Must be a valid email address"
},
{
"field": "age",
"code": "out-of-range",
"message": "Must be between 18 and 120"
}
}

Error Categories

Different error types require different HTTP status codes and recovery strategies. Classify errors clearly so clients can handle them appropriately.

Practical Example

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
"error": "Invalid request"
}

Problems:

  • No detail about what's invalid
  • No guidance on recovery
  • Can't distinguish field validation from request format errors
  • Hard to localize for different languages

Common Error Categories

CategoryHTTP StatusWhen to Use
Validation422Request body semantically invalid (invalid email, out of range, etc.)
Malformed400Request syntax invalid (missing JSON bracket, invalid header)
Authentication401Credentials missing or invalid
Permission403Authenticated but lacks permission
Not Found404Resource doesn't exist
Conflict409Request conflicts with resource state (already exists, optimistic lock)
Rate Limited429Too many requests
Server Error500Unexpected server condition
Unavailable503Temporarily unable to handle request (maintenance, overload)

Security Considerations

Don't leak internals: Error message "SQLException: Foreign key constraint violated" exposes your database. Say "Cannot delete this user because orders depend on it".

Don't expose stack traces: Never include stack traces in production error responses. Log them server-side.

Don't enumerate valid values: Error message "age must be one of: 18, 21, 25, 30" might enable age discrimination attacks. Say "age must be 18 or older".

Don't reveal timing information: Avoid "Email already exists" on signup if email enumeration is a concern. Say "This email cannot be used".

Do provide unique request IDs: Include instance or request_id so clients can report errors. You can debug efficiently.

Patterns and Pitfalls

Pitfall: Using generic error codes across unrelated failures. Distinguish between "validation failed," "permission denied," and "resource deleted."

Pitfall: Including stack traces or SQL errors in responses. Users shouldn't need to parse Java exceptions.

Pitfall: No field-level detail. Clients can't build good error UIs without knowing which field failed.

Pattern: Use error codes (machine-readable) and messages (human-readable). Codes enable i18n; messages provide context.

Pattern: Provide troubleshooting links. "type": "https://docs.example.com/errors/rate-limited" directs developers to solutions.

Design Review Checklist

  • All error responses use RFC 7807 structure (type, title, status, detail)
  • Validation errors include field-level information
  • Error codes documented and stable (don't change)
  • Sensitive information never leaked (stack traces, enumeration, timing)
  • Appropriate HTTP status codes used for all error scenarios
  • Transient errors include Retry-After header
  • Error type URLs link to documentation
  • Unique request IDs provided for debugging
  • Errors consistent across all endpoints
  • Client developers can build error handling without reading code

HTTP Status Codes Detailed

CodeNameWhen to UseExample
400Bad RequestRequest syntax invalid (unparseable)Invalid JSON: missing closing brace
401UnauthorizedMissing/invalid credentialsMissing Authorization header
402Payment RequiredPayment required before proceedingSubscription expired
403ForbiddenAuthenticated but lacks permissionUser can't access admin panel
404Not FoundResource doesn't existRequested user ID doesn't exist
409ConflictRequest conflicts with resource stateUpdating with wrong ETag; resource deleted
410GoneResource deliberately deleted, won't returnUser account permanently deleted
422Unprocessable EntitySemantically invalid requestEmail format invalid, required field missing
429Too Many RequestsRate limit exceeded1000 requests in 1 second
500Internal Server ErrorUnexpected error in serverNull pointer exception, database crash
502Bad GatewayUpstream service returned errorDatabase down, can't process
503Service UnavailableTemporarily unable to handle requestMaintenance, overload

400 vs 422 Distinction

400 Bad Request: Client can't even parse the request
Examples:
- Invalid JSON: { "name": "Alice" MISSING_BRACE
- Invalid Content-Type header
- Missing required header

422 Unprocessable Entity: Request parses, but data is invalid
Examples:
- Email format invalid (parsed, but not a valid email)
- Age out of range (parsed, but semantically wrong)
- Required field missing from valid JSON
- Unique constraint violated (database validation)

Best practice:
- 400: Problems with request format/protocol
- 422: Problems with request business logic/semantics

Localization and Error Codes

Error codes enable client-side localization without server translations:

// Server returns machine-readable codes
{
"status": 422,
"type": "https://api.example.com/errors/validation-failed",
"validation_errors": [
{
"field": "age",
"code": "out-of-range",
"message": "Age must be between 18 and 120"
},
{
"field": "terms",
"code": "required",
"message": "Must accept terms of service"
}
]
}

// Client translates code to user's language
const ERROR_MESSAGES = {
en: {
'out-of-range': 'Value is outside acceptable range',
'required': 'This field is required',
'invalid-format': 'Please use correct format',
},
es: {
'out-of-range': 'El valor está fuera del rango aceptable',
'required': 'Este campo es obligatorio',
'invalid-format': 'Por favor, utiliza el formato correcto',
},
fr: {
'out-of-range': 'La valeur est en dehors de la plage acceptable',
'required': 'Ce champ est obligatoire',
'invalid-format': 'Veuillez utiliser le format correct',
}
}

// Client application
const userLanguage = getUserLanguage(); // 'es'
const errorMessage = ERROR_MESSAGES[userLanguage][fieldError.code];
// Shows Spanish error to Spanish-speaking user

Authentication Error Handling

// Common authentication errors with proper guidance

// 1. Missing credentials
{
"status": 401,
"type": "https://api.example.com/errors/missing-credentials",
"title": "Unauthorized",
"detail": "Request is missing Authorization header"
}

// 2. Invalid token
{
"status": 401,
"type": "https://api.example.com/errors/invalid-token",
"title": "Unauthorized",
"detail": "Token has expired or is invalid",
"hint": "Refresh token using /auth/refresh endpoint"
}

// 3. Token format invalid
{
"status": 401,
"type": "https://api.example.com/errors/invalid-token-format",
"title": "Unauthorized",
"detail": "Authorization header must be: Bearer <token>",
"example": "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

// 4. MFA required
{
"status": 401,
"type": "https://api.example.com/errors/mfa-required",
"title": "Multi-Factor Authentication Required",
"detail": "Please complete MFA verification",
"mfa_endpoint": "POST /auth/mfa/verify",
"challenge_id": "challenge_abc123"
}

// 5. Insufficient permissions
{
"status": 403,
"type": "https://api.example.com/errors/insufficient-permissions",
"title": "Forbidden",
"detail": "User lacks required permissions",
"required_permissions": ["documents:write"],
"user_permissions": ["documents:read"]
}

Pagination Error Handling

{
"status": 400,
"type": "https://api.example.com/errors/invalid-pagination",
"title": "Invalid Pagination Parameters",
"detail": "Pagination parameters out of range",
"errors": [
{
"field": "page",
"code": "out-of-range",
"message": "page must be >= 1, got 0"
},
{
"field": "limit",
"code": "out-of-range",
"message": "limit must be <= 100, got 1000"
}
],
"valid_ranges": {
"page": { "minimum": 1, "maximum": null },
"limit": { "minimum": 1, "maximum": 100 }
}
}

Retry Strategy Based on Error Type

def should_retry(status_code):
"""Determine if request should be retried."""
# 4xx errors (client fault): don't retry
if 400 <= status_code < 500:
if status_code in [408, 429]: # Timeout, rate limit: retry
return True
return False # Other 4xx: client's problem

# 5xx errors (server fault): retry with backoff
if status_code >= 500:
return True

# 2xx success: no retry
return False

def calculate_backoff(attempt, base_delay=1, max_delay=32):
"""Exponential backoff with jitter."""
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, 0.1 * delay) # 0-10% jitter
return delay + jitter

# Client retry loop
for attempt in range(MAX_RETRIES):
try:
response = make_request(...)
if response.ok:
return response

if not should_retry(response.status_code):
raise ApiError(response) # Don't retry

delay = calculate_backoff(attempt)
time.sleep(delay)

except TimeoutError:
if attempt < MAX_RETRIES - 1:
delay = calculate_backoff(attempt)
time.sleep(delay)
else:
raise

raise ApiError("Max retries exceeded")

Error Metrics and Monitoring

# Track error types to identify patterns
error_metrics = {
"401_unauthorized": 1523, # Auth failures
"403_forbidden": 45, # Permission denied
"422_validation": 3421, # Bad input
"429_rate_limited": 234, # Rate limit hits
"500_internal": 12, # Server errors
"503_unavailable": 8, # Service down
}

# Analysis
most_common = max(error_metrics, key=error_metrics.get)
# "422_validation": Most common = users making mistakes
# Action: Improve documentation on field requirements

rate_limit_ratio = error_metrics["429_rate_limited"] / sum(error_metrics.values())
# If > 5%, API clients hitting limits frequently
# Action: Increase rate limit or help clients optimize requests

server_error_ratio = error_metrics["500_internal"] / sum(error_metrics.values())
# If > 0.1%, server reliability issue
# Action: Investigate and fix server-side bugs

Self-Check

  • What HTTP status code should validation errors use? Why not 400?
    • Answer: 422 Unprocessable Entity. 400 is for unparseable requests; 422 is for semantically invalid requests.
  • What information should validation_errors include to help developers?
    • Answer: field name, error code (machine-readable), message (human-readable), and optionally valid ranges or examples.
  • When would you include a conflicting_resources field in an error response?
    • Answer: In 409 Conflict responses when the conflict is due to related resources (e.g., can't delete user with active orders).
  • How should you handle sensitive information in error messages?
    • Answer: Never leak internals (SQL errors, stack traces). Use user-friendly messages instead.
  • What's the relationship between error codes and client-side i18n?
    • Answer: Codes enable clients to translate locally; servers don't need to handle all languages.
One Takeaway

An error response should guide clients toward recovery, not require them to guess what went wrong. Use RFC 7807 Problem Details, include field-level errors, provide actionable guidance, and never leak sensitive information. Good error design reduces support load and improves user experience.

Next Steps

  • Read Versioning Strategies for handling error format evolution
  • Study API Security for handling sensitive error scenarios
  • Explore Concurrency Control (ETags) for conflict error patterns

References

  • RFC 7807 Problem Details for HTTP APIs
  • JSON:API Error Objects
  • OpenAPI 3.0 Error Response Schema
  • Error Handling Best Practices (various API providers)