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

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, 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.

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. This utility supports three transport methods for the API key: query parameter (api_key), header (X-Api-Key), or cookie (api_key).

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. 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 module exposes strategy descriptors via GET /v1/config/strategy/, allowing clients to discover available processing strategies. The 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 with a router prefix of /v2. This version organizes functionality into distinct modules:

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:

@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 through a custom endpoint in 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. The meta router also serves static assets like /favicon.ico and /robots.txt, and facilitates serving the Clio single-page application and Thoth static UI through dedicated mount points:

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

Media Handling

Binary assets are managed through 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.

Summary

  • FastAPI foundation – The API boots from 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, 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 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), configuration (config.py), and bookmarks (bookmark.py). Version 2 routes reside in calliope/routes/v2/ and are consolidated through an __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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →