FastAPI Error Handling
When something goes wrong in your API — a resource does not exist, a user has no permission, or input fails a business rule — you raise an exception. FastAPI converts it into a clean JSON error response with the right HTTP status code. This topic shows you how to use built-in exceptions and create your own.
The HTTPException — Your Main Tool
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
if user_id != 1:
raise HTTPException(status_code=404, detail="User not found")
return {"id": 1, "name": "Meera"}
GET /users/1
→ 200 OK
→ {"id": 1, "name": "Meera"}
GET /users/99
→ 404 Not Found
→ {"detail": "User not found"}
Common HTTP Error Codes to Use
Status Code When to raise it ──────────────────────────────────────────────────── 400 Bad request — client sent invalid data 401 Unauthorized — not logged in 403 Forbidden — logged in but no permission 404 Not found — resource does not exist 409 Conflict — duplicate entry 422 Unprocessable entity (FastAPI auto-raises) 500 Server error — something broke on your side
Adding Extra Headers to an Error
Some errors require extra headers — like a 401 that tells the client which auth method to use:
@app.get("/secure")
def secure_route(token: str):
if token != "valid-token":
raise HTTPException(
status_code=401,
detail="Invalid token",
headers={"WWW-Authenticate": "Bearer"}
)
return {"access": "granted"}
Custom Exception Classes
For business-specific errors that appear in many places, create a custom exception class:
class InsufficientBalanceError(Exception):
def __init__(self, balance: float, required: float):
self.balance = balance
self.required = required
Registering a Custom Exception Handler
Tell FastAPI how to convert your custom exception into an HTTP response:
from fastapi import Request
from fastapi.responses import JSONResponse
@app.exception_handler(InsufficientBalanceError)
async def balance_exception_handler(request: Request, exc: InsufficientBalanceError):
return JSONResponse(
status_code=402,
content={
"error": "Insufficient balance",
"your_balance": exc.balance,
"amount_needed": exc.required
}
)
Now when any route raises InsufficientBalanceError:
raise InsufficientBalanceError(balance=50.0, required=200.0)
→ 402 Payment Required
→ {
"error": "Insufficient balance",
"your_balance": 50.0,
"amount_needed": 200.0
}
Overriding the Default Validation Error Handler
FastAPI raises RequestValidationError when input fails Pydantic validation. You can override how this error looks:
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=400,
content={
"message": "Invalid input",
"errors": exc.errors()
}
)
Using try/except Inside a Route
For errors from external code (databases, third-party libraries), catch them inside the route and convert to HTTPException:
@app.get("/data")
def get_data():
try:
result = external_service.fetch()
except ConnectionError:
raise HTTPException(
status_code=503,
detail="External service unavailable. Try again later."
)
return result
Error Handling Flow
Route function raises exception
│
▼
Is there a registered exception_handler?
Yes → run handler → return custom response
No → is it HTTPException?
Yes → FastAPI converts to JSON error
No → 500 Internal Server Error
Key Points
- Use
HTTPExceptionwith astatus_codeanddetailfor standard errors. - Create custom exception classes for domain-specific errors.
- Register handlers with
@app.exception_handler()to control the response format. - Override
RequestValidationErrorto customize validation error responses. - Always catch external library errors and convert them to
HTTPExceptionbefore they reach the client.
