Skip to main content
FastrAPI provides a comprehensive error handling system with built-in exceptions, automatic validation errors, and custom error handlers. Learn how to handle errors effectively to build robust APIs.

HTTPException

Raise HTTPException to return error responses with custom status codes and details:
from fastrapi import FastrAPI, HTTPException

app = FastrAPI()

@app.get("/users/{user_id}")
def get_user(user_id: int):
    if user_id not in users_database:
        raise HTTPException(status_code=404, detail="User not found")
    
    return users_database[user_id]

if __name__ == "__main__":
    app.serve("127.0.0.1", 8080)
The client receives:
{
  "detail": "User not found"
}
HTTPException automatically converts to a JSON response with the appropriate status code.

Common HTTP errors

Use standard HTTP status codes to indicate different error types:
@app.post("/items")
def create_item(name: str):
    if not name.strip():
        raise HTTPException(
            status_code=400,
            detail="Item name cannot be empty"
        )
    return {"item": name}

Custom error details

Provide structured error details for better client-side error handling:
from fastrapi import FastrAPI, HTTPException

app = FastrAPI()

@app.post("/charge")
def process_payment(amount: float, card_number: str):
    if amount <= 0:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "invalid_amount",
                "message": "Amount must be greater than zero",
                "field": "amount",
                "value": amount
            }
        )
    
    if not validate_card(card_number):
        raise HTTPException(
            status_code=400,
            detail={
                "error": "invalid_card",
                "message": "Invalid card number format",
                "field": "card_number"
            }
        )
    
    return {"status": "success", "transaction_id": "txn_123"}
Response:
{
  "detail": {
    "error": "invalid_amount",
    "message": "Amount must be greater than zero",
    "field": "amount",
    "value": -10
  }
}

Validation errors

FastrAPI automatically handles Pydantic validation errors and returns 422 responses:
from pydantic import BaseModel, Field
from fastrapi import FastrAPI

app = FastrAPI()

class Item(BaseModel):
    name: str = Field(min_length=3)
    price: float = Field(gt=0)
    quantity: int = Field(ge=0)

@app.post("/items")
def create_item(item: Item):
    return {"item": item.dict()}
Invalid request:
{
  "name": "ab",
  "price": -10,
  "quantity": -5
}
Automatic error response:
{
  "detail": "Validation failed"
}
Validation errors are caught at the Rust layer for maximum performance. The validation happens before your Python handler executes.

Custom headers in errors

Add custom headers to error responses:
from fastrapi import FastrAPI, HTTPException

app = FastrAPI()

@app.get("/rate-limited")
def rate_limited_endpoint():
    if check_rate_limit_exceeded():
        raise HTTPException(
            status_code=429,
            detail="Rate limit exceeded",
            headers={
                "X-RateLimit-Limit": "100",
                "X-RateLimit-Remaining": "0",
                "X-RateLimit-Reset": "1640000000",
                "Retry-After": "3600"
            }
        )
    
    return {"data": "success"}
The headers parameter accepts a dictionary of header names and values.

WebSocket exceptions

Use WebSocketException for WebSocket-specific errors:
from fastrapi import FastrAPI, WebSocketException

app = FastrAPI()

@app.websocket("/ws")
async def websocket_endpoint(ws):
    await ws.accept()
    
    try:
        data = await ws.receive_text()
        
        if not validate_message(data):
            raise WebSocketException(
                code=1003,
                reason="Invalid message format"
            )
        
        await ws.send_text(f"Received: {data}")
        
    except WebSocketException as e:
        print(f"WebSocket error: {e.code} - {e.reason}")
        await ws.close(code=e.code)
WebSocket close codes:
  • 1000: Normal closure
  • 1003: Unsupported data
  • 1008: Policy violation
  • 1011: Internal error
See RFC 6455 for complete list.

Error handling patterns

Try-except in handlers

Handle errors within your route handlers:
from fastrapi import FastrAPI, HTTPException
import logging

app = FastrAPI()
logger = logging.getLogger(__name__)

@app.get("/users/{user_id}")
def get_user(user_id: int):
    try:
        user = database.fetch_user(user_id)
        return user
    except DatabaseConnectionError:
        logger.error("Database connection failed")
        raise HTTPException(
            status_code=503,
            detail="Service temporarily unavailable"
        )
    except Exception as e:
        logger.exception("Unexpected error")
        raise HTTPException(
            status_code=500,
            detail="Internal server error"
        )

Dependency error handling

Handle errors in dependencies:
from fastrapi import FastrAPI, HTTPException, Depends

app = FastrAPI()

def verify_token(token: str):
    if not token:
        raise HTTPException(
            status_code=401,
            detail="Missing authentication token"
        )
    
    user = decode_token(token)
    if not user:
        raise HTTPException(
            status_code=401,
            detail="Invalid or expired token"
        )
    
    return user

@app.get("/protected")
def protected_route(user = Depends(verify_token)):
    return {"message": f"Hello, {user.name}"}

Centralized error responses

Create helper functions for consistent error responses:
from fastrapi import FastrAPI, HTTPException
from typing import Dict, Any

app = FastrAPI()

def not_found_error(resource: str, id: Any) -> HTTPException:
    return HTTPException(
        status_code=404,
        detail={
            "error": "not_found",
            "message": f"{resource} not found",
            "resource": resource,
            "id": id
        }
    )

def validation_error(field: str, message: str) -> HTTPException:
    return HTTPException(
        status_code=422,
        detail={
            "error": "validation_error",
            "field": field,
            "message": message
        }
    )

@app.get("/users/{user_id}")
def get_user(user_id: int):
    user = database.get(user_id)
    if not user:
        raise not_found_error("User", user_id)
    return user

@app.post("/users")
def create_user(email: str, age: int):
    if age < 18:
        raise validation_error("age", "Must be 18 or older")
    # Create user

How error handling works

FastrAPI’s error handling is integrated at the Rust level for performance:
1

Python exception raised

When you raise HTTPException in Python, it’s caught by the Rust runtime.
2

Rust conversion

The exception is converted to an Axum response with the appropriate status code and JSON body (src/exceptions.rs:136-143).
3

Response sent

The error response is sent to the client immediately, bypassing further processing.

Status code reference

Use the status module for readable status codes:
from fastrapi import FastrAPI, HTTPException
from fastrapi import status

app = FastrAPI()

@app.get("/items/{item_id}")
def get_item(item_id: int):
    if item_id not in database:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Item not found"
        )
    return database[item_id]
Common status codes:
  • status.HTTP_200_OK
  • status.HTTP_201_CREATED
  • status.HTTP_400_BAD_REQUEST
  • status.HTTP_401_UNAUTHORIZED
  • status.HTTP_403_FORBIDDEN
  • status.HTTP_404_NOT_FOUND
  • status.HTTP_422_UNPROCESSABLE_ENTITY
  • status.HTTP_500_INTERNAL_SERVER_ERROR
See the API reference for the complete list.

Best practices

Choose the right HTTP status code for each error type:
  • 400: Client error (bad input)
  • 401: Authentication required
  • 403: Authenticated but not authorized
  • 404: Resource doesn’t exist
  • 422: Validation failed
  • 500: Server error
Write clear, actionable error messages:
# Good
raise HTTPException(
    status_code=400,
    detail="Email format is invalid. Example: [email protected]"
)

# Bad
raise HTTPException(status_code=400, detail="Bad request")
Never include sensitive data in error messages:
# Bad - exposes internal details
raise HTTPException(
    status_code=500,
    detail=f"Database error: {db_password}"
)

# Good - generic message
raise HTTPException(
    status_code=500,
    detail="Internal server error"
)
Always log errors server-side while keeping client errors simple:
try:
    result = complex_operation()
except Exception as e:
    logger.exception("Complex operation failed")  # Server log
    raise HTTPException(
        status_code=500,
        detail="Operation failed"  # Client message
    )
Return consistent error formats:
{
  "error": "error_code",
  "message": "Human readable message",
  "field": "field_name",  # For validation errors
  "timestamp": "2024-01-01T12:00:00Z"
}

Pydantic validation

Learn about automatic validation

Status codes

Complete status code reference

API reference

HTTPException API documentation

Request handling

Understand request processing

Build docs developers (and LLMs) love