Overview
FastAPI generates an OpenAPI 3.1 schema from route declarations and Pydantic models. The schema feeds Swagger UI (/docs), ReDoc (/redoc), and client generators. It is only as accurate as your annotations: a route without a response type or status code produces a schema that misleads consumers and generates untyped clients. The model side is covered in fastapi-pydantic.
Declare the response type, status code, tags, and summary on every route
router = APIRouter(prefix="/orders", tags=["orders"])
@router.post(
"/",
response_model=OrderRead,
status_code=201,
summary="Place a new order",
responses={404: {"model": ErrorDetail, "description": "Product not found"}},
)
async def create_order(payload: OrderCreate, db: DB) -> OrderRead:
"""Place an order for the authenticated customer. Validates inventory first."""- A return annotation is enough as the response type. Add
response_modelwhen the function returns an ORM object or dict that differs from the annotation. Either way the output is filtered through the model, so extra internal fields do not leak. - The default
status_codeis 200. Use 201 for creation and 204 for an empty delete; see http-status-codes. summaryis the collapsed label; the docstring becomes the long description.responsesadds error shapes. FastAPI documents only the success response by default.
Set shared metadata on the router
prefix, tags, and dependencies on APIRouter apply to every route in it. Router-level dependencies run for every route, which is the right place for authentication; see fastapi-dependencies.
router = APIRouter(prefix="/orders", tags=["orders"], dependencies=[Depends(verify_api_key)])
app.include_router(router)Hide routes that should not appear in the public schema with include_in_schema=False (health checks, admin). For separate public and internal schemas, run two FastAPI apps with different openapi_url values.
Disable all three docs URLs in production when the API is private
/docs and /redoc both depend on /openapi.json. Setting docs_url=None and redoc_url=None leaves the raw schema public. Use openapi_url=None to remove the schema and, with it, the docs UIs.
app = FastAPI(title="Orders API", version="1.0.0", openapi_url=None)If tooling needs the schema, serve it from an authenticated route or export it at build time instead.
Generate clients from a stable schema
npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/clientFastAPI’s docs recommend Hey API for TypeScript clients. Generators must support OpenAPI 3.1. Default operation IDs look like create_order_orders__post, so generated method names are ugly; set a clean ID function once.
from fastapi.routing import APIRoute
def operation_id(route: APIRoute) -> str:
return f"{route.tags[0]}-{route.name}"
app = FastAPI(generate_unique_id_function=operation_id)The function above assumes every route has a tag. A route that returns Any or omits a response type yields an untyped client method. Export the schema in CI and diff it so API changes are reviewed.