Skip to main content

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:

  1. OpenAPI schema - A standardized description of your API
  2. Swagger UI - Interactive API documentation
  3. 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:

python
@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:

Swagger UI showing the docstring

Enhanced Documentation with summary and description

For more control, you can use explicit parameters:

python
@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:

python
@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:

python
@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:

python
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:

python
@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:

python
@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:

python
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:

python
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:

python
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:

python
app = FastAPI(docs_url=None, redoc_url=None)  # Disable all docs

Documenting Security Requirements

Clearly document security requirements:

python
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:

python
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

  1. Be consistent - Use the same style and level of detail throughout your API
  2. Consider your audience - Write for developers who may be unfamiliar with your API
  3. Use examples - Real use cases help developers understand faster
  4. Document errors - Clearly explain what can go wrong and how to fix it
  5. Keep it updated - Outdated documentation is worse than no documentation
  6. Use Markdown - Format your docstrings for readability
  7. 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

Exercises

  1. Create a simple FastAPI app with at least two endpoints and document them using the techniques in this guide
  2. Take an existing FastAPI application and improve its documentation
  3. Add examples to request and response models
  4. Create a Pydantic model with well-documented fields
  5. 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! :)