OpenAPI Customization
FastAPI generates OpenAPI 3.1 specs automatically. Customizing that spec — adding metadata, examples, tag groupings, and hiding internal endpoints — makes your API documentation professional and usable.
Learning Focus
By the end of this lesson you can: set API metadata, organize routes by tag, add request/response examples, hide internal endpoints, and generate a client SDK from the OpenAPI spec.
API Metadata
app/main.py
from fastapi import FastAPI
app = FastAPI(
title="My Production API",
version="2.1.0",
description="""
## Overview
This API provides access to the product catalog, user management, and order processing.
## Authentication
All endpoints except `/health` and `/auth/token` require a valid JWT bearer token.
## Rate Limits
- Public endpoints: 20 requests/minute
- Authenticated endpoints: 200 requests/minute
""",
terms_of_service="https://example.com/terms",
contact={
"name": "API Support",
"url": "https://example.com/support",
"email": "api@example.com",
},
license_info={
"name": "MIT",
"url": "https://opensource.org/licenses/MIT",
},
openapi_url="/api/v1/openapi.json", # Custom URL
docs_url="/api/v1/docs",
redoc_url="/api/v1/redoc",
)
Organizing by Tags
app/main.py
app = FastAPI(
openapi_tags=[
{
"name": "auth",
"description": "Authentication and token management",
},
{
"name": "products",
"description": "Product catalog — browse, search, and manage items",
"externalDocs": {
"description": "Product schema docs",
"url": "https://docs.example.com/products",
},
},
{
"name": "admin",
"description": "Administrative operations. **Requires admin role.**",
},
]
)
Adding Route Examples
app/routers/items.py
from fastapi import APIRouter
from pydantic import BaseModel, Field
class ItemCreate(BaseModel):
name: str = Field(..., examples=["Premium Widget"])
price: float = Field(..., examples=[29.99])
description: str | None = Field(None, examples=["A high-quality widget"])
@router.post(
"/",
response_model=ItemResponse,
status_code=201,
summary="Create an item",
description="Creates a new item in the catalog. Returns the full item object with generated ID.",
response_description="The created item",
openapi_extra={
"requestBody": {
"content": {
"application/json": {
"examples": {
"basic": {"value": {"name": "Widget", "price": 9.99}},
"full": {"value": {"name": "Premium Widget", "price": 49.99, "description": "High quality"}},
}
}
}
}
},
)
async def create_item(item: ItemCreate) -> ItemResponse:
...
Hiding Internal Routes
app/routers/internal.py
from fastapi import APIRouter
# Entire router hidden from docs
router = APIRouter(include_in_schema=False)
@router.get("/internal/metrics")
async def metrics() -> dict:
return {"memory_mb": 150, "requests": 10000}
Or hide individual routes:
app/main.py
@app.get("/health", include_in_schema=False)
async def health() -> dict:
return {"status": "ok"}
Restricting Docs Access
app/main.py
from fastapi import FastAPI
import os
# Only expose docs in non-production
docs_url = "/docs" if os.getenv("ENV") != "production" else None
redoc_url = "/redoc" if os.getenv("ENV") != "production" else None
app = FastAPI(docs_url=docs_url, redoc_url=redoc_url)
Generating Client SDKs
generate-sdk.sh
# Install openapi-generator
npm install @openapitools/openapi-generator-cli -g
# Generate TypeScript client
openapi-generator-cli generate \
-i http://localhost:8000/openapi.json \
-g typescript-axios \
-o ./clients/typescript
# Generate Python client
openapi-generator-cli generate \
-i http://localhost:8000/openapi.json \
-g python \
-o ./clients/python
Common Pitfalls
| Pitfall | Cause / Symptom | Fix |
|---|---|---|
| Docs empty | All routes have include_in_schema=False | Remove flag from non-internal routes |
| Tag not appearing | Tag not in openapi_tags list | Add tag definition to FastAPI(openapi_tags=[...]) |
| Examples not showing | Using Pydantic v1 schema_extra | Use Pydantic v2 json_schema_extra in model_config |
operation_id collision | Two routes auto-generate same ID | Set operation_id= explicitly on each route |
| Docs blocked in production | All routes hidden | Use environment variable to conditionally set docs_url |