Skip to main content

Raising HTTP Exceptions

When a client requests a resource that doesn't exist or lacks the necessary permissions, your API needs to stop the current request and return a clear error response. In FastAPI, you achieve this by raising an HTTPException.

In this tutorial, you will build an API that validates item lookups and security requirements, returning specific status codes and custom headers when things go wrong.

Prerequisites

To follow this tutorial, you need FastAPI installed:

pip install fastapi

Step 1: Raise a Basic 404 Error

When a user requests an item that isn't in your database, you should return a 404 Not Found status code. Instead of returning a normal dictionary, you raise an HTTPException.

Create a file named main.py with the following code:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}

@app.get("/items/{item_id}")
async def read_item(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item": items[item_id]}

When you raise an HTTPException, FastAPI stops the execution of the path operation function and sends the error to the client immediately. The detail parameter is converted into a JSON response body: {"detail": "Item not found"}.

Step 2: Add Custom Headers for Security

In some scenarios, such as authentication failures, the HTTP protocol requires you to send specific headers (like WWW-Authenticate). You can pass these using the headers parameter in HTTPException.

Update your main.py to include a protected route:

from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status

app = FastAPI()

def get_current_user(token: str):
if token != "secret-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return {"user": "ghost"}

@app.get("/users/me")
async def read_users_me(current_user: Annotated[dict, Depends(get_current_user)]):
return current_user

By using status.HTTP_401_UNAUTHORIZED from fastapi.status, you make your code more readable. The headers dictionary ensures the client receives the necessary metadata to handle the error, which is a common requirement in OAuth2 and security flows.

Step 3: Send Complex Data in the Error Detail

The detail parameter is not limited to strings; it can be any JSON-serializable object, such as a dictionary or a list. This is useful for providing structured error information.

@app.get("/items-validation/{item_id}")
async def read_item_validation(item_id: str):
if item_id == "bar":
raise HTTPException(
status_code=400,
detail={
"error_code": "ITEM_RESERVED",
"message": "The item 'bar' is reserved for internal use.",
"hint": "Try searching for 'foo' instead."
}
)
return {"item_id": item_id}

FastAPI will automatically serialize this dictionary into the JSON response body under the detail key.

Step 4: Customize the Global Exception Handler

If you want to change how all HTTPException errors are logged or formatted before they reach the client, you can override the default exception handler.

Add this to your main.py:

from fastapi.exception_handlers import http_exception_handler
from starlette.exceptions import HTTPException as StarletteHTTPException

@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
print(f"An HTTP error occurred: {exc.status_code} - {exc.detail}")
# You can return a custom response here or use the default one
return await http_exception_handler(request, exc)

FastAPI's HTTPException inherits from Starlette's HTTPException. When creating a custom handler, you should catch StarletteHTTPException to ensure you capture exceptions raised by both FastAPI and Starlette's internal components.

Complete Example

Here is the complete code combining these concepts:

from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.exception_handlers import http_exception_handler
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()

@app.exception_handler(StarletteHTTPException)
async def custom_http_exception_handler(request, exc):
print(f"Log: {exc.status_code} error on {request.url.path}")
return await http_exception_handler(request, exc)

items = {"foo": "The Foo Wrestlers"}

def verify_token(token: str = None):
if token != "supersecret":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid Token",
headers={"WWW-Authenticate": "Bearer"},
)

@app.get("/items/{item_id}")
async def read_item(item_id: str, token: Annotated[str | None, Depends(verify_token)] = None):
if item_id not in items:
raise HTTPException(
status_code=404,
detail={"error": "Not Found", "id": item_id}
)
return {"item": items[item_id]}

Next Steps

Now that you can return errors to your clients, you might want to:

  • Learn how to handle Request Validation Errors to customize the messages sent when Pydantic validation fails.
  • Explore Custom Exception Handlers for your own application-specific exception classes.
  • Use Dependencies to centralize your error-raising logic for authentication and authorization.