# How API Routes Are Registered and Organized with Tags in open-notebook's main.py

> Learn how lfnovo open-notebook registers and organizes API routes in main.py. Discover how imported APIRouter instances create OpenAPI documentation groups using tags.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: internals
- Published: 2026-06-06

---

**In `lfnovo/open-notebook`, every API route is registered inside [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) by importing modular `APIRouter` instances from `api/routers/` and mounting them with `app.include_router()` using a shared `/api` prefix and a descriptive `tags` list that drives OpenAPI documentation grouping.**

Managing a growing FastAPI backend requires a predictable registration pattern. In the `lfnovo/open-notebook` repository, the [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) file serves as the single source of truth for how API routes are registered and organized with tags, ensuring that Swagger UI stays clean and developers can locate endpoints quickly.

## The Router Registry in api/main.py

According to the `lfnovo/open-notebook` source code, the FastAPI application instance is created in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py). Rather than defining endpoints directly in that file, the project uses a modular router pattern that keeps the main bootstrap file focused solely on assembly.

All router modules live in the `api/routers/` package and are imported in bulk near the top of [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py):

```python
from api.routers import (
    auth, chat, config, context, credentials, embedding,
    embedding_rebuild, episode_profiles, insights, languages,
    models, notebooks, notes, podcasts, search, settings,
    source_chat, sources, speaker_profiles, transformations,
)
from api.routers import commands as commands_router

```

The `commands` router is imported separately as `commands_router` to avoid naming conflicts with the module itself.

After the FastAPI app is instantiated, each router is attached using the same consistent signature starting around line 89:

```python
app.include_router(auth.router, prefix="/api", tags=["auth"])
app.include_router(config.router, prefix="/api", tags=["config"])
app.include_router(notebooks.router, prefix="/api", tags=["notebooks"])

# ... continues for all routers

```

The `prefix="/api"` argument guarantees that every endpoint in every router is mounted under the `/api` base path. The `tags` argument is a list that tells FastAPI how to group routes in the generated OpenAPI schema and the interactive Swagger UI.

## Tag Mapping and OpenAPI Grouping

Because each call to `app.include_router()` supplies its own `tags` list, the resulting `/docs` page automatically organizes endpoints by functional area. Developers do not need to annotate every individual path operation with a tag because the router-level tag cascades to all endpoints in that module.

The concrete registrations in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) map routers to tags as follows:

| Tag | Router | Source Lines |
|-----|--------|--------------|
| `auth` | `auth.router` | L89–L91 |
| `config` | `config.router` | L91–L92 |
| `notebooks` | `notebooks.router` | L92–L93 |
| `search` | `search.router` | L93–L94 |
| `models` | `models.router` | L94–L95 |
| `transformations` | `transformations.router` | L95–L96 |
| `notes` | `notes.router` | L96–L97 |
| `embedding` | `embedding.router` | L97–L98 |
| `embeddings` | `embedding_rebuild.router` | L98–L100 |
| `settings` | `settings.router` | L100–L101 |
| `context` | `context.router` | L101–L102 |
| `sources` | `sources.router` | L102–L103 |
| `insights` | `insights.router` | L103–L104 |
| `commands` | `commands_router.router` | L104–L106 |
| `podcasts` | `podcasts.router` | L106–L108 |
| `episode-profiles` | `episode_profiles.router` | L107–L109 |
| `speaker-profiles` | `speaker_profiles.router` | L108–L110 |
| `chat` | `chat.router` | L109–L111 |
| `source-chat` | `source_chat.router` | L110–L112 |
| `credentials` | `credentials.router` | L111–L113 |
| `languages` | `languages.router` | L112–L114 |

When you start the application and visit `/docs`, endpoints appear under collapsible headings such as **auth**, **notebooks**, and **transformations**. This reduces cognitive load and mirrors the project's package structure.

## Defining Routes Inside api/routers/

Each router file in `api/routers/` exports an `APIRouter` instance named `router`. For example, [`api/routers/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/auth.py) defines authentication-scoped endpoints without worrying about global prefixes or tags:

```python

# api/routers/auth.py

from fastapi import APIRouter, Depends

router = APIRouter()

@router.get("/auth/status")
async def status():
    return {"authenticated": True}

```

Because [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) registers this router with `prefix="/api"` and `tags=["auth"]`, the effective endpoint is `GET /api/auth/status` and it appears under the **auth** group in the documentation.

### Extending an Existing Router

You can add new endpoints to a group by modifying the relevant router file. The `models` router illustrates this pattern:

```python

# api/routers/models.py

from fastapi import APIRouter

router = APIRouter()

@router.get("/models")
async def list_models():
    return {"models": ["gpt-4", "claude"]}

@router.post("/models")
async def create_model(name: str):
    # implementation …

    return {"created": name}

```

Both methods are automatically prefixed with `/api` and grouped under the **models** tag because [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) already mounts `models.router` with those settings.

## Testing Endpoints Programmatically

Routes registered through this system behave like standard FastAPI paths in tests. You can exercise them with an async HTTP client:

```python
import httpx
import asyncio

async def test_health():
    async with httpx.AsyncClient(base_url="http://localhost:5055") as client:
        r = await client.get("/health")
        assert r.json() == {"status": "healthy"}

asyncio.run(test_health())

```

The `/health` endpoint is defined directly in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) outside the router system, but it follows the same FastAPI conventions as the tagged router endpoints.

## Summary

- [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) acts as the single registry for every API router in `lfnovo/open-notebook`.
- Routers are imported from the `api/routers/` package and attached with `app.include_router()`.
- A universal `prefix="/api"` ensures consistent URL paths across all modules.
- The `tags` parameter on each `include_router()` call drives OpenAPI and Swagger UI grouping.
- Individual router files only need to export an `APIRouter` instance named `router`.

## Frequently Asked Questions

### What is the base prefix for all API routes in open-notebook?

Every router imported into [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) is mounted with `prefix="/api"`. Therefore, all endpoints exposed through the router system are reachable under the `/api` base path.

### How do tags affect the Swagger UI documentation?

FastAPI uses the `tags` argument from `app.include_router()` to categorize endpoints in the auto-generated `/docs` interface. In `lfnovo/open-notebook`, each router receives a descriptive tag such as `auth` or `notebooks`, so related endpoints appear together.

### Where are individual endpoint functions defined if not in main.py?

Endpoint functions live inside dedicated modules under `api/routers/`. For instance, authentication logic resides in [`api/routers/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/auth.py), while source management lives in [`api/routers/sources.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/sources.py). Each module exports a `router` object that [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) consumes.

### How can I add a new tag group to the API?

Create a new file in `api/routers/` that instantiates an `APIRouter`, define your endpoints on that `router`, and then import the module in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py). Add a line such as `app.include_router(new_module.router, prefix="/api", tags=["new-tag"])` alongside the existing registrations.