Skip to main content

fastapi-streams

CI Python 3.12+ MIT License

Decorate a FastAPI endpoint. Get typed streaming clients in TypeScript and Python.

Define your events with Pydantic and add @router.stream(). fastapi-streams turns that definition into an OpenAPI 3.2 stream contract and generated SDKs with named event values, typed async iteration, and shared SSE transport code.

FastAPI decorator → OpenAPI 3.2 → shared HTTP IR → TypeScript + Python clients

Why this exists

An endpoint labeled text/event-stream does not give consumers a typed SDK. They still need to know which JSON events arrive, how to parse a fragmented stream, and how to stop it cleanly.

OpenAPI 3.2 can describe each SSE event with itemSchema and its JSON payload with contentSchema. fastapi-streams connects that contract to both clients, so event models, endpoint parameters, and client types come from one definition. A CI check catches drift between the Python app, the contract, and generated code.

1. Decorate your FastAPI endpoint

Install from PyPI with the optional codegen extra:

uv add 'fastapi-streams[codegen]'

Save this as app.py:

from collections.abc import AsyncIterator
from typing import Annotated, Literal

from fastapi import FastAPI
from pydantic import BaseModel, Field

from fastapi_streams import StreamRouter


class TokenDelta(BaseModel):
    type: Literal["token.delta"]
    text: str


class RunCompleted(BaseModel):
    type: Literal["run.completed"]
    run_id: str


type RunEvent = Annotated[TokenDelta | RunCompleted, Field(discriminator="type")]

router = StreamRouter(prefix="/runs", tags=["agentRuns"])


@router.stream("/{run_id}/events", operation_id="stream_run_events")
async def stream_run_events(run_id: str) -> AsyncIterator[RunEvent]:
    yield TokenDelta(type="token.delta", text="Hello")
    yield RunCompleted(type="run.completed", run_id=run_id)


app = FastAPI()
app.include_router(router, prefix="/v1")

The result is GET /v1/runs/{run_id}/events. StreamRouter extends APIRouter: normal Depends, security dependencies, validation, middleware, and nested routers keep working. FastAPI handles SSE framing, serialization, and heartbeats. Use methods=["POST"] for request-body-driven streams.

2. Export once, generate both clients

uv run fastapi-streams schema \
  --app app:app --output openapi.json

uv run fastapi-streams generate \
  --schema openapi.json --language typescript --output generated/typescript

uv run fastapi-streams generate \
  --schema openapi.json --language python --output generated/python

These commands import the app and read its contract; no running server is needed. The exporter validates OpenAPI 3.2 and the event schemas. Both generators consume one HTTP intermediate representation (IR), carrying operations, parameters, and event types into the generated SDKs.

3. Consume typed events

The RunEvent union produces RunEventType, with the same named values in both languages. The discriminator narrows each event to its matching model.

TypeScript — Fetch, AsyncIterable<RunEvent>, no runtime dependencies:

import { createClient, RunEventType } from "./generated/typescript/index.js";

const client = createClient({ baseUrl: "http://localhost:8000" });

for await (const event of client.agentRuns.streamRunEvents({ runId: "demo" })) {
  switch (event.type) {
    case RunEventType.TOKEN_DELTA:
      console.log(event.text); // TokenDelta
      break;
    case RunEventType.RUN_COMPLETED:
      console.log(event.run_id); // RunCompleted
      break;
    default: {
      const exhaustive: never = event;
      throw new Error(`Unexpected event: ${exhaustive}`);
    }
  }
}

Python — HTTPX, Pydantic models, and StrEnum values:

import asyncio
from typing import assert_never

from generated.python import Client, RunEventType


async def main() -> None:
    async with Client("http://localhost:8000") as client:
        async for event in client.agent_runs.stream_run_events(run_id="demo"):
            match event.type:
                case RunEventType.TOKEN_DELTA:
                    print(event.text)
                case RunEventType.RUN_COMPLETED:
                    print(event.run_id)
                case _:
                    assert_never(event)


asyncio.run(main())

Install httpx and pydantic in a Python consumer (uv add httpx pydantic). HTTPX remains the default; opt in to HTTPX2 at generation time with --python-http-client httpx2 and install httpx2 instead. See Python HTTP library selection. Both SDKs handle fragmented SSE frames, UTF-8 decoding, HTTP errors, and resource cleanup. TypeScript supports AbortSignal; Python supports task cancellation and explicit async-generator closure. See client usage.

4. Catch contract drift in CI

Commit the exported schema and generated clients. Then check the complete chain:

uv run fastapi-streams check \
  --app app:app --schema openapi.json \
  --language typescript --output generated/typescript

uv run fastapi-streams check \
  --app app:app --schema openapi.json \
  --language python --output generated/python

check fails if the app's contract changed or generated files are stale. It writes nothing and needs no running server. Add --diff to print what changed. Pair it with TypeScript's compiler and Pyright to catch invalid parameters and event access in consumer code.

To regenerate or check the schema and every client with one command, list them under [tool.fastapi-streams] in pyproject.toml and run uv run fastapi-streams sync (or sync --check --diff in CI); see the sync configuration.

Check What it verifies
Contract validation OpenAPI structure, JSON Schemas, and local references.
fastapi-streams check App → committed contract → generated client consistency.
TypeScript / Pyright Parameter types, event fields, and exhaustive narrowing.

Codegen and CI reference →

Run the complete example

The package is developed in the Canon workspace, which shares one lockfile across its packages:

git clone https://github.com/mathisarends/canon.git
cd canon
uv sync --locked --all-packages --all-groups
npm ci --ignore-scripts --prefix packages/fastapi-streams
cd packages/fastapi-streams
uv run --locked uvicorn examples.agent_runs:app --reload

In another terminal:

# TypeScript
npx tsc -p tests/typescript/tsconfig.json
node tests/typescript/dist/examples/agent_runs/consume.js

# Python
uv run --locked python -m examples.agent_runs.consume

The example includes GET and authenticated POST streams, both generated SDKs, and committed OpenAPI regression fixtures in tests/fixtures/. Verify all generated artifacts with uv run --locked python scripts/codegen.py --check.

Documentation

See the changelog and upgrade steps when updating the library and regenerating committed clients.

Guide Topics
Quickstart Setup, event models, and FastAPI integration.
Contracts and codegen OpenAPI 3.2, both generators, and drift checks.
Client usage Enums, authentication, cancellation, and errors.
Architecture Native SSE, shared IR, templates, and transports.
Development Tests, supported scope, and roadmap.
Releasing Workspace release process and package-prefixed tags.

The current release supports GET/POST JSON SSE streams. Ordinary REST endpoints stay in the contract for your existing REST SDK. See the supported scope.

Python 3.12+ · FastAPI 0.143.3+ · Pydantic v2 · MIT

Metadata

Release files for fastapi-streams 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastapi-streams 0.5.0
File Size Uploaded Provenance Actions
fastapi_streams-0.5.0.tar.gz 141.4 kB With provenance Details for fastapi_streams-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for fastapi-streams 0.5.0
File Interpreter ABI Platform Actions
fastapi_streams-0.5.0-py3-none-any.whl Python 3 none any Details for fastapi_streams-0.5.0-py3-none-any.whl

Total release size: 200.0 kB

Release files / fastapi_streams-0.5.0.tar.gz

Download URL fastapi_streams-0.5.0.tar.gz
Size 141.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d617cf42a01bcdd7f1914aa8d0dcdc2337b76f06c4a025cd42cc6a1497870e6c
BLAKE2b-256 checksum
How to use checksums
15794174db73cb37d44a6614a79fe8d909efcb7562c561d80d6e7e391dc120b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Release files / fastapi_streams-0.5.0-py3-none-any.whl

Download URL fastapi_streams-0.5.0-py3-none-any.whl
Size 58.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d1b26571f00dfad606df7a557363558c89b816f8deee34d57a4065b270734bb7
BLAKE2b-256 checksum
How to use checksums
809a26ef865b1b47cc2c623ff9c750fe2e6469c405c262ea4cebc262b3a895c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

Security for fastapi-streams 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for fastapi_streams-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 25, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page