OpenAPI#

Litestar has first class OpenAPI support offering the following features:

  • Automatic OpenAPI 3.1.0 Schema generation, which is available as both YAML and JSON.

  • Builtin support for static documentation site generation using several different libraries.

  • Simple configuration using pydantic based classes.

Litestar includes a complete implementation of the latest version of the OpenAPI specification using Python dataclasses. This implementation is used as a basis for generating OpenAPI specs, supporting builtins including dataclasses and TypedDict, as well as Pydantic models and any 3rd party entities for which a plugin is implemented.

This is also highly configurable - and users can customize the OpenAPI spec in a variety of ways - ranging from passing configuration globally, to settings specific kwargs on route handler decorators.

Configuring schema generation#

OpenAPI schema generation is enabled by default. To configure it you can pass an instance of OpenAPIConfig to the Litestar class using the openapi_config kwarg:

from litestar import Litestar
from litestar.openapi import OpenAPIConfig

app = Litestar(
    route_handlers=[...], openapi_config=OpenAPIConfig(title="My API", version="1.0.0")
)

Disabling schema generation#

If you wish to disable schema generation and not include the schema endpoints in your API, simply pass None as the value for openapi_config:

from litestar import Litestar

app = Litestar(route_handlers=[...], openapi_config=None)

Configuring schema generation on a route handler#

By default, an operation schema is generated for all route handlers. You can omit a route handler from the schema by setting include_in_schema=False:

from litestar import get


@get(path="/some-path", include_in_schema=False)
def my_route_handler() -> None: ...

You can also modify the generated schema for the route handler using the following kwargs:

tags

A list of strings that correlate to the tag specification.

security:

A list of dictionaries that correlate to the security requirements specification. The values for this key are string keyed dictionaries with the values being a list of objects.

summary

Text used for the route’s schema summary section.

description

Text used for the route’s schema description section.

response_description

Text used for the route’s response schema description section.

operation_class

A subclass of Operation which can be used to fully customize the operation object for the handler.

operation_id

A string or callable that returns a string, which servers as an identifier used for the route’s schema operationId.

deprecated

A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. Defaults to False.

raises

A list of exception classes extending from litestar.HttpException. This list should describe all exceptions raised within the route handler’s function/method. The Litestar ValidationException will be added automatically for the schema if any validation is involved (e.g. there are parameters specified in the method/function). For custom exceptions, a detail class property should be defined, which will be integrated into the OpenAPI schema. If detail isn’t specified and the exception’s status code matches one from stdlib status code, a generic message will be applied.

responses

A dictionary of additional status codes and a description of their expected content. The expected content should be based on a Pydantic model describing its structure. It can also include a description and the expected media type. For example:

Note

operation_id will be prefixed with the method name when function is decorated with HTTPRouteHandler and multiple http_method. Will also be prefixed with path strings used in Routers and Controllers to make sure id is unique.

from datetime import datetime
from typing import Optional

from pydantic import BaseModel

from litestar import get
from litestar.openapi.datastructures import ResponseSpec


class Item(BaseModel): ...


class ItemNotFound(BaseModel):
    was_removed: bool
    removed_at: Optional[datetime]


@get(
    path="/items/{pk:int}",
    responses={
        404: ResponseSpec(
            data_container=ItemNotFound, description="Item was removed or not found"
        )
    },
)
def retrieve_item(pk: int) -> Item: ...
from datetime import datetime

from pydantic import BaseModel

from litestar import get
from litestar.openapi.datastructures import ResponseSpec


class Item(BaseModel): ...


class ItemNotFound(BaseModel):
    was_removed: bool
    removed_at: datetime | None


@get(
    path="/items/{pk:int}",
    responses={
        404: ResponseSpec(
            data_container=ItemNotFound, description="Item was removed or not found"
        )
    },
)
def retrieve_item(pk: int) -> Item: ...

You can also specify security and tags on higher level of the application, e.g. on a controller, router, or the app instance itself. For example:

from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from litestar.openapi.spec import Components, SecurityScheme, Tag


@get(
    "/public",
    tags=["public"],
    security=[{}],  # this endpoint is marked as having optional security
)
def public_path_handler() -> dict[str, str]:
    return {"hello": "world"}


@get("/other", tags=["internal"], security=[{"apiKey": []}])
def internal_path_handler() -> None: ...


app = Litestar(
    route_handlers=[public_path_handler, internal_path_handler],
    openapi_config=OpenAPIConfig(
        title="my api",
        version="1.0.0",
        tags=[
            Tag(name="public", description="This endpoint is for external users"),
            Tag(name="internal", description="This endpoint is for internal users"),
        ],
        security=[{"BearerToken": []}],
        components=Components(
            security_schemes={
                "BearerToken": SecurityScheme(
                    type="http",
                    scheme="bearer",
                )
            },
        ),
    ),
)

Customizing schema endpoints with the OpenAPIController#

Litestar includes an OpenAPIController class that is used as the default controller in the OpenAPIConfig.

This controller exposes the following endpoints:

/schema/openapi.yaml or /schema/openapi.yml

allowing for download of the OpenAPI schema as YAML.

/schema/openapi.json

allowing for download of the OpenAPI schema as JSON.

/schema/redoc

Serves the docs using Redoc.

/schema/swagger

Serves the docs using Swagger-UI.

/schema/elements

Serves the docs using Stoplight Elements.

/schema/rapidoc

Serves the docs using RapiDoc.

Additionally, the root /schema/ path is accessible, serving the site that is configured as the default in the OpenAPIConfig.

You can use your own subclass of OpenAPIController by setting it as the controller to use in the OpenAPIConfig openapi_controller kwarg.

For example, lets say we wanted to change the base path of the OpenAPI related endpoints from /schema to /api-docs, in this case we’d the following:

from litestar import Litestar
from litestar.openapi import OpenAPIConfig
from litestar.openapi import OpenAPIController


class MyOpenAPIController(OpenAPIController):
    path = "/api-docs"


app = Litestar(
    route_handlers=[...],
    openapi_config=OpenAPIConfig(
        title="My API", version="1.0.0", openapi_controller=MyOpenAPIController
    ),
)

OAuth2 in Swagger UI#

When using Swagger, OAuth2 settings can be configured via swagger_ui_init_oauth, which can be set to a dictionary containing the parameters described in the Swagger UI documentation for OAuth2 here.

We that you can preset your clientId or enable PKCE support.

Example Usage

from litestar import Litestar
from litestar.openapi import OpenAPIConfig
from litestar.openapi import OpenAPIController


class MyOpenAPIController(OpenAPIController):
    swagger_ui_init_oauth = {
        "clientId": "your-client-id",
        "appName": "your-app-name",
        "scopeSeparator": " ",
        "scopes": "openid profile",
        "useBasicAuthenticationWithAccessCodeGrant": True,
        "usePkceWithAuthorizationCodeGrant": True,
    }


app = Litestar(
    route_handlers=[...],
    openapi_config=OpenAPIConfig(
        title="My API", version="1.0.0", openapi_controller=MyOpenAPIController
    ),
)

CDN and offline file support#

You can change the default download paths for JS and CSS bundles as well as google fonts by subclassing OpenAPIController and setting any of the following class variables:

from litestar import Litestar
from litestar.openapi import OpenAPIConfig
from litestar.openapi import OpenAPIController


class MyOpenAPIController(OpenAPIController):
    path = "/api-docs"
    redoc_google_fonts = False
    redoc_js_url = "https://offline_location/redoc.standalone.js"
    swagger_css_url = "https://offline_location/swagger-ui-css"
    swagger_ui_bundle_js_url = "https://offline_location/swagger-ui-bundle.js"
    swagger_ui_standalone_preset_js_url = (
        "https://offline_location/swagger-ui-standalone-preset.js"
    )
    stoplight_elements_css_url = "https://offline_location/spotlight-styles.mins.css"
    stoplight_elements_js_url = (
        "https://offline_location/spotlight-web-components.min.js"
    )
    rapidoc_js_url = "https://offline_location/rapidock-min.js"


app = Litestar(
    route_handlers=[...],
    openapi_config=OpenAPIConfig(
        title="My API", version="1.0.0", openapi_controller=MyOpenAPIController
    ),
)

Accessing the OpenAPI schema in code#

The OpenAPI schema is generated during the Litestar app’s init method. Once init is finished, its accessible as app.openapi_schema. As such you can always access it inside route handlers, dependencies, etc. by access the request instance:

from litestar import Request, get


@get(path="/")
def my_route_handler(request: Request) -> dict:
    schema = request.app.openapi_schema
    return schema.dict()

Customizing Pydantic model schemas#

You can customize the OpenAPI schemas generated for pydantic models by following the guidelines in the Pydantic docs.

Additionally, you can affect how pydantic models are translated into OpenAPI components by settings a special dunder attribute on the model called __schema_name__:

Customize Components Example#
from uuid import UUID, uuid4

from pydantic import BaseModel

from litestar import Litestar, get


class IdModel(BaseModel):
    __schema_name__ = "IdContainer"

    id: UUID


@get("/id", sync_to_thread=False)
def retrieve_id_handler() -> IdModel:
    """

    Returns: An IdModel

    """
    return IdModel(id=uuid4())


app = Litestar(route_handlers=[retrieve_id_handler])

The above will result in an OpenAPI schema object that looks like this:

{
    "openapi": "3.1.0",
    "info": {"title": "Litestar API", "version": "1.0.0"},
    "servers": [{"url": "/"}],
    "paths": {
        "/id": {
            "get": {
                "operationId": "Retrieve Id Handler",
                "responses": {
                    "200": {
                        "description": "Request fulfilled, document follows",
                        "headers": {},
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/IdContainer"
                                }
                            }
                        }
                    }
                },
                "deprecated": false
            }
        }
    },
    "components": {
        "schemas": {
            "IdContainer": {
                "properties": {
                    "id": {"type": "string", "format": "uuid", "title": "Id"}
                },
                "type": "object",
                "required": ["id"],
                "title": "IdContainer"
            }
        }
    }
}

Attention

If you use multiple pydantic models that use the same name in the schema, you will need to use the __schema_name__ dunder to ensure each has a unique name in the schema, otherwise the schema components will be ambivalent.

Customizing Operation class#

You can customize the operation object used for a path in the generated OpenAPI schemas by creating a subclass of Operation.

This option can be helpful in situations where request data needs to be manually parsed as Litestar will not know how to create the OpenAPI operation data by default.

Customize Components Example#
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from litestar import Litestar, MediaType, Request, post
from litestar.exceptions import HTTPException
from litestar.openapi.spec import OpenAPIMediaType, OpenAPIType, Operation, RequestBody, Schema
from litestar.status_codes import HTTP_400_BAD_REQUEST


@dataclass
class CustomOperation(Operation):
    """Custom Operation class which includes a non-standard field which is part of an OpenAPI extension."""

    x_code_samples: Optional[List[Dict[str, str]]] = field(default=None, metadata={"alias": "x-codeSamples"})

    def __post_init__(self) -> None:
        self.tags = ["ok"]
        self.description = "Requires OK, Returns OK"
        self.request_body = RequestBody(
            content={
                "text": OpenAPIMediaType(
                    schema=Schema(
                        title="Body",
                        type=OpenAPIType.STRING,
                        example="OK",
                    )
                ),
            },
            description="OK is the only accepted value",
        )
        self.x_codeSamples = [
            {"lang": "Python", "source": "import requests; requests.get('localhost/example')", "label": "Python"},
            {"lang": "cURL", "source": "curl -XGET localhost/example", "label": "curl"},
        ]


@post("/", operation_class=CustomOperation, media_type=MediaType.TEXT)
async def route(request: Request) -> str:
    """

    Returns: OK

    """

    if (await request.body()) == b"OK":
        return "OK"
    raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="request payload must be OK")


app = Litestar(route_handlers=[route])
Customize Components Example#
from dataclasses import dataclass, field

from litestar import Litestar, MediaType, Request, post
from litestar.exceptions import HTTPException
from litestar.openapi.spec import OpenAPIMediaType, OpenAPIType, Operation, RequestBody, Schema
from litestar.status_codes import HTTP_400_BAD_REQUEST


@dataclass
class CustomOperation(Operation):
    """Custom Operation class which includes a non-standard field which is part of an OpenAPI extension."""

    x_code_samples: list[dict[str, str]] | None = field(default=None, metadata={"alias": "x-codeSamples"})

    def __post_init__(self) -> None:
        self.tags = ["ok"]
        self.description = "Requires OK, Returns OK"
        self.request_body = RequestBody(
            content={
                "text": OpenAPIMediaType(
                    schema=Schema(
                        title="Body",
                        type=OpenAPIType.STRING,
                        example="OK",
                    )
                ),
            },
            description="OK is the only accepted value",
        )
        self.x_codeSamples = [
            {"lang": "Python", "source": "import requests; requests.get('localhost/example')", "label": "Python"},
            {"lang": "cURL", "source": "curl -XGET localhost/example", "label": "curl"},
        ]


@post("/", operation_class=CustomOperation, media_type=MediaType.TEXT)
async def route(request: Request) -> str:
    """

    Returns: OK

    """

    if (await request.body()) == b"OK":
        return "OK"
    raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail="request payload must be OK")


app = Litestar(route_handlers=[route])

The above example will result in an OpenAPI schema object that looks like this:

{
    "info": { "title": "Litestar API", "version": "1.0.0" },
    "openapi": "3.0.3",
    "servers": [{ "url": "/" }],
    "paths": {
        "/": {
            "post": {
                "tags": ["ok"],
                "summary": "Route",
                "description": "Requires OK, Returns OK",
                "operationId": "Route",
                "requestBody": {
                    "content": {
                        "text": {
                            "schema": { "type": "string", "title": "Body", "example": "OK" }
                        }
                    },
                    "description": "OK is the only accepted value",
                    "required": false
                },
                "responses": {
                    "201": {
                        "description": "Document created, URL follows",
                        "headers": {}
                    }
                },
                "deprecated": false,
                "x-codeSamples": [
                    {
                        "lang": "Python",
                        "source": "import requests; requests.get('localhost/example')",
                        "label": "Python"
                    },
                    {
                        "lang": "cURL",
                        "source": "curl -XGET localhost/example",
                        "label": "curl"
                    }
                ]
            }
        }
    },
    "components": { "schemas": {} }
}

Attention

OpenAPI Vendor Extension fields need to start with x- and should not be processed with the default field name converter. To work around this, Litestar will honor an alias field provided to the dataclass.field metadata when generating the field name in the schema.