fastapi-docs-plus
Enhanced interactive API docs for FastAPI that add two capabilities essential for integration and debugging:
- AI parameter filling — one click per operation to call an LLM and generate realistic request data that respects the JSON Schema, then write it directly into the Try-it-out form fields.
- Python-side pre-request hooks — server-side Python functions that intercept every Swagger UI request to inject authentication, signatures, or dynamic headers (JWT tokens, tenant IDs, CSRF tokens, etc.) using your project's own code.
It replaces FastAPI's built-in /docs while keeping Swagger UI as the rendering engine — no fork, no custom frontend build.
Installation
pip install fastapi-docs-plus
Requires Python 3.10 or later.
Quick start
from fastapi import FastAPI
from fastapi_docs_plus import DocsPlusConfig, PreRequestContext, setup_docs_plus
# Turn off the built-in docs, otherwise FastAPI's own /docs route takes
# precedence and this library can never serve the page.
app = FastAPI(docs_url=None)
# ... your routes ...
# Register the enhanced docs. Do this in development only: the pre-request
# hooks run with server-side privileges and the AI endpoints spend your quota.
docs = setup_docs_plus(app, DocsPlusConfig(identities=["admin", "shop_owner"]))
# Optional — give every request a freshly signed token.
@docs.pre_request
async def inject_auth(ctx: PreRequestContext) -> None:
if ctx.route_path == "/auth/login": # login issues its own token
return
username = ctx.identity or "admin"
token = create_token(username) # reuse your project's own signing code
ctx.headers["Authorization"] = f"Bearer {token}"
Start the app with an LLM key to enable the AI buttons:
DOCS_PLUS_LLM_API_KEY=sk-xxx uvicorn app.main:app --reload
Then open http://127.0.0.1:8000/docs. Compared with the stock docs you now get:
- a Generate button on every operation, which asks the LLM for realistic parameters and caches the validated result;
- a Fill button that writes the next cached result straight into the form;
- an identity dropdown (populated from
identities), whose selection reaches your hook asctx.identity.
Without DOCS_PLUS_LLM_API_KEY everything still works — the AI buttons are simply not rendered.
Pre-request hooks
Registration
@docs.pre_request
def sync_hook(ctx: PreRequestContext) -> None: ...
@docs.pre_request
async def async_hook(ctx: PreRequestContext) -> None: ...
Both synchronous and asynchronous callables are supported. Multiple hooks run in registration order and share the same PreRequestContext.
How it works
Before Swagger UI sends any request, the browser POSTs the pending request metadata to {api_prefix}/api/pre-request. The hooks execute server-side and return header / query patches. The browser then applies those patches and sends the real request.
- Hooks have full access to your project: databases, signing functions, HTTP clients.
- Protected
openapi.jsonURLs work correctly (the pre-request call has no method; the server falls back toGET). - Injected headers appear in the Swagger UI cURL display.
PreRequestContext
| Field | Type | Description |
|---|---|---|
method |
str |
Uppercase HTTP method |
url |
str |
Full request URL |
path |
str |
URL path component |
headers |
dict[str, str] |
Mutable. Sent back as the complete set — deleting a key removes the header |
query |
dict[str, str] |
Mutable. URL-decoded; re-encoded on return |
env |
dict[str, Any] |
Environment from the frontend: identity selection + extra env JSON |
identity |
str | None |
Shorthand for env["identity"] |
route_path |
str | None |
Matched route template, e.g. /shops/{shop_id} |
operation_id |
str | None |
The route's operation_id, or the endpoint function name |
path_params |
dict[str, Any] |
Path parameters extracted from the URL |
route_path, operation_id, and path_params are resolved server-side by matching the method and path against app.routes, independent of frontend input.
Headers injected by hooks
Declare hook-injected headers with include_in_schema=False so Swagger UI does not require them in the form:
from typing import Annotated
from fastapi import Header
tenant_id: Annotated[str | None, Header(alias="X-Tenant-Id", include_in_schema=False)] = None
Identity switching and extra environment
DocsPlusConfig(identities=[...])controls the top-bar dropdown. The selected value is passed to hooks asenv["identity"].- The extra environment variables textarea accepts a JSON object that is merged into
env, useful for flags like{"tenant": "...", "debug": true}. - Both are persisted in
localStorageacross page refreshes.
AI parameter filling
Workflow
Split into two separate actions:
- Generate — calls the LLM and caches validated results (does not modify the form).
- Fill — writes the next cached result into the form fields.
Generate → POST {api_prefix}/api/ai/generate:
- Extracts the operation from
app.openapi(), inlines all$refpointers, truncates circular references and levels beyondmax_schema_depth. - Preserves
description,enum,pattern,format,minimum/maximum,examplesas the only signals the LLM receives about real-world semantics. - Appends any previously cached results for the same operation so the model avoids duplicates; temperature scales up with history count.
- Calls the LLM (OpenAI-compatible API, JSON mode) to produce a fixed envelope:
{ "path": {}, "query": {}, "header": {}, "cookie": {}, "body": null }
- Strips parameters the model invented that are not declared in the schema; forces
bodytonullwhen the operation has norequestBody. - Validates
bodyagainstjsonschema. On failure, feeds the validation error back to the model for one retry. Only validated results enter the cache.
The cache is a per-operation in-memory queue (ai_cache_max_size, default 5). Oldest entries are evicted first. When the schema changes, the cache for that operation is invalidated automatically.
Fill → POST {api_prefix}/api/ai/fill:
Returns the next result in round-robin order. The frontend expands the operation and writes path/query/header/cookie/body values into the form. Returns 409 when the cache is empty.
Business hints
Add an x-ai-hint extension to your route for domain-specific guidance:
@router.post(
"/orders",
openapi_extra={"x-ai-hint": "E-commerce order; use realistic province/city names; unit price 100-2000 CNY"},
)
The hint is sent to the LLM as businessHint.
Model configuration
Supports any OpenAI-compatible service (OpenAI, DeepSeek, Tongyi Qianwen, Ollama, etc.).
| Environment variable | DocsPlusConfig field |
Default |
|---|---|---|
DOCS_PLUS_LLM_API_KEY or OPENAI_API_KEY |
llm_api_key |
(none) |
DOCS_PLUS_LLM_BASE_URL or OPENAI_BASE_URL |
llm_base_url |
OpenAI official |
DOCS_PLUS_LLM_MODEL |
llm_model |
gpt-4o-mini |
When no API key is configured, the AI buttons are not rendered and a notice is shown in the toolbar. The rest of the documentation works normally.
Configuration
DocsPlusConfig
| Field | Default | Description |
|---|---|---|
docs_url |
"/docs" |
Documentation page path |
api_prefix |
"/_docs" |
Prefix for internal endpoints and static assets |
openapi_url |
"/openapi.json" |
URL for the OpenAPI spec |
swagger_ui_version |
"5.17.14" |
Swagger UI version for CDN URLs |
swagger_ui_js_url |
None |
Override the JS bundle URL |
swagger_ui_css_url |
None |
Override the CSS URL |
swagger_ui_js_integrity |
None |
SRI hash for the JS bundle |
swagger_ui_css_integrity |
None |
SRI hash for the CSS |
swagger_ui_parameters |
see below | Extra SwaggerUIBundle options; merged key-wise over the defaults, so you only need to pass the keys you want to change |
identities |
[] |
Identity dropdown options; empty hides the dropdown |
llm_model |
"gpt-4o-mini" |
LLM model name |
llm_base_url |
None |
API base URL (reads DOCS_PLUS_LLM_BASE_URL / OPENAI_BASE_URL) |
llm_api_key |
None |
API key (reads DOCS_PLUS_LLM_API_KEY / OPENAI_API_KEY) |
llm_temperature |
0.3 |
LLM temperature; +0.1 per history item, capped at 1.0 |
llm_timeout |
60.0 |
Per-call timeout in seconds |
max_schema_depth |
4 |
Maximum $ref inlining depth |
max_schema_chars |
60000 |
Max characters for schema + history; beyond this the request is rejected |
ai_cache_max_size |
5 |
Max cached results per operation; oldest evicted first |
Default swagger_ui_parameters:
{
"persistAuthorization": True,
"displayRequestDuration": True,
"docExpansion": "list",
"showExtensions": True,
"showCommonExtensions": True,
}
User-supplied values are merged key-wise over these defaults, so you only need to pass the keys you want to change; the remaining keys keep their default values.
HTTP endpoints
All are excluded from the OpenAPI document.
| Method | Path | Purpose |
|---|---|---|
| GET | {docs_url} |
Documentation HTML page |
| GET | {api_prefix}/static/* |
Frontend static assets |
| POST | {api_prefix}/api/ai/generate |
Generate and cache validated AI parameters |
| POST | {api_prefix}/api/ai/fill |
Retrieve next cached result (409 if empty) |
| GET | {api_prefix}/api/ai/cache |
Read cache counts for all operations |
| POST | {api_prefix}/api/pre-request |
Execute pre-request hooks and return patches |
Limitations
- Development use only. Pre-request hooks issue server-side credentials and AI calls consume tokens. Do not register enhanced docs in production.
static/adapter.jsis the sole bridge to Swagger UI internals. Upgradingswagger_ui_versionrequires regression testing of parameter and body filling. Two known pitfalls (already handled in the adapter):- Parameter values use
${in}.${name}.hash-${param.hashCode()}as storage keys; the hash comes from the raw parameter object. Objects fromoperationWithMeta()carryvalue/errorsand produce a different hash, causing writes to land on keys nobody reads. - When an operation is collapsed the
RequestBodycomponent is not mounted; on mount it overwrites values with auto-generated examples. The fill flow expands the operation first.
- Parameter values use
- AI cache is in-process memory. Per-operation queue, lost on restart. Multi-worker processes have independent caches — badge counts and round-robin order may be inconsistent across workers.
- Complex parameter type support. Scalars, enums, and arrays use native form controls. Deeply nested object-type query parameters are serialised as JSON strings and may not match the target API's deserialisation convention. Request bodies (JSON) are not affected.
Metadata
Release files for fastapi-docs-plus 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastapi_docs_plus-1.0.0.tar.gz | 447.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastapi_docs_plus-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 479.7 kB
Release files / fastapi_docs_plus-1.0.0.tar.gz
| Download URL | fastapi_docs_plus-1.0.0.tar.gz |
|---|---|
| Size | 447.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d2c9d214c038b6fad2f3baaeb9b4c1f7ac66c78c905394e23dbf1af5779f604e
|
|
BLAKE2b-256 checksum How to use checksums |
3c48fb21fa166479c9750910bb7399af52cc56ae08ce5afe49526e84294f63b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency logRelease files / fastapi_docs_plus-1.0.0-py3-none-any.whl
| Download URL | fastapi_docs_plus-1.0.0-py3-none-any.whl |
|---|---|
| Size | 32.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3471497d44e2b8e11b4b87919a175f482bcd1a3eeeaac2b5d05dbd76aed8ae9e
|
|
BLAKE2b-256 checksum How to use checksums |
9df3236eb8f42f5cf1f84bccbfb944a77ebd13ee2c98f4e203a1c5dbb8899a46
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 12, 2026.
Transparency log