azure-functions-openapi ships with a CLI entry point for generating OpenAPI output from decorated handlers.
pip install azure-functions-openapiThen verify:
azure-functions-openapi --helpCurrent command set:
generate: build OpenAPI spec in JSON or YAML
azure-functions-openapi generateBy default this prints JSON to stdout using:
- title:
API - version:
1.0.0 - OpenAPI version:
3.1.0
Generate JSON to stdout:
azure-functions-openapi generate --title "Todo API" --version "1.2.0"Generate YAML to stdout:
azure-functions-openapi generate --format yaml --title "Todo API"Write JSON to file:
azure-functions-openapi generate --output openapi.json --format jsonWrite YAML to file:
azure-functions-openapi generate --output openapi.yaml --format yamlFile output is written atomically, so a failed serialization or write leaves an
existing specification unchanged. --fail-on-warnings exits before file output
and therefore neither creates nor modifies the requested artifact.
Generate OpenAPI 3.1 output:
azure-functions-openapi generate --openapi-version 3.1 --output openapi-3.1.jsonImport and scan your function app so decorator routes use their real bindings:
azure-functions-openapi generate --app function_app:app --title "Todo API"Infer each operation's security requirement from its Azure Functions
auth_level binding:
azure-functions-openapi generate \
--app function_app:app \
--infer-auth-level \
--output openapi.json--infer-auth-level is opt-in and requires the scan-capable
--app module:variable form. ANONYMOUS routes remain public, while
FUNCTION and ADMIN routes use the AzureFunctionKey x-functions-key
scheme. Explicit operation security= metadata keeps precedence.
Pretty-print JSON output:
azure-functions-openapi generate --app function_app:app --pretty --output openapi.json@openapi-decorated routes register themselves when the module is imported,
but an innermost @openapi (below @app.route) runs before its route binding
exists. Therefore --app module:variable is required to recover the real route.
It is also required for producers that register only through the shared
endpoint-metadata namespace — @validate_http
(azure-functions-validation), azure-functions-langgraph, and others — because
their metadata is attached to the live FunctionApp object and discovered by
scanning it.
To include those endpoints, pass an explicit module:variable so the CLI can
resolve the FunctionApp instance and run discovery on it:
# Imports function_app, resolves `app`, then discovers BOTH decorator-
# registered and endpoint-metadata routes into a single spec.
azure-functions-openapi generate --app function_app:app --output openapi.json!!! warning "--app executes module-level code"
The CLI imports every --app module target, so all module-level code
runs, not only decorators. Use only trusted modules. Import-time effects can
include creating network clients or connections, environment-variable
validation that raises, file writes, global initialization, and
telemetry or logging side effects.
There is no sandbox: decorator registration and binding discovery require
executing normal Python imports, and the CLI does not isolate or restrict
them. Keep module-level code free of destructive side effects and provide
build-time-safe defaults, for example by guarding external initialization
with an environment flag when generating a spec in CI.
Discovery semantics:
--app function_app(module only): decorators fire, but binding discovery is not run. An innermost@openapitherefore emits anunresolved-routewarning and uses the historical function-name fallback. This warning remains non-fatal with--strict, because the fallback can be Azure's correct default route. The CLI never guesses theFunctionAppvariable name.--app function_app:app(module + variable): the namedFunctionAppattribute is resolved and scanned, merging endpoint-metadata routes into the same registry as the decorator routes.- Discovery is one-shot per run and uses the idempotent
FunctionBuilder.build()path. The CLI never callsFunctionApp.get_functions(), which is non-idempotent on all supported SDK versions (1.21–1.25): it accumulates intofunctions_bindingsand raises on a second call. - Blueprints (raw or registered via
register_functions/register_blueprint) are enumerated through the same path — no special casing is required. - Re-running discovery on the same app is safe: the registry merges by function id, so no duplicate paths or operations are produced.
| Option | Alias | Values | Default | Description |
|---|---|---|---|---|
--app |
- | module or module:var |
- | Import the module; use module:var to scan route bindings (required for innermost @openapi) and endpoint metadata |
--title |
- | any string | API |
OpenAPI info.title |
--version |
- | any string | 1.0.0 |
OpenAPI info.version |
--description |
- | any string | library default | OpenAPI info.description (Markdown / CommonMark supported) |
--output |
-o |
file path | stdout | Write generated content to file |
--format |
-f |
json, yaml |
json |
Output serialization format |
--openapi-version |
- | 3.0, 3.1, 3.2 |
3.1 |
OpenAPI schema version |
--pretty |
-p |
flag | false |
Pretty-print JSON output (adds indentation); no effect on YAML |
--route-prefix |
- | any string (or "") |
/api |
HTTP route prefix from host.json extensions.http.routePrefix. See Route Prefix. |
--fail-on-empty-paths |
- | flag | false |
Exit with code 1 if the generated spec has no paths |
--strict |
- | flag | false |
Fail on any malformed registry entry instead of skipping it. Recommended for CI where a missing path should break the build |
--fail-on-warnings |
- | flag | false |
Exit with code 2 if the generator emits any structured warnings (version skew, namespace fallback, or spec-validation issues; the advisory unresolved-route warning never fails). Use in CI to stop a wrong-but-plausible spec from being published |
--infer-auth-level |
- | flag | false |
Infer operation security from scanned Azure Functions auth_level bindings. Requires --app module:variable; explicit operation security wins |
--isolate-app |
- | flag | false |
Compatibility flag requiring --app module:variable; selecting a variable now always uses a fresh, app-scoped registry, even without this flag |
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Runtime or generation error (including --fail-on-empty-paths with no paths, or an --isolate-app that cannot be honored) |
2 |
Either invalid CLI arguments (argparse parse error) or structured warnings were emitted while --fail-on-warnings is set |
Use a validator in local checks and CI:
pip install openapi-spec-validator
openapi-spec-validator openapi.jsonFor YAML:
openapi-spec-validator openapi.yamlTwo different things can be checked in CI, and they are not the same recipe:
- Basic usage (above) has no
--apptarget. It is useful for trying the tool, but with no application imported the registry is empty:generatesucceeds withpaths: {}, and a schema validator accepts that empty document too. Neither command on its own proves anything about your actual API. - Application validation (below) imports your real
FunctionApp, fails the build on generation diagnostics, and fails the build if no paths were discovered — the recipe to use for CI that is meant to catch regressions in your documented API surface.
When passing --app in CI, first review the
--app import-execution warning:
import-time side effects (network clients, environment validation that
raises, and so on) must be build-time-safe, guarded behind an environment
flag if necessary.
name: OpenAPI Validation
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install tools and application dependencies
# Generating with --app imports your application module, so its own
# dependencies (azure-functions, any requirements.txt, etc.) must
# already be importable before the next step runs.
run: |
pip install azure-functions-openapi openapi-spec-validator
pip install -r requirements.txt
- name: Generate spec
# --app function_app:app: substitute your actual module path and
# FunctionApp variable name. Run from the directory function_app is
# importable from (or set PYTHONPATH), matching normal Python import
# resolution -- the CLI does not search for it.
#
# --strict --fail-on-warnings surface generation diagnostics (a
# malformed registry entry, version skew, a namespace fallback, a
# spec-validation issue) as a build failure. This is a different
# concern from the next step: these flags check how the spec was
# generated, not whether the resulting document is schema-valid.
#
# --fail-on-empty-paths is the guard this recipe depends on: without
# it, a forgotten --app target (or wrong module:variable) silently
# produces an empty-but-schema-valid document, and the next step
# (openapi-spec-validator) reports success on it.
run: |
azure-functions-openapi generate \
--app function_app:app \
--openapi-version 3.1 \
--strict \
--fail-on-warnings \
--fail-on-empty-paths \
--output openapi.json
- name: Validate spec
# Checks that the document is schema-valid OpenAPI -- it does not
# check that the document describes your application's endpoints;
# that is what --fail-on-empty-paths above is for.
run: openapi-spec-validator openapi.jsonTo deliberately generate (and validate) an empty document — for example, to
smoke-test the toolchain itself before any routes exist yet — omit --app
and --fail-on-empty-paths, matching the basic usage example.
- Confirm package installed in active environment
- Use
python -m azure_functions_openapi.cli --helpas fallback
-
Ensure app handlers are decorated with
@openapi -
Pass
--app <module>:<variable>so decorated routes and bindings are reconciled:azure-functions-openapi generate --app function_app:app --title "My API" -
Use
module:variablesyntax when theFunctionAppinstance is not namedapp:azure-functions-openapi generate --app function_app:my_app --title "My API" -
In CI, add
--fail-on-empty-pathsso a forgotten--apptarget or a wrongmodule:variablefails the build instead of silently producing (and validating) an empty document — see the CI example.
- Use only
--openapi-version 3.0,--openapi-version 3.1, or--openapi-version 3.2