---
title: "FastAPI: OpenAPI and Docs"
slug: "fastapi-openapi"
category: "backend"
tags: ["backend", "fastapi", "python", "openapi", "swagger", "codegen", "documentation"]
status: "stable"
last_updated: 2026-05-14
summary: "Keeping FastAPI's auto-generated OpenAPI schema accurate: response_model, tags, summaries, ReDoc vs Swagger UI, and client codegen."
related: ["[[backend/fastapi]]", "[[coding/python]]", "[[backend/fastapi-pydantic]]", "[[backend/fastapi-dependencies]]", "[[cheatsheets/http-status-codes]]", "[[seo/technical]]", "[[coding/general-principles]]"]
---

## Overview

FastAPI generates an OpenAPI 3.1 schema from your route declarations and Pydantic models. The schema powers `/docs` (Swagger UI), `/redoc` (ReDoc), and any client codegen pipeline. The schema is only as accurate as your annotations; leaving `response_model` unset or `status_code` at its default produces a schema that misleads API consumers and breaks generated clients. This page covers what to set on every route and how to use the generated schema downstream. See [[backend/fastapi-pydantic]] for the model side of the contract.

## Set `response_model`, `status_code`, `tags`, and `summary` on every route

Treat these four parameters as mandatory. Omitting any of them makes the schema incomplete.

```python
from fastapi import APIRouter
from app.models import OrderCreate, OrderRead

router = APIRouter(prefix="/orders", tags=["orders"])

@router.post(
    "/",
    response_model=OrderRead,
    status_code=201,
    summary="Place a new order",
    response_description="The created order",
)
async def create_order(payload: OrderCreate, db=Depends(get_db)) -> OrderRead:
    """
    Place a new order for the authenticated customer.

    - Validates inventory before confirming.
    - Returns the order with its assigned `order_id`.
    """
    ...
```

- `response_model` filters the return value through Pydantic. Extra internal fields do not leak to clients. FastAPI also generates the response schema from this model.
- `status_code` defaults to `200`. `POST` should be `201`, `DELETE` with no body should be `204`. See [[cheatsheets/http-status-codes]].
- `tags` group routes in Swagger UI. Use one tag per logical resource, matching the router's resource name.
- `summary` is the one-line label shown collapsed in the docs. The route docstring becomes the expanded `description`.

## Declare all error responses with `responses`

FastAPI only adds the success response to the schema by default. Add error shapes with the `responses` parameter so clients know what to expect on failure.

```python
@router.get(
    "/{order_id}",
    response_model=OrderRead,
    responses={
        404: {"description": "Order not found"},
        403: {"description": "Insufficient permissions"},
    },
)
async def get_order(order_id: str, user=Depends(get_current_user)) -> OrderRead: ...
```

Provide `model` inside the response dict to generate a schema for the error body:

```python
from app.models import ErrorDetail

responses={404: {"model": ErrorDetail, "description": "Order not found"}}
```

## Use `APIRouter` to apply shared metadata at the router level

Setting `prefix`, `tags`, and `dependencies` on the router avoids repeating them on every route. The `include_router` call in `main.py` composes routers without duplicating configuration.

```python
# routers/orders.py
router = APIRouter(prefix="/orders", tags=["orders"], dependencies=[Depends(verify_api_key)])

# main.py
app.include_router(orders.router)
app.include_router(users.router)
```

Router-level `dependencies` run for every route in that router, which is the correct place for authentication guards. See [[backend/fastapi-dependencies]] for the dependency injection pattern.

## Suppress internal routes from the schema when needed

Development-only routes, health checks, and internal admin endpoints should not appear in the public schema. Set `include_in_schema=False` to hide them.

```python
@app.get("/healthz", include_in_schema=False)
async def health() -> dict:
    return {"status": "ok"}
```

For separate public and internal schemas, create two `FastAPI` instances with `openapi_url` set differently, or use middleware to filter routes by tag before generating the schema.

## Choose ReDoc for documentation, Swagger UI for exploration

Both are served by default. `/redoc` renders a clean two-panel view suited for sharing with API consumers. `/docs` (Swagger UI) has a "Try it out" button suited for developer exploration.

```python
app = FastAPI(
    title="Orders API",
    version="1.0.0",
    docs_url="/docs",
    redoc_url="/redoc",
    openapi_url="/openapi.json",
)
```

In production, consider disabling `/docs` and `/redoc` entirely to reduce the attack surface, and expose the schema only to authenticated tooling. Set `docs_url=None, redoc_url=None` to suppress both.

## Generate typed clients from the OpenAPI schema

The schema at `/openapi.json` is the input for client codegen. `openapi-typescript-codegen` and `openapi-generator` both consume it.

```bash
# Generate a TypeScript client
npx openapi-typescript-codegen \
  --input http://localhost:8000/openapi.json \
  --output ./src/api \
  --client axios

# Generate a Python client
openapi-generator generate \
  -i http://localhost:8000/openapi.json \
  -g python \
  -o ./clients/python
```

Generated clients stay in sync with the API as long as `response_model` and request body types are accurate. A route that returns `Any` or omits `response_model` produces an untyped client method. See [[backend/fastapi-pydantic]] for keeping models strict enough to generate useful clients.

## Related

- [[backend/fastapi]]
- [[coding/python]]
- [[backend/fastapi-pydantic]]
- [[backend/fastapi-dependencies]]
- [[cheatsheets/http-status-codes]]
- [[seo/technical]]
- [[coding/general-principles]]
