# How the API Layer Is Structured in Calliope: A FastAPI Architecture Deep Dive

> Explore Calliope's modular FastAPI API layer architecture. Learn how endpoints are organized into functional routers with versioning and API-key authentication.

- Repository: [chrisimmel/calliope](https://github.com/chrisimmel/calliope)
- Tags: architecture
- Published: 2026-02-27

---

**Calliope's API layer is built on FastAPI with a modular, versioned architecture that uses API-key authentication across query parameters, headers, or cookies, and organizes endpoints into functional routers under `/v1` and `/v2` prefixes.**

The `chrisimmel/calliope` repository implements a clean, production-ready HTTP interface using FastAPI. Understanding the API layer structure in Calliope reveals how the application handles authentication, request validation, and route organization to serve its story-generation functionality.

## Application Bootstrap and FastAPI Initialization

The entry point for the API is [`calliope/app.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/app.py), which contains the `create_app()` factory function. This function instantiates the FastAPI application, mounts the Piccolo admin interface, and registers all API routers via the `register_views()` function.

```python
def create_app() -> FastAPI:
    app = FastAPI(
        title="Calliope",
        description="Let me tell you a story.",
        version=settings.APP_VERSION,
    )
    # Mount Piccolo Admin at /admin

    admin_app = create_admin(...)
    app.mount("/admin", admin_app)

    # Register all versioned routers

    register_views(app)
    return app

app = create_app()

```

The `register_views()` function explicitly includes each router module: `meta_routes`, `v1_story_routes`, `v1_config_routes`, `media_routes`, `thoth_routes`, `v1_bookmark_routes`, and the consolidated `v2_router`. This centralized registration pattern keeps the main application factory clean while allowing granular control over route inclusion.

## API Key Authentication Implementation

All protected endpoints rely on the `get_api_key` dependency defined in [`calliope/utils/authentication.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/utils/authentication.py). This utility supports three transport methods for the API key: query parameter (`api_key`), header (`X-Api-Key`), or cookie (`api_key`).

```python
api_key_query = APIKeyQuery(name="api_key", auto_error=False)
api_key_header = APIKeyHeader(name="X-Api-Key", auto_error=False)
api_key_cookie = APIKeyCookie(name="api_key", auto_error=False)

async def get_api_key(
    api_key_query: str = Security(api_key_query),
    api_key_header: str = Security(api_key_header),
    api_key_cookie: str = Security(api_key_cookie),
) -> str:
    if api_key_query == settings.CALLIOPE_API_KEY:
        return api_key_query
    elif api_key_header == settings.CALLIOPE_API_KEY:
        return api_key_header
    elif api_key_cookie == settings.CALLIOPE_API_KEY:
        return api_key_cookie
    raise HTTPException(status_code=HTTP_403_FORBIDDEN,
                        detail="Could not validate credentials")

```

If none of the provided keys match the configured secret, the function raises an `HTTPException` with a **403 Forbidden** status. This dependency is injected into every router that requires protection, ensuring consistent security across the API surface.

## Versioned Router Architecture

Calliope segments its API into versioned namespaces to maintain backward compatibility while evolving the interface. The routing logic is organized under `calliope/routes/` with separate subdirectories for `v1` and `v2`.

### V1 Story and Frame Endpoints

The core story-generation functionality resides in [`calliope/routes/v1/story.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/routes/v1/story.py). This router uses the prefix `/v1` and tag `["story"]` to group related endpoints in the OpenAPI documentation.

Key endpoints include:
- `GET /story/` – Returns frames from the current story sequence
- `GET /story/slug/{slug}` – Retrieves a specific story by its slug identifier
- `POST /frames/` – Accepts input snippets (image, audio, text) and generates a new frame
- `GET /frames/` – Query-parameter variant of the frame endpoint
- `PUT /story/reset/` – Resets the client’s story state

Each endpoint declares `Depends(get_api_key)` for security and uses **Pydantic models** for request parsing and response serialization.

### Configuration and Bookmark Routes

The [`calliope/routes/v1/config.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/routes/v1/config.py) module exposes strategy descriptors via `GET /v1/config/strategy/`, allowing clients to discover available processing strategies. The [`calliope/routes/v1/bookmark.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/routes/v1/bookmark.py) module handles story bookmarking operations, sharing the same authentication dependency pattern.

### V2 API Structure

The next-generation API resides in `calliope/routes/v2/`, initialized through [`calliope/routes/v2/__init__.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/routes/v2/__init__.py) with a router prefix of `/v2`. This version organizes functionality into distinct modules:
- [`v2/models.py`](https://github.com/chrisimmel/calliope/blob/main/v2/models.py) – Model-related operations
- [`v2/stories.py`](https://github.com/chrisimmel/calliope/blob/main/v2/stories.py) – Story CRUD operations
- [`v2/tasks.py`](https://github.com/chrisimmel/calliope/blob/main/v2/tasks.py) – Task-queue interactions

This structure allows parallel development of new features without breaking existing v1 integrations.

## Pydantic Models and Request Validation

Request and response schemas are defined using **Pydantic** models located in `calliope/models/` and inline within router modules. These models enforce validation rules and generate OpenAPI schemas automatically.

Common models include:
- `StoryRequestParamsModel` – Validates story query parameters
- `FramesRequestParamsModel` – Validates frame generation input
- `StoryResponseV1` and `StoriesResponseV1` – Structure JSON responses

FastAPI uses these models through the `response_model` parameter and `Depends()` injection:

```python
@router.get("/story/", response_model=StoryResponseV1)
async def get_story_request(
    request: Request,
    api_key: APIKey = Depends(get_api_key),
    request_params: StoryRequestParamsModel = Depends(StoryRequestParamsModel),
) -> StoryResponseV1:
    ...

```

## Documentation, Meta Routes, and Static Files

The OpenAPI schema is served at [`/openapi.json`](https://github.com/chrisimmel/calliope/blob/main//openapi.json) through a custom endpoint in [`calliope/app.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/app.py) that requires the same API-key authentication as other protected resources. This prevents unauthorized scraping of the API specification.

The Swagger UI is available at `/docs`, implemented in [`calliope/routes/meta.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/routes/meta.py). The meta router also serves static assets like `/favicon.ico` and [`/robots.txt`](https://github.com/chrisimmel/calliope/blob/main//robots.txt), and facilitates serving the **Clio** single-page application and **Thoth** static UI through dedicated mount points:

```python
app.mount("/thoth/", StaticFiles(directory="static/thoth", html=True), name="thoth_static")

```

## Media Handling

Binary assets are managed through [`calliope/routes/media.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/routes/media.py), which handles uploads and downloads for story frames. When deployed to Cloud Run, this module integrates with Google Cloud Platform storage via helper functions in [`calliope/utils/google.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/utils/google.py).

## Summary

- **FastAPI foundation** – The API boots from [`calliope/app.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/app.py) with a factory pattern and centralized router registration.
- **Multi-method authentication** – API keys are validated via `get_api_key` in [`calliope/utils/authentication.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/utils/authentication.py), supporting query, header, and cookie transport.
- **Versioned routing** – Endpoints are organized under `/v1` and `/v2` prefixes in `calliope/routes/v1/` and `calliope/routes/v2/` directories.
- **Pydantic validation** – Request and response shapes are strictly defined in `calliope/models/` for automatic validation and documentation.
- **Protected documentation** – The OpenAPI schema and Swagger UI require authentication, preventing unauthorized access to API specifications.

## Frequently Asked Questions

### How does Calliope validate API requests?

Calliope uses the `get_api_key` dependency function in [`calliope/utils/authentication.py`](https://github.com/chrisimmel/calliope/blob/main/calliope/utils/authentication.py) to validate requests. It checks for an API key in the query string, `X-Api-Key` header, or `api_key` cookie, comparing the value against `settings.CALLIOPE_API_KEY`. If no valid key is found, it returns a 403 Forbidden error.

### Where are the different API versions defined in the codebase?

Version 1 routes are located in `calliope/routes/v1/` with separate files for story endpoints ([`story.py`](https://github.com/chrisimmel/calliope/blob/main/story.py)), configuration ([`config.py`](https://github.com/chrisimmel/calliope/blob/main/config.py)), and bookmarks ([`bookmark.py`](https://github.com/chrisimmel/calliope/blob/main/bookmark.py)). Version 2 routes reside in `calliope/routes/v2/` and are consolidated through an [`__init__.py`](https://github.com/chrisimmel/calliope/blob/main/__init__.py) router that applies the `/v2` prefix to all sub-modules.

### What framework does Calliope use for request and response validation?

The API relies on **Pydantic** models stored in `calliope/models/` (such as `StoryRequestParamsModel` and `StoryResponseV1`) to validate incoming JSON and structure outgoing responses. FastAPI leverages these models to generate OpenAPI schemas and return precise HTTP 422 errors for invalid payloads.

### How can I access the OpenAPI documentation for a Calliope instance?

The OpenAPI JSON spec is available at [`/openapi.json`](https://github.com/chrisimmel/calliope/blob/main//openapi.json) and the Swagger UI at `/docs`. Both endpoints are protected by the same API-key authentication used throughout the application, requiring you to pass a valid key via header, query, or cookie to view the documentation.