Skip to content

API Reference

This page documents the public runtime API exposed by azure-functions-openapi.

Import from package root

All symbols below are exported from azure_functions_openapi.__init__, so you can import from azure_functions_openapi directly.

from azure_functions_openapi import (
    OPENAPI_VERSION_3_0,
    OPENAPI_VERSION_3_1,
    OpenAPIOperationMetadata,
    OpenAPISpecConfigError,
    clear_openapi_registry,
    generate_openapi_spec,
    get_openapi_json,
    get_openapi_yaml,
    openapi,
    register_openapi_metadata,
    render_swagger_ui,
    scan_validation_metadata,
)

Public API surface

Symbol Kind Purpose
openapi decorator Attach operation metadata to function handlers
register_openapi_metadata function Register metadata for dynamically-created endpoints
clear_openapi_registry function Remove all entries from the registry
scan_validation_metadata function Auto-discover validation metadata from @validate_http handlers
generate_openapi_spec function Build OpenAPI dictionary from decorator registry
get_openapi_json function Build OpenAPI and serialize to JSON string
get_openapi_yaml function Build OpenAPI and serialize to YAML string
render_swagger_ui function Return Swagger UI HttpResponse
OpenAPIOperationMetadata dataclass Frozen dataclass for operation metadata
OpenAPISpecConfigError exception Raised for configuration errors
OPENAPI_VERSION_3_0 constant OpenAPI version string "3.0.0"
OPENAPI_VERSION_3_1 constant OpenAPI version string "3.1.0"
## Decorator behavior model

@openapi stores metadata in a thread-safe registry and the spec functions read from that registry to generate output.

@openapi metadata ---------> internal registry --> generate_openapi_spec --> JSON/YAML endpoint
                                                    @validate_http metadata --> scan_validation_metadata(app) --^       `--> render_swagger_ui (docs)

Note

get_openapi_json() and get_openapi_yaml() return strings, not HttpResponse. Wrap the returned value in func.HttpResponse in your Azure Function route.

Common usage patterns

Minimal endpoint

@app.route(route="ping", methods=["GET"])
@openapi(summary="Ping", description="Health check endpoint")
def ping(req: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse("ok", status_code=200)

With Pydantic request and response

class CreateItemRequest(BaseModel):
    name: str


class ItemResponse(BaseModel):
    id: int
    name: str


@app.route(route="items", methods=["POST"])
@openapi(
    summary="Create item",
    method="post",
    route="/api/items",
    request_model=CreateItemRequest,
    response_model=ItemResponse,
    response={201: {"description": "Created"}},
)
def create_item(req: func.HttpRequest) -> func.HttpResponse:
    ...

With raw schema dictionaries

@app.route(route="raw", methods=["POST"])
@openapi(
    summary="Raw schema example",
    method="post",
    request_body={
        "type": "object",
        "properties": {"value": {"type": "string"}},
        "required": ["value"],
    },
    response={
        200: {
            "description": "OK",
            "content": {
                "application/json": {
                    "schema": {
                        "type": "object",
                        "properties": {"accepted": {"type": "boolean"}},
                    }
                }
            },
        }
    },
)
def raw(req: func.HttpRequest) -> func.HttpResponse:
    ...

Expose OpenAPI + Swagger routes

@app.route(route="openapi.json", methods=["GET"])
def openapi_json(req: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse(get_openapi_json(title="My API", version="1.0.0"), mimetype="application/json")


@app.route(route="openapi.yaml", methods=["GET"])
def openapi_yaml(req: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse(get_openapi_yaml(title="My API", version="1.0.0"), mimetype="application/x-yaml")


@app.route(route="docs", methods=["GET"])
def docs(req: func.HttpRequest) -> func.HttpResponse:
    return render_swagger_ui(title="My API Docs", openapi_url="/api/openapi.json")

mkdocstrings reference

The sections below are generated directly from source docstrings.

openapi

Decorator that attaches OpenAPI metadata to an Azure Functions handler.

Examples

1 · Minimal “Hello World”

@app.route(route="hello")
@openapi(summary="Hello", description="Returns plain text.")
def hello(req: func.HttpRequest) -> func.HttpResponse:
    return func.HttpResponse("Hello, world!", status_code=200)

2 · Pydantic-powered JSON API

from pydantic import BaseModel

class TodoRequest(BaseModel):
    title: str
    done: bool = False

class TodoResponse(BaseModel):
    id: int
    title: str
    done: bool

@app.route(route="todos/{id}", method="put")
@openapi(
    summary="Update a todo item",
    description="""Update a todo and return the updated document.""",
    tags=["Todo"],
    parameters=[{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
    request_model=TodoRequest,
    response_model=TodoResponse,
    operation_id="updateTodo",
)
def update_todo(req: func.HttpRequest) -> func.HttpResponse:
    # ... business logic ...
    body = TodoRequest.model_validate_json(req.get_body())
    todo = TodoResponse(id=1, **body.model_dump())
    return func.HttpResponse(
        todo.model_dump_json(),
        status_code=200,
        mimetype="application/json",
    )

After starting the Function App you get:

  • Swagger UI → http://localhost:7071/api/docs
  • Raw JSON spec → http://localhost:7071/api/openapi.json

Parameters

summary: Short description shown in Swagger UI. description: Longer Markdown-enabled description. tags: List of group tags. operation_id: Custom operationId (defaults to function name). route: Override for the HTTP route path (e.g. "/items/{id}"). method: Explicit HTTP method if not inferrable. parameters: List of param objects (query/path/header/cookie). security: List of OpenAPI Security Requirement Objects. Example: [{"BearerAuth": []}] security_scheme: Security scheme definitions to include in components.securitySchemes. Example: {"BearerAuth": {"type": "http", "scheme": "bearer"}} request_model: Pydantic model used to derive requestBody schema. request_body: Raw requestBody schema (if you don't use Pydantic). requests: Unified request parameter that accepts either a Pydantic model class (equivalent to request_model) or a raw requestBody schema dict (equivalent to request_body). request_body_required: Whether the request body is required. Defaults to True. response_model: Pydantic model used to derive 200-response schema. response: Manual responses dict keyed by status code. responses: Unified response parameter that accepts either a Pydantic model class (equivalent to response_model) or a manual responses dict keyed by status code (equivalent to response).

Returns

Callable The original function, with its name stored in _openapi_registry.

Source code in src/azure_functions_openapi/decorator.py
def openapi(
    # ── basic metadata ───────────────────────────────────────────
    summary: str = "",
    description: str = "",
    tags: list[str] | None = None,
    operation_id: str | None = None,
    # ── routing information ─────────────────────────────────────
    route: str | None = None,
    method: str | None = None,
    parameters: list[dict[str, Any]] | None = None,
    security: list[dict[str, list[str]]] | None = None,
    security_scheme: dict[str, dict[str, Any]] | None = None,
    # ── request / response schema ───────────────────────────────
    request_model: type[BaseModel] | None = None,
    request_body: dict[str, Any] | None = None,
    requests: type[BaseModel] | dict[str, Any] | None = None,
    request_body_required: bool = True,
    response_model: type[BaseModel] | None = None,
    response: dict[int, dict[str, Any]] | None = None,
    responses: type[BaseModel] | dict[int, dict[str, Any]] | None = None,
) -> Callable[[F], F]:
    """
    Decorator that attaches OpenAPI metadata to an Azure Functions handler.

    Examples
    --------
    ### 1 · Minimal “Hello World”

    ```python
    @app.route(route="hello")
    @openapi(summary="Hello", description="Returns plain text.")
    def hello(req: func.HttpRequest) -> func.HttpResponse:
        return func.HttpResponse("Hello, world!", status_code=200)
    ```

    ### 2 · Pydantic-powered JSON API

    ```python
    from pydantic import BaseModel

    class TodoRequest(BaseModel):
        title: str
        done: bool = False

    class TodoResponse(BaseModel):
        id: int
        title: str
        done: bool

    @app.route(route="todos/{id}", method="put")
    @openapi(
        summary="Update a todo item",
        description=\"""Update a todo and return the updated document.\""",
        tags=["Todo"],
        parameters=[{"name": "id", "in": "path", "required": True, "schema": {"type": "integer"}}],
        request_model=TodoRequest,
        response_model=TodoResponse,
        operation_id="updateTodo",
    )
    def update_todo(req: func.HttpRequest) -> func.HttpResponse:
        # ... business logic ...
        body = TodoRequest.model_validate_json(req.get_body())
        todo = TodoResponse(id=1, **body.model_dump())
        return func.HttpResponse(
            todo.model_dump_json(),
            status_code=200,
            mimetype="application/json",
        )
    ```

    After starting the Function App you get:

    * **Swagger UI** → `http://localhost:7071/api/docs`
    * **Raw JSON spec** → `http://localhost:7071/api/openapi.json`

    Parameters
    ----------
    summary:
        Short description shown in Swagger UI.
    description:
        Longer Markdown-enabled description.
    tags:
        List of group tags.
    operation_id:
        Custom operationId (defaults to function name).
    route:
        Override for the HTTP route path (e.g. "/items/{id}").
    method:
        Explicit HTTP method if not inferrable.
    parameters:
        List of param objects (query/path/header/cookie).
    security:
        List of OpenAPI Security Requirement Objects.
        Example: [{"BearerAuth": []}]
    security_scheme:
        Security scheme definitions to include in components.securitySchemes.
        Example: {"BearerAuth": {"type": "http", "scheme": "bearer"}}
    request_model:
        Pydantic model used to derive requestBody schema.
    request_body:
        Raw requestBody schema (if you don't use Pydantic).
    requests:
        Unified request parameter that accepts either a Pydantic model class
        (equivalent to `request_model`) or a raw requestBody schema dict
        (equivalent to `request_body`).
    request_body_required:
        Whether the request body is required. Defaults to True.
    response_model:
        Pydantic model used to derive 200-response schema.
    response:
        Manual responses dict keyed by status code.
    responses:
        Unified response parameter that accepts either a Pydantic model class
        (equivalent to `response_model`) or a manual responses dict keyed by
        status code (equivalent to `response`).

    Returns
    -------
    Callable
        The original function, with its name stored in `_openapi_registry`.
    """

    def decorator(func: F) -> F:
        target_name = getattr(func, "__qualname__", getattr(func, "__name__", "<unknown>"))
        try:
            original_func, metadata_func = _resolve_metadata_target(func)
            target_name = f"{metadata_func.__module__}.{metadata_func.__qualname__}"

            # Enhanced input validation and sanitization
            validated_route = _validate_and_sanitize_route(route, metadata_func.__name__)
            sanitized_operation_id = _validate_and_sanitize_operation_id(
                operation_id, metadata_func.__name__
            )
            validated_parameters = _validate_parameters(parameters, metadata_func.__name__)
            validated_security = _validate_security(security, metadata_func.__name__)
            validated_security_scheme = _validate_security_scheme(
                security_scheme, metadata_func.__name__
            )
            validated_tags = _validate_tags(tags, metadata_func.__name__)

            resolved_request_model = request_model
            resolved_request_body = request_body
            resolved_response_model = response_model
            resolved_response = response

            if requests is not None:
                if request_model is not None or request_body is not None:
                    raise ValueError(
                        "Cannot provide both 'requests' and 'request_model'/'request_body'."
                    )
                if isinstance(requests, dict):
                    resolved_request_body = requests
                elif isinstance(requests, type) and issubclass(requests, BaseModel):
                    resolved_request_model = requests
                else:
                    raise ValueError(
                        "'requests' must be either a Pydantic BaseModel subclass or a dictionary."
                    )

            if responses is not None:
                if response_model is not None or response is not None:
                    raise ValueError(
                        "Cannot provide both 'responses' and 'response_model'/'response'."
                    )
                if isinstance(responses, dict):
                    resolved_response = responses
                elif isinstance(responses, type) and issubclass(responses, BaseModel):
                    resolved_response_model = responses
                else:
                    raise ValueError(
                        "'responses' must be either a Pydantic BaseModel subclass or a dictionary."
                    )

            # Validate request/response models
            _validate_models(
                resolved_request_model,
                resolved_response_model,
                metadata_func.__name__,
            )

            function_id = f"{metadata_func.__module__}.{metadata_func.__qualname__}"

            with _registry_lock:
                registry_key = metadata_func.__name__
                existing = _openapi_registry.get(registry_key)
                if existing and existing.get("_function_id") != function_id:
                    existing_id = existing.get("_function_id")
                    if isinstance(existing_id, str):
                        # Preserve displaced entry under its fully-qualified id
                        _openapi_registry.setdefault(existing_id, existing)

                _openapi_registry[registry_key] = {
                    # ── basic metadata ────────────────────────────────────────
                    "summary": summary,
                    "description": description,
                    "tags": validated_tags,
                    "operation_id": sanitized_operation_id,
                    # ── routing info ─────────────────────────────────────────
                    "route": validated_route,
                    "method": method,
                    "parameters": validated_parameters,
                    "security": validated_security,
                    "security_scheme": validated_security_scheme,
                    # ── request / response schema ────────────────────────
                    "request_model": resolved_request_model,
                    "request_body": resolved_request_body,
                    "request_body_required": request_body_required,
                    "response_model": resolved_response_model,
                    "response": resolved_response or {},
                    "function_name": metadata_func.__name__,
                    "_function_id": function_id,
                }

            logger.debug(f"Registered OpenAPI metadata for function '{metadata_func.__name__}'")
            return cast(F, original_func)

        except OpenAPISpecConfigError as e:
            logger.error(f"Failed to register OpenAPI metadata for '{target_name}': {str(e)}")
            raise
        except ValueError as e:
            logger.error(f"Failed to register OpenAPI metadata for '{target_name}': {str(e)}")
            raise
        except Exception as e:
            logger.error(f"Failed to register OpenAPI metadata for '{target_name}': {str(e)}")
            raise RuntimeError(
                f"Failed to register OpenAPI metadata for '{target_name}': {e}"
            ) from e

    return decorator

generate_openapi_spec

Compile an OpenAPI specification from the registry.

Parameters:

Name Type Description Default
title str

API title

'API'
version str

API version

'1.0.0'
openapi_version str

OpenAPI specification version ("3.0.0" or "3.1.0")

OPENAPI_VERSION_3_0
description str

Description for the OpenAPI info object

DEFAULT_OPENAPI_INFO_DESCRIPTION
security_schemes dict[str, dict[str, Any]] | None

Security scheme definitions for components.securitySchemes. Example: {"BearerAuth": {"type": "http", "scheme": "bearer"}}

None
route_prefix str

HTTP route prefix from host.json (extensions.http.routePrefix). Defaults to "/api". Pass "" for hosts that disable the prefix or a custom value such as "/v1". Routes that already start with the prefix are not re-prefixed.

DEFAULT_ROUTE_PREFIX

Returns:

Type Description
dict[str, Any]

OpenAPI specification dictionary

Source code in src/azure_functions_openapi/spec.py
def generate_openapi_spec(
    title: str = "API",
    version: str = "1.0.0",
    openapi_version: str = OPENAPI_VERSION_3_0,
    description: str = DEFAULT_OPENAPI_INFO_DESCRIPTION,
    security_schemes: dict[str, dict[str, Any]] | None = None,
    route_prefix: str = DEFAULT_ROUTE_PREFIX,
) -> dict[str, Any]:
    """
    Compile an OpenAPI specification from the registry.

    Parameters:
        title: API title
        version: API version
        openapi_version: OpenAPI specification version ("3.0.0" or "3.1.0")
        description: Description for the OpenAPI info object
        security_schemes: Security scheme definitions for components.securitySchemes.
            Example: {"BearerAuth": {"type": "http", "scheme": "bearer"}}
        route_prefix: HTTP route prefix from ``host.json``
            (``extensions.http.routePrefix``). Defaults to ``"/api"``. Pass
            ``""`` for hosts that disable the prefix or a custom value such
            as ``"/v1"``. Routes that already start with the prefix are not
            re-prefixed.

    Returns:
        OpenAPI specification dictionary
    """
    if openapi_version not in (OPENAPI_VERSION_3_0, OPENAPI_VERSION_3_1):
        raise OpenAPISpecConfigError(
            f"Unsupported OpenAPI version: {openapi_version}. Supported: "
            f"{OPENAPI_VERSION_3_0}, {OPENAPI_VERSION_3_1}"
        )

    normalized_prefix = normalize_route_prefix(route_prefix)

    try:
        registry = get_openapi_registry()
        paths: dict[str, dict[str, Any]] = {}
        components: dict[str, Any] = {"schemas": {}}

        for func_name, meta in registry.items():
            try:
                logical_name = meta.get("function_name") or func_name
                # route & method --------------------------------------------------
                raw_path = f"/{(meta.get('route') or logical_name).lstrip('/')}"
                path = apply_route_prefix(raw_path, normalized_prefix)
                method = (meta.get("method") or "get").lower()

                # responses -------------------------------------------------------
                responses: dict[str, Any] = {}
                for status, detail in meta.get("response", {}).items():
                    resp = dict(detail)
                    resp.setdefault("description", "")
                    responses[str(status)] = resp

                if meta.get("response_model"):
                    try:
                        model_schema = model_to_schema(meta["response_model"], components)
                        target_status = "200"
                        for status_key in responses:
                            if str(status_key).startswith("2"):
                                target_status = str(status_key)
                                break

                        if target_status not in responses:
                            responses[target_status] = {
                                "description": "Successful Response",
                                "content": {"application/json": {"schema": model_schema}},
                            }
                        else:
                            content = responses[target_status].setdefault("content", {})
                            if not isinstance(content, dict):
                                content = {}
                                responses[target_status]["content"] = content

                            json_content = content.setdefault("application/json", {})
                            if not isinstance(json_content, dict):
                                json_content = {}
                                content["application/json"] = json_content

                            json_content.setdefault("schema", model_schema)
                    except Exception as e:
                        logger.warning(
                            f"Failed to generate response schema for {func_name}: {str(e)}"
                        )
                        _ensure_default_response(responses)

                _ensure_default_response(responses)

                # operation object ------------------------------------------------
                op: dict[str, Any] = {
                    "summary": meta.get("summary", ""),
                    "description": meta.get("description", ""),
                    "operationId": meta.get("operation_id") or f"{method}_{logical_name}",
                    "tags": meta.get("tags") or ["default"],
                    "responses": responses,
                }

                # parameters ------------------------------------------------------
                parameters: list[dict[str, Any]] = meta.get("parameters", [])
                if parameters:
                    op["parameters"] = parameters

                # security --------------------------------------------------------
                security: list[dict[str, list[str]]] = meta.get("security", [])
                if security:
                    op["security"] = security

                # requestBody (POST/PUT/PATCH/DELETE) --------------------------
                if method in {"post", "put", "patch", "delete"}:
                    required = meta.get("request_body_required", True)
                    if meta.get("request_body"):
                        op["requestBody"] = {
                            "required": required,
                            "content": {"application/json": {"schema": meta["request_body"]}},
                        }
                    elif meta.get("request_model"):
                        try:
                            op["requestBody"] = {
                                "required": required,
                                "content": {
                                    "application/json": {
                                        "schema": model_to_schema(meta["request_model"], components)
                                    }
                                },
                            }
                        except Exception as e:
                            logger.warning(
                                f"Failed to generate request schema for {func_name}: {str(e)}"
                            )
                            op["requestBody"] = {
                                "required": required,
                                "content": {"application/json": {"schema": {"type": "object"}}},
                            }

                # merge into paths (support multiple methods per route) ----------
                paths.setdefault(path, {})[method] = op

            except (KeyError, TypeError, ValueError):
                logger.exception("Failed to process function %s", func_name)
                # Continue processing other functions
                continue

        spec: dict[str, Any] = {
            "openapi": openapi_version,
            "info": {
                "title": title,
                "version": version,
                "description": description,
            },
            "paths": paths,
        }

        if openapi_version == OPENAPI_VERSION_3_1:
            spec["info"]["summary"] = title

        # Merge security schemes: explicit param + per-operation schemes from registry.
        # Raises OpenAPISpecConfigError on collision (same name, different definition).
        all_security_schemes: dict[str, dict[str, Any]] = {}
        if security_schemes:
            all_security_schemes.update(security_schemes)
        for _fn, meta in registry.items():
            scheme = meta.get("security_scheme")
            if isinstance(scheme, dict):
                for name, definition in scheme.items():
                    if name in all_security_schemes and all_security_schemes[name] != definition:
                        raise OpenAPISpecConfigError(
                            f"Conflicting security scheme definition for '{name}': "
                            f"existing={all_security_schemes[name]!r}, "
                            f"new={definition!r}"
                        )
                    all_security_schemes[name] = definition

        if all_security_schemes:
            components["securitySchemes"] = all_security_schemes

        if components.get("schemas"):
            if openapi_version == OPENAPI_VERSION_3_1:
                components["schemas"] = _convert_schemas_to_3_1(components["schemas"])

        if components.get("schemas") or components.get("securitySchemes"):
            spec["components"] = components

        spec = _normalize_spec_output(spec)

        logger.info(
            f"Generated OpenAPI {openapi_version} spec with {len(paths)} paths "
            f"for {len(registry)} functions"
        )
        return spec

    except OpenAPISpecConfigError:
        raise
    except Exception as e:
        logger.error(f"Failed to generate OpenAPI specification: {str(e)}")
        raise RuntimeError("Failed to generate OpenAPI specification") from e

get_openapi_json

Return the spec as pretty-printed JSON (UTF-8).

Parameters:

Name Type Description Default
title str

API title

'API'
version str

API version

'1.0.0'
openapi_version str

OpenAPI specification version ("3.0.0" or "3.1.0")

OPENAPI_VERSION_3_0
description str

Description for the OpenAPI info object

DEFAULT_OPENAPI_INFO_DESCRIPTION
security_schemes dict[str, dict[str, Any]] | None

Security scheme definitions for components.securitySchemes.

None
route_prefix str

HTTP route prefix from host.json (extensions.http.routePrefix). Defaults to "/api". Pass "" for hosts that disable the prefix or a custom value such as "/v1".

DEFAULT_ROUTE_PREFIX

Returns:

Type Description
str

OpenAPI spec in JSON format.

Source code in src/azure_functions_openapi/spec.py
def get_openapi_json(
    title: str = "API",
    version: str = "1.0.0",
    openapi_version: str = OPENAPI_VERSION_3_0,
    description: str = DEFAULT_OPENAPI_INFO_DESCRIPTION,
    security_schemes: dict[str, dict[str, Any]] | None = None,
    route_prefix: str = DEFAULT_ROUTE_PREFIX,
) -> str:
    """Return the spec as pretty-printed JSON (UTF-8).

    Parameters:
        title: API title
        version: API version
        openapi_version: OpenAPI specification version ("3.0.0" or "3.1.0")
        description: Description for the OpenAPI info object
        security_schemes: Security scheme definitions for components.securitySchemes.
        route_prefix: HTTP route prefix from ``host.json``
            (``extensions.http.routePrefix``). Defaults to ``"/api"``. Pass
            ``""`` for hosts that disable the prefix or a custom value such
            as ``"/v1"``.

    Returns:
        OpenAPI spec in JSON format.
    """
    try:
        spec = generate_openapi_spec(
            title,
            version,
            openapi_version,
            description=description,
            security_schemes=security_schemes,
            route_prefix=route_prefix,
        )
        return json.dumps(spec, indent=2, ensure_ascii=False)
    except OpenAPISpecConfigError:
        raise
    except Exception as e:
        logger.error(f"Failed to generate OpenAPI JSON: {str(e)}")
        raise RuntimeError("Failed to generate OpenAPI JSON") from e

get_openapi_yaml

Return the spec as YAML.

Parameters:

Name Type Description Default
title str

API title

'API'
version str

API version

'1.0.0'
openapi_version str

OpenAPI specification version ("3.0.0" or "3.1.0")

OPENAPI_VERSION_3_0
description str

Description for the OpenAPI info object

DEFAULT_OPENAPI_INFO_DESCRIPTION
security_schemes dict[str, dict[str, Any]] | None

Security scheme definitions for components.securitySchemes.

None
route_prefix str

HTTP route prefix from host.json (extensions.http.routePrefix). Defaults to "/api". Pass "" for hosts that disable the prefix or a custom value such as "/v1".

DEFAULT_ROUTE_PREFIX

Returns:

Type Description
str

OpenAPI spec in YAML format.

Source code in src/azure_functions_openapi/spec.py
def get_openapi_yaml(
    title: str = "API",
    version: str = "1.0.0",
    openapi_version: str = OPENAPI_VERSION_3_0,
    description: str = DEFAULT_OPENAPI_INFO_DESCRIPTION,
    security_schemes: dict[str, dict[str, Any]] | None = None,
    route_prefix: str = DEFAULT_ROUTE_PREFIX,
) -> str:
    """Return the spec as YAML.

    Parameters:
        title: API title
        version: API version
        openapi_version: OpenAPI specification version ("3.0.0" or "3.1.0")
        description: Description for the OpenAPI info object
        security_schemes: Security scheme definitions for components.securitySchemes.
        route_prefix: HTTP route prefix from ``host.json``
            (``extensions.http.routePrefix``). Defaults to ``"/api"``. Pass
            ``""`` for hosts that disable the prefix or a custom value such
            as ``"/v1"``.

    Returns:
        OpenAPI spec in YAML format.
    """
    try:
        spec = generate_openapi_spec(
            title,
            version,
            openapi_version,
            description=description,
            security_schemes=security_schemes,
            route_prefix=route_prefix,
        )
        return yaml.safe_dump(spec, sort_keys=False, allow_unicode=True)
    except OpenAPISpecConfigError:
        raise
    except Exception as e:
        logger.error(f"Failed to generate OpenAPI YAML: {str(e)}")
        raise RuntimeError("Failed to generate OpenAPI YAML") from e

render_swagger_ui

Render Swagger UI with enhanced security headers and CSP protection.

Parameters:

Name Type Description Default
title str

Page title for the Swagger UI

'API Documentation'
openapi_url str

URL to the OpenAPI specification

'/api/openapi.json'
custom_csp str | None

Custom Content Security Policy (optional)

None
enable_client_logging bool

Whether to enable browser-side response logging

False

Returns:

Type Description
HttpResponse

HttpResponse with Swagger UI HTML and security headers

Source code in src/azure_functions_openapi/swagger_ui.py
def render_swagger_ui(
    title: str = "API Documentation",
    openapi_url: str = "/api/openapi.json",
    custom_csp: str | None = None,
    enable_client_logging: bool = False,
) -> HttpResponse:
    """
    Render Swagger UI with enhanced security headers and CSP protection.

    Parameters:
        title: Page title for the Swagger UI
        openapi_url: URL to the OpenAPI specification
        custom_csp: Custom Content Security Policy (optional)
        enable_client_logging: Whether to enable browser-side response logging

    Returns:
        HttpResponse with Swagger UI HTML and security headers
    """
    nonce = secrets.token_urlsafe(16)

    # Enhanced CSP policy for better security
    default_csp = (
        "default-src 'self'; "
        f"script-src 'self' 'nonce-{nonce}' https://cdn.jsdelivr.net; "
        "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
        "img-src 'self' data: https:; "
        "font-src 'self' https://cdn.jsdelivr.net; "
        "connect-src 'self'; "
        "frame-ancestors 'none'; "
        "base-uri 'self'; "
        "form-action 'self'"
    )

    csp_policy = custom_csp or default_csp

    # Validate and sanitize inputs
    sanitized_title = _sanitize_html_content(title)
    sanitized_url = _sanitize_url(openapi_url)
    response_interceptor = """
            responseInterceptor: function(response) {
              return response;
            }
    """
    if enable_client_logging:
        response_interceptor = """
            responseInterceptor: function(response) {
              console.log('API Response:', response.status, response.url);
              return response;
            }
    """

    html_content = f"""
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta http-equiv="Content-Security-Policy" content="{csp_policy}">
        <meta http-equiv="X-Content-Type-Options" content="nosniff">
        <meta http-equiv="X-Frame-Options" content="DENY">
        <meta http-equiv="X-XSS-Protection" content="1; mode=block">
        <meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
        <title>{sanitized_title}</title>
        <link rel="stylesheet" 
              type="text/css" 
              href="{_SWAGGER_UI_CDN_BASE}/swagger-ui.css" />
      </head>
      <body>
        <div id="swagger-ui"></div>
        <script src="{_SWAGGER_UI_CDN_BASE}/swagger-ui-bundle.js"></script>
        <script nonce="{nonce}">
          // Enhanced security configuration
          const ui = SwaggerUIBundle({{
            url: '{sanitized_url}',
            dom_id: '#swagger-ui',
            presets: [SwaggerUIBundle.presets.apis],
            layout: 'BaseLayout',
            validatorUrl: null,  // Disable external validator for security
            tryItOutEnabled: true,
            supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
            requestInterceptor: function(request) {{
              // Add security headers to requests
              request.headers['X-Requested-With'] = 'XMLHttpRequest';
              return request;
            }},
            {response_interceptor}
          }});
        </script>
      </body>
    </html>
    """

    # Create response with security headers
    response = HttpResponse(html_content, mimetype="text/html")

    # Add additional security headers
    headers = {
        "Content-Security-Policy": csp_policy,
        "X-Content-Type-Options": "nosniff",
        "X-Frame-Options": "DENY",
        "X-XSS-Protection": "1; mode=block",
        "Referrer-Policy": "strict-origin-when-cross-origin",
        "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
        "Cache-Control": "no-cache, no-store, must-revalidate",
        "Pragma": "no-cache",
        "Expires": "0",
    }

    for header, value in headers.items():
        response.headers[header] = value

    logger.info(f"Swagger UI rendered with enhanced security headers for URL: {sanitized_url}")
    return response

Bridge: Auto-discover validation metadata

scan_validation_metadata

Scans a FunctionApp for HTTP-triggered functions decorated with @validate_http and auto-registers their Pydantic models in the OpenAPI registry.

from azure_functions_openapi import scan_validation_metadata

# Call after all routes are registered
scan_validation_metadata(app)

No extra dependencies required

scan_validation_metadata() reads the convention-based metadata attribute written by @validate_http. No import from azure-functions-validation is needed — just install both packages in your project.

Merge rules

Scenario Behavior
Only @validate_http Auto-registers discovered models
Only @openapi Existing behavior unchanged
Both with same models Merges additional OpenAPI fields
Both with different models Raises OpenAPISpecConfigError
Explicit @openapi Always takes precedence

While not part of the top-level runtime import list for app code, these internals are useful when debugging:

  • Registry accessor: azure_functions_openapi.decorator.get_openapi_registry
  • Route sanitizer: azure_functions_openapi.utils.validate_route_path
  • Operation ID sanitizer: azure_functions_openapi.utils.sanitize_operation_id

Version constants

Use these constants for explicit version selection:

from azure_functions_openapi import OPENAPI_VERSION_3_0, OPENAPI_VERSION_3_1

spec_30 = get_openapi_json(openapi_version=OPENAPI_VERSION_3_0)
spec_31 = get_openapi_json(openapi_version=OPENAPI_VERSION_3_1)

Tip

Prefer constants over hardcoded strings to avoid typos and keep version intent explicit in code review.