Enlitt

Python SDK

Use Enlitt from a Python backend such as FastAPI, Starlette, Django, or Flask.

The Python SDK, enlitt on PyPI, does what the Node.js SDK does on a Python server:

  • login handlers and a session middleware;
  • standard calls through an OpenAI-shaped client;
  • with_premium_charge() around any provider call;
  • usage();
  • the same typed errors.

Every rule on the other pages applies unchanged. That includes login, premium limits, idempotency, and errors. This page shows only the Python names.

Install

The SDK needs Python 3.10 or later. It is async only, so every call is awaited.

pip install "enlitt[openai,starlette]"
# or
uv add "enlitt[openai,starlette]"

Each extra adds a dependency:

  • openai adds the official openai package, for the OpenAI-shaped client.
  • starlette adds the FastAPI and Starlette routes.

The core needs only httpx.

Then wire the project:

npx enlitt setup --name "My Python app"

The CLI needs Node.js. setup finds pyproject.toml or requirements.txt. It then writes ENLITT_CLIENT_ID and ENLITT_SECRET_KEY to .env and records the application in enlitt.json. Keep .env out of git; npx enlitt doctor reports it when it is committed. Load .env with your usual tool, such as python-dotenv or your process manager. The SDK reads ENLITT_SECRET_KEY from the environment.

Mount login and the session middleware (FastAPI)

import os

from fastapi import FastAPI
from enlitt.asgi import EnlittSessionMiddleware
from enlitt.auth import CallbackConfig, StateRecord
from enlitt.starlette import enlitt_routes


class MemoryStateStore:
    """Development only. Use a shared store (Redis, your database) when you run more than one process."""

    def __init__(self) -> None:
        self._records: dict[str, StateRecord] = {}

    async def set(self, state: str, record: StateRecord, ttl_ms: int) -> None:
        self._records[state] = record

    async def get(self, state: str) -> StateRecord | None:
        return self._records.get(state)

    async def consume(self, state: str) -> StateRecord | None:
        return self._records.pop(state, None)


config = CallbackConfig(
    client_id=os.environ["ENLITT_CLIENT_ID"],
    app_origin="https://your-app.example",
    redirect_uri="https://your-app.example/enlitt/callback",
    default_return_to="/",
    state_store=MemoryStateStore(),
    # secret_key defaults to the ENLITT_SECRET_KEY environment variable.
)

app = FastAPI(routes=enlitt_routes(config=config))
app.add_middleware(EnlittSessionMiddleware)

enlitt_routes() mounts three routes:

  • login start at /enlitt/login;
  • the callback at /enlitt/callback, which must be the path of redirect_uri;
  • logout at /enlitt/logout.

Each takes a keyword argument to change its path. The handlers behave exactly as described on the login page, and StateRecord and StateStore have the same meaning there.

Django and Flask

The handlers are plain async functions over a small HandlerRequest and HandlerResponse, so any async framework can mount them. In Django, convert in an async view:

from django.http import HttpResponse
from enlitt.auth import HandlerRequest, login_start_handler

login = login_start_handler(config)


async def enlitt_login(request):
    response = await login(
        HandlerRequest(
            method=request.method,
            url=request.build_absolute_uri(),
            headers={k.lower(): v for k, v in request.headers.items()},
            body=request.body,
        )
    )
    out = HttpResponse(response.body, status=response.status)
    for name, value in response.headers:
        out.headers[name] = value  # use out.cookies for several set-cookie values
    return out

To make a model call from a sync view, wrap it in the session yourself:

from enlitt import enlitt_session, run_with_enlitt_session

token = request.COOKIES.get("enlitt_session")
session = enlitt_session(token) if token else None
result = await run_with_enlitt_session(session, lambda: make_the_call())

Flask works the same way with flask[async].

Make a standard call

The client mirrors the OpenAI Responses API. Call it inside a request that has gone through the middleware:

from enlitt.openai import ENLITT_STANDARD_MODEL, enlitt

client = enlitt()


@app.post("/api/summary")
async def summary() -> dict[str, str]:
    response = await client.responses.create(
        model=ENLITT_STANDARD_MODEL,
        input="Summarize this support ticket.",
    )
    return {"text": response.output_text}

Pass stream=True to get an async iterator of OpenAI stream events. To cancel a call, cancel the task or close the iterator. The standard model's limits are on the models page; unsupported options raise EnlittConfigurationError before any request is sent.

Make a premium call

Wrap your own AsyncOpenAI client. Your provider key stays on your server:

from openai import AsyncOpenAI
from enlitt.openai import EnlittCallOptions, enlitt, per_token

premium = enlitt(AsyncOpenAI())

response = await premium.responses.create(
    model="gpt-4.1-mini",
    input="Write a long story.",
    enlitt=EnlittCallOptions(
        max_charge="20000000",  # NANOUSD, as a string
        price=per_token(input="400", output="1600"),
    ),
)

enlitt= is never sent to the provider. Amounts are always canonical integer strings in NANOUSD.

For any other provider (images, voice, another model SDK), use with_premium_charge():

from enlitt import PremiumProvider, WithPremiumChargeOptions, with_premium_charge


async def run(charge, operation):
    image = await my_image_provider.generate(prompt)
    charge("5000000")  # what this call costs the user, in NANOUSD
    return image


image = await with_premium_charge(
    WithPremiumChargeOptions(
        max_charge="5000000",
        provider=PremiumProvider(id="my-image-provider", model="image-v2"),
    ),
    run,
)

The call is reported exactly once. If run returns, it is reported as completed; if it raises, as failed; if it is cancelled, as cancelled.

Remaining capacity

from enlitt import usage

capacity = await usage()
print(capacity.standard.remaining_percent, capacity.premium.limit_cap_nano_usd)

Errors

The error classes have the same names as in Node.js: EnlittError, EnlittLoginRequiredError, EnlittInsufficientCreditsError, EnlittLimitExceededError, EnlittSubscriptionInactiveError, EnlittRateLimitError, EnlittAuthenticationError, EnlittAuthorizationError, EnlittConfigurationError, and EnlittModelError. Each carries code, provider_invoked, retryable, retry_after_ms, request_id, and authorize_url. The errors page says which ones are safe to show a user.

from enlitt import EnlittLoginRequiredError

try:
    response = await client.responses.create(model=ENLITT_STANDARD_MODEL, input=prompt)
except EnlittLoginRequiredError:
    return RedirectResponse("/enlitt/login?return_to=/chat", status_code=303)

Games and native apps

If your Python server backs a game or a desktop or mobile app, mount the client sign-in routes with enlitt_routes(config=config, client_sign_in=...). Then add the middleware with EnlittSessionMiddleware(app, header=True), so the game's Enlitt-Session header is read.

On this page