Part of the Azure Functions Python DX Toolkit — dogfood-tested by azure-functions-cookbook-python.
Read this in: 한국어 | 日本語 | 简体中文
OpenAPI (Swagger) documentation and Swagger UI for the Azure Functions Python v2 programming model.
Native func.FunctionApp() HTTP handlers do not automatically get OpenAPI
documentation from the Azure Functions SDK alone:
- No auto-generated docs — you maintain OpenAPI specs by hand or not at all
- No Swagger UI — no browser-based API explorer for testing endpoints
- Hard to test — consumers rely on tribal knowledge or external tools to discover your API
- Spec drift — hand-written docs diverge from actual handler behavior over time
❌ Without azure-functions-openapi — maintain specs by hand
# openapi_spec.json — manually written, manually updated
{
"paths": {
"/api/users": {
"post": {
"summary": "Create user",
"requestBody": {"...": "..."},
"responses": {"200": {"...": "..."}},
}
}
}
}
# function_app.py — no connection to the spec above
@app.route(route="users", methods=["POST"])
def create_user(req): ...Spec drifts. Consumers guess. No Swagger UI.
✅ With azure-functions-openapi — spec lives next to the handler
from pydantic import BaseModel
class CreateUserRequest(BaseModel):
name: str
class UserResponse(BaseModel):
id: str
name: str
@openapi(
summary="Create user",
requests=CreateUserRequest,
responses=UserResponse,
)
@app.route(route="users", methods=["POST"])
def create_user(req): ...
# Auto-generated endpoints:
# GET /api/openapi.json — generated from registered metadata and bindings
# GET /api/docs — Swagger UI includedThe spec reflects metadata registered via @openapi or register_openapi_metadata
and discovered Azure Functions bindings. It does not verify that runtime request
parsing, status codes, or response bodies match unless a validation layer such as
azure-functions-validation is also applied. Swagger UI works out of the box.
@openapidecorator — attach operation metadata directly to your handler- Auto-generated spec —
/openapi.jsonand/openapi.yamlendpoints from decorated handlers - Swagger UI — built-in
/docsendpoint with security defaults - CLI tooling — generate specs at build time for CI validation
- Schema support — query, path, header, body, and response schemas
- Metadata inference — infer the
200response from a handler's return type (on by default, opt out withinfer_return_types=False), and opt intosummary/descriptioninference from its docstring withinfer_docstring=True; both are gap-fill-only and explicit-always-wins (see Usage Guide) auth_levelsecurity inference — opt-ininfer_auth_level=Truederives OpenAPI security from a route's Azure Functionsauth_level(see Usage Guide)
flowchart LR
DEC["@openapi decorator"] --> REG["OpenAPIRegistry"]
REG --> GEN["spec.generate_openapi_spec()"]
GEN --> EP["GET /api/openapi.json | .yaml"]
UI["render_swagger_ui()<br/>GET /api/docs"] -.->|browser fetches spec| EP
registry.py owns OpenAPIRegistry and its process-wide registry instance.
Decorators register metadata there; selected-app generation seeds a separate
registry with only that app's handlers. Swagger UI fetches the generated document
and does not read registry state directly.
FastAPI also runs on Azure Functions. The choice is the HTTP programming
model, not whether Azure Functions supports a web framework. See the official
FastAPI on Azure Functions sample
(archived reference) and the current
AsgiFunctionApp API.
| Concern | Native FunctionApp + this package |
FastAPI on AsgiFunctionApp |
|---|---|---|
| HTTP routing | Azure @app.route handlers |
FastAPI path operations behind the ASGI adapter |
| Documentation | @openapi metadata; explicitly register spec/UI routes |
FastAPI generates its own OpenAPI and docs routes |
| Bindings | Keep Azure input/output bindings on native handlers | FastAPI endpoints do not automatically receive native binding arguments; use SDK clients or explicit integration code |
| Middleware and DI | This package supplies neither ASGI middleware nor dependency injection | Keep FastAPI/Starlette middleware and FastAPI dependencies |
| Validation | Documentation only; optionally pair with azure-functions-validation |
FastAPI owns request parsing, validation and response-model serialization |
| Spec export | azure-functions-openapi generate |
Export the document returned by the FastAPI app's openapi() method |
When to use: choose this package when your handlers already use native
FunctionApp decorators and bindings and you want documentation without replacing
that model. When not to use: keep FastAPI on AsgiFunctionApp for an existing
FastAPI app, or when its middleware, dependency injection and integrated validation
are the desired application model. This package does not inspect FastAPI's nested
router or replace its OpenAPI generator. ASGI hosting does not preclude separate
native functions, but bridging their bindings into FastAPI endpoints is not automatic.
This is the same documentation-versus-runtime-validation boundary described for the optional Validation companion below. Neither approach is claimed to have lower cold-start or import time; no comparable benchmark is provided here.
- Azure Functions Python v2 programming model
- Decorator-based
func.FunctionApp()applications - HTTP-triggered functions documented with
@openapi - Pydantic schema generation (requires Pydantic v2)
This package does not support the legacy function.json-based v1 programming model.
This package does not own:
- Runtime exposure or graph deployment — use
azure-functions-langgraph - Request/response validation or serialization — use
azure-functions-validation - Project scaffolding — use
azure-functions-scaffold
Generate an OpenAPI spec from your decorated function app:
# Install
pip install azure-functions-openapi
# Generate spec from a function app module (registers @openapi routes)
azure-functions-openapi generate --app function_app --title "My API" --format json
# Write to file with pretty-printing
azure-functions-openapi generate --app function_app --title "My API" --pretty --output openapi.json
# YAML output
azure-functions-openapi generate --app function_app --format yaml --output openapi.yamlPass module:variable to resolve and scan the completed FunctionApp. This is
required when @openapi is below @app.route, because the inner decorator runs
before the route binding exists. The scan also discovers endpoint-metadata routes
from producers like @validate_http or azure-functions-langgraph. With
module alone, routes that cannot be verified emit a non-fatal
unresolved-route warning and fall back to the function name. This warning
remains non-fatal under strict=True, because the fallback can be Azure's
correct default route:
azure-functions-openapi generate --app function_app:app --title "My API"Selecting module:variable scopes generation to that app's reachable handlers.
Likewise, the Python facades' app=app uses a fresh OpenAPIRegistry by default,
without changing the process-wide registry. Unrelated decorated handlers and
unbound programmatic metadata are excluded. For deliberate aggregation, omit
app (CLI: use a module without :variable), or supply an explicit registry=
to the Python facade; that registry remains caller-owned and additive.
See the CLI Guide for all options, CI integration examples, and
the warning that --app imports execute module-level code.
pip install azure-functions-openapiYour Function App dependencies should include:
azure-functions
azure-functions-openapi
This package discovers routes, methods, and handlers from the azure-functions SDK through a single isolated adapter (azure_functions_openapi.adapters). Discovery is public-API-first: it enumerates via the public, idempotent FunctionBuilder.build() and reads everything else through public Function accessors (get_function_name / get_user_function / get_bindings / is_http_function). The adapter never calls the non-idempotent FunctionApp.get_functions(). The one unavoidable private token — app._function_builders, which has no public substitute for enumeration — lives exclusively in the adapter and is covered by a mandatory guard test. We validate the package against an explicit matrix in CI. See issue #258 and issue #327 for background.
azure-functions |
Python 3.11 | Python 3.12 | Python 3.13 | Python 3.14 |
|---|---|---|---|---|
1.21.0 (floor) |
✅ tested | |||
1.24.0 |
✅ tested | |||
latest 1.x |
✅ tested | ✅ tested | ||
2.x (>=2,<3) |
✅ tested | ✅ tested |
The version pin in pyproject.toml is interpreter-aware:
azure-functions>=1.21.0,<2.0.0 on Python < 3.13, and azure-functions>=1.21.0,<3.0.0
on Python 3.13+. The <3.0.0 ceiling keeps an uncertified azure-functions 3.x
out. The floor is 1.21.0 because earlier releases return None from
FunctionBuilder.__call__ (breaking direct invocation of
decorated handlers in tests and CLI extraction). The split exists because
azure-functions 2.x drops support for Python < 3.13, so the 2.x line is only
installable — and only offered — on Python 3.13+. The 2.x path is proven by a
dedicated wheel-based compatibility matrix in CI (real Python 3.13 and 3.14
interpreters) and certified against real Azure — a Python 3.13 Function App
deployed on a Flex Consumption plan in koreacentral. See
issue #528
and issue #488
for the cap-lift work.
import json
import azure.functions as func
from pydantic import BaseModel
from azure_functions_openapi import (
get_openapi_json,
get_openapi_yaml,
openapi,
register_openapi_routes,
render_swagger_ui,
)
app = func.FunctionApp()
# Describe your API with plain Pydantic models.
class GreetRequest(BaseModel):
name: str
class GreetResponse(BaseModel):
message: str
# @openapi infers the route and method from the @app.route below —
# no need to repeat them here.
@openapi(
summary="Greet user",
tags=["Example"],
requests=GreetRequest,
responses=GreetResponse,
)
@app.route(route="http_trigger", auth_level=func.AuthLevel.ANONYMOUS, methods=["POST"])
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
# @openapi documents the request/response contract — it does not validate.
# For runtime validation, see azure-functions-validation.
data = req.get_json()
name = data.get("name", "world")
return func.HttpResponse(
json.dumps({"message": f"Hello, {name}!"}),
mimetype="application/json",
)Pydantic v2 is a required dependency (
pydantic>=2.0,<3.0) and is installed with this package.requests=/responses=accept Pydantic models (the recommended path) or raw JSON Schema dicts (see below) — so you can describe schemas without authoring Pydantic models, but Pydantic itself is always present.
Want runtime validation too? (optional)
@openapidocuments your Pydantic models — it does not parse or validate requests at runtime. If you also want automatic request parsing, consistent400/422error responses, and response-model enforcement, layerazure-functions-validation's@validate_httpon the same Pydantic models. It is an optional companion, not a dependency — this package works fully on its own. See the validation README for details, or a full runnable example that pairs both on one resource: Full Stack CRUD API.
Want structured logging too? (optional) Pair your documented endpoints with
azure-functions-loggingfor structured, correlation-aware logs and observability. It attaches request/response context to every log line without changing your@openapicontracts. Like validation, it is an optional companion, not a dependency — this package works fully on its own. See the logging README for details.
Register the three documentation endpoints explicitly with one call:
register_openapi_routes(app, title="Sample API", version="1.0.0")This opt-in helper adds ordinary routes to your existing FunctionApp; it does
not replace Azure Functions routing or register anything at import by itself.
Manual alternative: wire up openapi.json / openapi.yaml / docs
# Serve the generated spec and Swagger UI as ordinary HTTP routes.
@app.route(route="openapi.json", auth_level=func.AuthLevel.ANONYMOUS, methods=["GET"])
def openapi_json(req: func.HttpRequest) -> func.HttpResponse:
return func.HttpResponse(
get_openapi_json(
title="Sample API",
description="OpenAPI document for the Sample API.",
app=app,
),
mimetype="application/json",
)
@app.route(route="openapi.yaml", auth_level=func.AuthLevel.ANONYMOUS, methods=["GET"])
def openapi_yaml(req: func.HttpRequest) -> func.HttpResponse:
return func.HttpResponse(
get_openapi_yaml(
title="Sample API",
description="OpenAPI document for the Sample API.",
app=app,
),
mimetype="application/x-yaml",
)
@app.route(route="docs", auth_level=func.AuthLevel.ANONYMOUS, methods=["GET"])
def swagger_ui(req: func.HttpRequest) -> func.HttpResponse:
return render_swagger_ui()The anonymous documentation routes above are intended for local development; review production exposure and protection options before deployment.
For key-protected docs, explicitly opt into browser-side header forwarding:
register_openapi_routes(
app,
title="Private API",
version="1.0.0",
auth_level=func.AuthLevel.FUNCTION,
docs_key_forwarding="header",
)The page key is not echoed into HTML or the spec URL; the browser removes code
from its address bar and sends x-functions-key only on the same-origin spec
download, never Try-it-out. A redirect on this key-bearing spec request fails
closed with Swagger UI's normal load error; the destination is not requested.
The initial navigation URL can still reach access logs before replacement.
Browser navigation timing (performance.getEntriesByType("navigation")[0].name)
retains the original ?code= URL after history.replaceState in page-lifetime
memory. The key must authorize both functions. Prefer edge
authentication in production. The default None retains legacy query forwarding
with a DeprecationWarning for protected registrations; select "header",
"none" (no forwarding), or explicitly "query" (legacy exposure). The default
will become "header" in a later minor release before 1.0, not in the release that
first warns; see Protecting documentation routes.
Advanced: describe the schema with raw JSON Schema instead of Pydantic
@openapi(
summary="Greet user",
tags=["Example"],
requests={
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
responses={
200: {
"description": "Successful greeting",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {"message": {"type": "string"}},
}
}
},
}
},
)
@app.route(route="http_trigger", auth_level=func.AuthLevel.ANONYMOUS, methods=["POST"])
def http_trigger(req: func.HttpRequest) -> func.HttpResponse: ...Run locally with Azure Functions Core Tools:
func startAfter deploying (see docs/deployment.md), the same request produces the same response in both environments.
curl -s http://localhost:7071/api/http_trigger \
-H "Content-Type: application/json" \
-d '{"name": "World"}'{"message": "Hello, World!"}curl -s "https://<your-app>.azurewebsites.net/api/http_trigger" \
-H "Content-Type: application/json" \
-d '{"name": "World"}'{"message": "Hello, World!"}The /api/openapi.json, /api/openapi.yaml, and /api/docs endpoints are also available in both environments.
Verified against a temporary Azure Functions deployment in koreacentral on a Flex Consumption plan (certified on Python 3.13). Response captured and URL anonymized.
The representative webhook_receiver example shows the full outcome of adopting this library:
- You annotate an Azure Functions v2 HTTP handler with
@openapi. - The package generates a real OpenAPI document for that route.
- The same route is rendered in Swagger UI for browser-based inspection.
The generated OpenAPI file is captured as a static preview from the same example run, so the README shows the actual document produced by the representative function.
The web preview below is generated from the same representative example and captured automatically from the rendered Swagger UI page produced by that example flow.
- You have HTTP-triggered Azure Functions and need API documentation
- You want Swagger UI for browser-based API testing
- You need OpenAPI specs for client code generation or CI validation
- You want generated docs to reflect registered endpoint metadata and discovered bindings
- Full docs: yeongseon.dev/azure-functions-python/openapi
- Smoke-tested examples:
examples/ - Installation Guide
- Usage Guide
- API Reference
- CLI Guide
- Extend this: Producer Guide — write your own endpoint metadata
This package is part of the Azure Functions Python DX Toolkit.
Design principle: azure-functions-openapi owns API documentation and spec generation. azure-functions-validation owns request/response validation and serialization. azure-functions-langgraph owns LangGraph runtime exposure.
| Package | Role |
|---|---|
| azure-functions-openapi-python | OpenAPI spec generation and Swagger UI |
| azure-functions-validation-python | Request/response validation and serialization |
| azure-functions-db-python | SQLAlchemy-powered DB integration helpers (poll-based pseudo trigger, input/output/client injection) |
| azure-functions-langgraph-python | LangGraph deployment adapter for Azure Functions |
| azure-functions-scaffold-python | Project scaffolding CLI |
| azure-functions-logging-python | Structured logging and observability |
| azure-functions-doctor-python | Pre-deploy diagnostic CLI |
| azure-functions-cookbook-python | Dogfood examples — runnable recipes that exercise the full toolkit |
Contributor verification: Chromium key-forwarding lane and local prerequisites.
This repository includes llms.txt and llms-full.txt in the root directory.
These files provide comprehensive package and API information optimized for LLM context windows.
llms.txt— Quick reference with core API, installation, and quick-start examplellms-full.txt— Complete reference with full signatures, patterns, design principles, and ecosystem context
Use these files to get better context when working with this package in AI-assisted coding environments.
We welcome contributions! This package is maintained as open source and improves through community involvement.
Getting started: See the Contributing Guide for setup, testing, and pull request workflows.
Find an issue to work on:
- Good first issue — scoped for first-time contributors. These are bounded, independently verifiable, and include clear acceptance criteria.
- Help wanted — community contributions welcomed. These may require more familiarity with the codebase or Azure Functions.
Before you start: Please comment on the issue to discuss it first. This helps clarify ownership, expectations, and scope before you begin work.
Contributions we welcome:
- Bug fixes and feature implementations (when labeled with
good first issueorhelp wanted) - Documentation improvements and examples (even when no label exists)
- Test coverage and diagnostic improvements
No pull request without an issue: Open or reference an issue first — this keeps work coordinated and prevents duplicate effort.
This project is an independent community project and is not affiliated with, endorsed by, or maintained by Microsoft.
Azure and Azure Functions are trademarks of Microsoft Corporation.
MIT

