Python API Error Handling Standards — Core Concepts

Why error handling standards matter

An API without error standards is an API that generates support tickets. When every endpoint returns errors differently — sometimes a string, sometimes nested JSON, sometimes just a status code — client developers waste hours reverse-engineering failure modes instead of handling them.

Consistent error handling reduces integration time, improves debugging speed, and makes automated retry logic possible.

The building blocks of a good error response

Every error response should include:

  • HTTP status code — The right category (4xx for client mistakes, 5xx for server failures). Never return 200 with an error body.
  • Error type or code — A machine-readable identifier like VALIDATION_ERROR or RESOURCE_NOT_FOUND. Clients use this for branching logic.
  • Human-readable message — What went wrong in plain language. This is for developers reading logs, not for end users.
  • Details — Specific field-level errors, constraint violations, or contextual information.
  • Request ID — A correlation identifier so support teams can trace the exact request in logs.

RFC 7807: Problem Details for HTTP APIs

RFC 7807 (updated by RFC 9457) defines a standard JSON structure for API errors:

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request body contains invalid fields",
  "instance": "/users/signup",
  "errors": [
    {"field": "email", "message": "Not a valid email address"},
    {"field": "age", "message": "Must be 18 or older"}
  ]
}

The type field is a URI that can point to documentation. The title is a short summary. The detail provides specifics. This format is gaining adoption across the industry because it gives clients a predictable shape to parse.

Status code categories that matter

  • 400 Bad Request — Malformed syntax, missing required fields
  • 401 Unauthorized — No credentials or invalid credentials
  • 403 Forbidden — Valid credentials but insufficient permissions
  • 404 Not Found — Resource does not exist
  • 409 Conflict — State conflict (duplicate email, concurrent modification)
  • 422 Unprocessable Entity — Syntax is fine but semantics are wrong (age = -5)
  • 429 Too Many Requests — Rate limit exceeded, include Retry-After header
  • 500 Internal Server Error — Unhandled server-side exception
  • 503 Service Unavailable — Temporary overload or maintenance

Getting these right means clients can implement proper retry logic. A 429 with Retry-After: 30 tells the client to wait 30 seconds. A 503 signals temporary unavailability. A 400 means “do not retry, fix your request.”

Framework patterns in Python

FastAPI uses exception handlers that catch specific exception types and return structured responses:

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

class APIError(Exception):
    def __init__(self, status: int, error_type: str, detail: str, errors=None):
        self.status = status
        self.error_type = error_type
        self.detail = detail
        self.errors = errors or []

app = FastAPI()

@app.exception_handler(APIError)
async def api_error_handler(request: Request, exc: APIError):
    return JSONResponse(
        status_code=exc.status,
        content={
            "type": exc.error_type,
            "detail": exc.detail,
            "errors": exc.errors,
        },
    )

Django REST Framework provides a custom exception handler hook that transforms all errors into a consistent shape.

Common misconception

Many developers think error handling means catching every exception and returning 500. The opposite is true — the goal is to catch specific failures and return the most precise status code and message possible. A generic 500 tells the client nothing useful. A specific 422 with field-level details tells the client exactly what to fix.

Error handling as a contract

Document your error responses in OpenAPI specs. Every endpoint should list its possible error codes and response shapes. This turns error handling from an afterthought into a first-class part of your API contract that clients can generate code against.

The one thing to remember: Standardize your error format once (consider RFC 7807), use precise HTTP status codes, and include enough detail for clients to handle failures programmatically.

pythonapierror-handlingbest-practices

See Also