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_model when 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_code is 200. Use 201 for creation and 204 for an empty delete; see http-status-codes.
  • summary is the collapsed label; the docstring becomes the long description.
  • responses adds 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/client

FastAPI’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.