Skip to main content
Technology & EngineeringApi Design168 lines

API Error Handling

Error response design and HTTP status code conventions for consistent, actionable API error reporting

Quick Summary11 lines
You are an expert in error response design for designing robust APIs.

## Key Points

- Use a consistent error envelope (`{"error": {...}}`) across every endpoint so consumers can write a single error-handling path.
- Include a machine-readable `code` string (not just the HTTP status) so clients can branch on specific error conditions.
- Attach a `request_id` to every response so support teams can correlate client reports with server logs.
- Returning 200 with an error message in the body, which breaks standard HTTP client error detection.
- Leaking stack traces, SQL queries, or internal file paths in production error responses.
skilldb get api-design-skills/API Error HandlingFull skill: 168 lines
Paste into your CLAUDE.md or agent config

API Error Handling — API Design

You are an expert in error response design for designing robust APIs.

Core Philosophy

Overview

Consistent, informative error responses are critical to API usability. A well-designed error format tells the consumer what went wrong, why, and how to fix it — without leaking internal implementation details.

Core Concepts

HTTP Status Code Categories

RangeMeaningResponsibility
2xxSuccessRequest was processed correctly
3xxRedirectionClient should follow the redirect
4xxClient errorThe request is malformed or unauthorized
5xxServer errorThe server failed to fulfill a valid request

Commonly Used Status Codes

200 OK              — Successful read or update
201 Created         — Resource successfully created
204 No Content      — Successful delete, no body
400 Bad Request     — Malformed syntax or invalid parameters
401 Unauthorized    — Missing or invalid authentication
403 Forbidden       — Authenticated but insufficient permissions
404 Not Found       — Resource does not exist
409 Conflict        — State conflict (e.g., duplicate creation)
422 Unprocessable   — Valid syntax but semantic errors (validation)
429 Too Many Reqs   — Rate limit exceeded
500 Internal Error  — Unexpected server failure
503 Service Unavail — Temporary downtime or overload

Standard Error Response Format

{
  "error": {
    "code": "validation_error",
    "message": "The request body contains invalid fields.",
    "details": [
      {
        "field": "email",
        "issue": "must be a valid email address",
        "value": "not-an-email"
      },
      {
        "field": "age",
        "issue": "must be a positive integer",
        "value": -5
      }
    ],
    "request_id": "req_abc123",
    "doc_url": "https://api.example.com/docs/errors#validation_error"
  }
}

Implementation Patterns

Centralized Error Handler

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

class APIError(Exception):
    def __init__(self, code: str, message: str, status: int = 400, details=None):
        self.code = code
        self.message = message
        self.status = status
        self.details = details or []

@app.exception_handler(APIError)
async def api_error_handler(request: Request, exc: APIError):
    return JSONResponse(
        status_code=exc.status,
        content={
            "error": {
                "code": exc.code,
                "message": exc.message,
                "details": exc.details,
                "request_id": request.state.request_id,
            }
        },
    )

@app.exception_handler(Exception)
async def unhandled_error_handler(request: Request, exc: Exception):
    # Log the full traceback internally, return a generic message
    logger.exception("Unhandled error", request_id=request.state.request_id)
    return JSONResponse(
        status_code=500,
        content={
            "error": {
                "code": "internal_error",
                "message": "An unexpected error occurred.",
                "request_id": request.state.request_id,
            }
        },
    )

Validation Error Aggregation

Return all validation errors at once so the client can fix them in a single retry.

def validate_create_user(data):
    errors = []
    if not data.get("name"):
        errors.append({"field": "name", "issue": "is required"})
    if "@" not in data.get("email", ""):
        errors.append({"field": "email", "issue": "must be a valid email address"})
    if errors:
        raise APIError("validation_error", "Invalid input.", status=422, details=errors)

Retry-After for Rate Limiting

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 30 seconds."
  }
}

Best Practices

  • Use a consistent error envelope ({"error": {...}}) across every endpoint so consumers can write a single error-handling path.
  • Include a machine-readable code string (not just the HTTP status) so clients can branch on specific error conditions.
  • Attach a request_id to every response so support teams can correlate client reports with server logs.

Common Pitfalls

  • Returning 200 with an error message in the body, which breaks standard HTTP client error detection.
  • Leaking stack traces, SQL queries, or internal file paths in production error responses.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add api-design-skills

Get CLI access →