FastAPI Swagger: Auto-Magic… Until You Need Control
One of FastAPI’s biggest selling points is also one of its biggest traps.
You write Python. You add type hints. You use Pydantic.
And suddenly you have:
- a full OpenAPI spec
- a polished Swagger UI
- zero YAML files
No annotations.
No boilerplate.
Just code.
It feels like magic.
Until the day you need control.
The good part: auto-generated OpenAPI
FastAPI does something genuinely impressive:
@app.get("/users/{id}")
async def get_user(id: int):
...
That single function:
- becomes an HTTP endpoint
- defines parameter types
- generates request/response schemas
- appears instantly in Swagger UI
For most CRUD APIs, this is perfect.
You focus on business logic. FastAPI handles documentation.
Where things start to crack: security
The moment you introduce real authentication, things get interesting.
Imagine a backend that supports:
- session cookies (primary)
- basic auth (fallback or internal use)
In OpenAPI terms, this is straightforward.
In Go / YAML-style OpenAPI
You might write something like:
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
sessionCookie:
type: apiKey
in: cookie
name: session
security:
- basicAuth: []
- sessionCookie: []
Swagger now shows both auth methods in the “Authorize” dialog.
Clear. Explicit. Predictable.
In FastAPI… it depends
In FastAPI, your OpenAPI security model is inferred from dependencies.
def get_current_user(
credentials: HTTPBasicCredentials = Depends(security)
):
...
or
def get_current_user(
session=Depends(get_session_cookie)
):
...
What Swagger shows depends on:
- which dependency you use
- whether you used
Security(...)orDepends(...) - how FastAPI interprets that dependency
And here’s the catch:
If you forget to use
Security(...), Swagger may silently assume Basic Auth.
Even if:
- your app actually uses session cookies
- Basic Auth is only a fallback
- production traffic never uses it
No error. No warning. Just misleading documentation.
Why this matters more than it seems
Swagger is not just documentation.
It is:
- a contract
- a debugging tool
- a communication layer with frontend and external teams
If Swagger says:
“Use Basic Auth”
but your app actually expects:
“Session cookie”
You’ve already lost clarity.
Auto-magic stops being helpful when it lies politely.
The moment you outgrow auto-magic
At some point, you’ll want to:
- define multiple security schemes explicitly
- control which endpoints require which auth
- customize Swagger UI behavior
- hide or expose features based on environment
That’s when FastAPI’s “just infer it” approach starts to feel restrictive.
Your escape hatches
FastAPI does give you ways out — but they are more manual.
1. Override app.openapi
You can fully control the generated spec:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
schema = get_openapi(
title=app.title,
version=app.version,
routes=app.routes,
)
# modify security schemes here
app.openapi_schema = schema
return schema
app.openapi = custom_openapi
Powerful. Verbose. Easy to get wrong if you’re not careful.
2. Export and tweak /openapi.json
Sometimes the simplest approach:
- generate OpenAPI
- export it
- edit it manually
- use it for docs or clients
You lose some automation, but gain precision.
3. Accept that YAML was doing something useful
If you’ve worked with Go or hand-written OpenAPI specs, this feeling is familiar:
“I kind of miss just writing the YAML.”
Not because YAML is fun — but because explicit control scales better in complex systems.
A note on feature-flagged documentation
One underused trick:
You can modify OpenAPI output based on:
- environment (dev / staging / prod)
- feature flags
- CI/CD preview deployments
For example:
- hide internal endpoints in production docs
- expose experimental auth methods only in staging
- document beta APIs selectively
It’s powerful.
It’s also easy to abuse.
Feature flags in documentation deserve the same discipline as feature flags in code.
But that’s a story for another blog.
The takeaway
FastAPI’s automatic Swagger generation is genuinely excellent.
It:
- lowers the barrier to entry
- keeps docs close to code
- works beautifully for simple and medium APIs
But:
The more your API becomes a product, the more you’ll want YAML-level control again.
Especially for:
- security schemes
- authentication UX
- multi-client APIs
- long-lived contracts
Auto-magic is great.
Just don’t confuse it with authority.
Related reading
- Can Pydantic Fix Untyped Python?
- Async Doesn’t Make Your System Fast — It Makes It Honest
- REST to Event-Driven: When to Split Your Backend
- Designing APIs That Stay Honest Under Load