FastAPI Documentation Standards
Introduction
Documentation is a crucial aspect of any API development process. Well-documented APIs are easier to use, maintain, and collaborate on. FastAPI excels in this area by automatically generating interactive API documentation based on your code and docstrings.
In this guide, we'll explore the best practices for documenting your FastAPI applications. We'll cover everything from function and parameter documentation to enhancing your OpenAPI schema for a better developer experience.
Why Documentation Matters in FastAPI
FastAPI leverages Python type hints and docstrings to automatically generate:
- OpenAPI schema - A standardized description of your API
- Swagger UI - Interactive API documentation
- ReDoc - Alternative documentation view
Proper documentation benefits:
- API consumers who need to understand your endpoints
- Team members maintaining or extending your code
- Future you, who will need to remember why certain decisions were made
Documenting Path Operations
Basic Documentation with Docstrings
FastAPI automatically uses Python docstrings to document your API:
@app.get("/items/")
async def read_items():
"""
Retrieve a list of items.
This endpoint returns all available items in the database.
"""
return [{"name": "Plumbus", "price": 3.99}, {"name": "Portal Gun", "price": 9999.99}]
This simple docstring will appear in the generated documentation:
Enhanced Documentation with summary
and description
For more control, you can use explicit parameters:
@app.get(
"/items/",
summary="Retrieve Items",
description="Retrieve a list of items from the database with pagination support."
)
async def read_items():
return [{"name": "Plumbus", "price": 3.99}, {"name": "Portal Gun", "price": 9999.99}]
Adding Response Descriptions
Document the possible responses from your API:
@app.get(
"/items/{item_id}",
summary="Get Item by ID",
responses={
200: {
"description": "Successful retrieval of item data",
"content": {
"application/json": {
"example": {"name": "Plumbus", "price": 3.99}
}
}
},
404: {
"description": "Item not found"
}
}
)
async def get_item(item_id: int):
if item_id == 42:
return {"name": "Plumbus", "price": 3.99}
return JSONResponse(status_code=404, content={"message": "Item not found"})
Documenting Request Bodies and Parameters
Path, Query and Header Parameters
Document parameters with clear type hints and descriptions:
@app.get("/items/")
async def read_items(
skip: int = Query(
0,
title="Skip",
description="Number of items to skip",
ge=0
),
limit: int = Query(
10,
title="Limit",
description="Maximum number of items to return",
le=100
)
):
return {"skip": skip, "limit": limit}
Request Body Documentation
Use Pydantic models with field descriptions:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(
..., # ... means required
title="Item Name",
description="Name of the item",
example="Screwdriver"
)
description: str | None = Field(
None,
title="Description",
description="Optional detailed description of the item",
max_length=300
)
price: float = Field(
...,
title="Price",
description="Price of the item in USD",
gt=0
)
tax: float | None = Field(
None,
title="Tax",
description="Tax rate applied to the item"
)
class Config:
schema_extra = {
"example": {
"name": "Screwdriver",
"description": "A simple screwdriver with a blue handle",
"price": 9.99,
"tax": 0.21
}
}
@app.post("/items/")
async def create_item(item: Item):
"""
Create a new item in the database.
The endpoint accepts item details and stores them in the database.
"""
return item
Advanced Documentation Techniques
Adding Tags for Organization
Group your endpoints logically:
@app.get("/users/", tags=["users"])
async def read_users():
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
@app.get("/items/", tags=["items"])
async def read_items():
return [{"name": "Plumbus", "price": 3.99}]
Deprecating Endpoints
Mark outdated endpoints:
@app.get(
"/items/old-endpoint/",
tags=["items"],
deprecated=True
)
async def read_items_old():
"""This endpoint is deprecated. Use /items/ instead."""
return [{"name": "Old data format"}]
Documentation Examples
Add example requests and responses:
from fastapi import Body
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item = Body(
...,
examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Screwdriver",
"description": "A simple screwdriver with a blue handle",
"price": 9.99,
"tax": 0.21
}
},
"missing_tax": {
"summary": "A example with missing tax",
"description": "Tax is optional so it can be omitted.",
"value": {
"name": "Hammer",
"description": "A heavy-duty hammer",
"price": 14.99
}
}
}
)
):
results = {"item_id": item_id, "item": item}
return results
Customizing the OpenAPI Documentation
Global Documentation Settings
Configure your FastAPI application with documentation details:
from fastapi import FastAPI
app = FastAPI(
title="My Awesome API",
description="This API does awesome things",
version="0.1.0",
terms_of_service="http://example.com/terms/",
contact={
"name": "API Support Team",
"url": "http://www.example.com/support",
"email": "[email protected]",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)
Custom Documentation URLs
Change the URL for documentation:
app = FastAPI(
docs_url="/documentation", # Custom Swagger UI URL (default is /docs)
redoc_url="/redoc", # Custom ReDoc URL (default is /redoc)
)
Disabling Documentation
You can also disable documentation for production:
app = FastAPI(docs_url=None, redoc_url=None) # Disable all docs
Documenting Security Requirements
Clearly document security requirements:
from fastapi import FastAPI, Depends, Security, HTTPException
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="token",
scopes={"users:read": "Read users", "users:write": "Create or update users"}
)
app = FastAPI()
@app.get(
"/users/",
tags=["users"],
summary="Get all users",
description="Requires admin privileges to access all user data",
dependencies=[Depends(oauth2_scheme)],
responses={
401: {"description": "Not authenticated"},
403: {"description": "Not enough privileges"}
}
)
async def get_users():
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
Real-World Documentation Example
Let's create a complete example for a user management API endpoint:
from fastapi import FastAPI, HTTPException, Depends, Path, Query
from pydantic import BaseModel, Field, EmailStr
from typing import List, Optional
from datetime import datetime
app = FastAPI(
title="User Management API",
description="API for managing users in the system",
version="1.0.0"
)
class UserBase(BaseModel):
email: EmailStr = Field(..., description="User's email address")
full_name: str = Field(..., description="User's full name", min_length=1, max_length=100)
is_active: bool = Field(True, description="Whether the user is active")
class UserCreate(UserBase):
password: str = Field(..., description="User's password", min_length=8)
class User(UserBase):
id: int = Field(..., description="User's unique identifier")
created_at: datetime = Field(..., description="When the user was created")
class Config:
schema_extra = {
"example": {
"id": 1,
"email": "[email protected]",
"full_name": "John Doe",
"is_active": True,
"created_at": "2023-01-15T14:30:00Z"
}
}
@app.post(
"/users/",
response_model=User,
status_code=201,
tags=["users"],
summary="Create a new user",
description="""
Create a new user with the provided information.
The email address must be unique in the system.
* The password must be at least 8 characters long
* Email will be validated for proper format
"""
)
async def create_user(
user: UserCreate = Body(
...,
examples={
"normal": {
"summary": "Normal user creation",
"description": "Creates a standard active user",
"value": {
"email": "[email protected]",
"full_name": "Jane Smith",
"password": "securepassword123",
"is_active": True
}
},
"inactive_user": {
"summary": "Inactive user creation",
"description": "Creates an inactive user that needs verification",
"value": {
"email": "[email protected]",
"full_name": "New User",
"password": "temporarypass",
"is_active": False
}
}
}
)
):
"""
Create a new user in the system.
The user will be assigned a unique ID and creation timestamp automatically.
Returns the created user data (excluding password).
"""
# In a real app, you would:
# 1. Check if the user already exists
# 2. Hash the password
# 3. Store in database
# For this example, we'll simulate creating a user
created_user = User(
id=1,
email=user.email,
full_name=user.full_name,
is_active=user.is_active,
created_at=datetime.now()
)
return created_user
@app.get(
"/users/",
response_model=List[User],
tags=["users"],
summary="List all users",
description="Retrieve a list of users with optional filtering"
)
async def list_users(
skip: int = Query(
0,
title="Skip",
description="Number of users to skip",
ge=0
),
limit: int = Query(
100,
title="Limit",
description="Maximum number of users to return",
le=1000
),
active_only: bool = Query(
False,
title="Active Only",
description="Filter only active users"
)
):
"""
Retrieve a list of users.
- Supports pagination with skip and limit parameters
- Can filter to show only active users
"""
# In a real app, you would query your database
users = [
User(
id=1,
email="[email protected]",
full_name="John Doe",
is_active=True,
created_at=datetime.fromisoformat("2023-01-15T14:30:00")
),
User(
id=2,
email="[email protected]",
full_name="Jane Smith",
is_active=False,
created_at=datetime.fromisoformat("2023-02-20T09:15:00")
)
]
if active_only:
users = [user for user in users if user.is_active]
return users[skip:skip+limit]
@app.get(
"/users/{user_id}",
response_model=User,
responses={
200: {
"description": "User found",
"content": {
"application/json": {
"example": {
"id": 1,
"email": "[email protected]",
"full_name": "John Doe",
"is_active": True,
"created_at": "2023-01-15T14:30:00Z"
}
}
}
},
404: {
"description": "User not found",
"content": {
"application/json": {
"example": {"detail": "User not found"}
}
}
}
},
tags=["users"],
summary="Get user by ID",
description="Retrieve a specific user's information by their ID"
)
async def get_user(
user_id: int = Path(
...,
title="User ID",
description="The ID of the user to retrieve",
ge=1
)
):
"""
Retrieve a specific user by their ID.
If the user doesn't exist, a 404 error will be returned.
"""
# In a real app, you would query your database
if user_id == 1:
return User(
id=1,
email="[email protected]",
full_name="John Doe",
is_active=True,
created_at=datetime.fromisoformat("2023-01-15T14:30:00")
)
else:
raise HTTPException(status_code=404, detail="User not found")
Documentation Best Practices
- Be consistent - Use the same style and level of detail throughout your API
- Consider your audience - Write for developers who may be unfamiliar with your API
- Use examples - Real use cases help developers understand faster
- Document errors - Clearly explain what can go wrong and how to fix it
- Keep it updated - Outdated documentation is worse than no documentation
- Use Markdown - Format your docstrings for readability
- Add type hints - FastAPI uses them for validation and documentation
Summary
Good documentation is essential for API usability. FastAPI makes documentation easy by automatically generating it from your code, but it's still your responsibility to provide clear, accurate descriptions and examples.
By following the standards and practices outlined in this guide, you'll create FastAPI applications that are not only functional but also easy to understand and use. Remember that well-documented APIs save time for everyone - both consumers and developers.
Additional Resources
- FastAPI Official Documentation on OpenAPI
- OpenAPI Specification
- Swagger UI Documentation
- ReDoc Documentation
Exercises
- Create a simple FastAPI app with at least two endpoints and document them using the techniques in this guide
- Take an existing FastAPI application and improve its documentation
- Add examples to request and response models
- Create a Pydantic model with well-documented fields
- Customize the OpenAPI schema for your API with contact information and terms of service
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)