# How to Add Custom API Endpoints to the FastAPI Router in Open Notebook

> Learn how to add custom API endpoints to the FastAPI router in Open Notebook. Create new routers, define routes, and register them in api main.py to extend your application's functionality.

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

---

**To add custom API endpoints to the FastAPI router in Open Notebook, create a new router module in `api/routers/`, define routes using `APIRouter`, and register it in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) using `app.include_router()`.**

Open Notebook is an open-source knowledge management system built on FastAPI that organizes HTTP routes into modular router components under `api/routers/`. Extending the application's REST API requires following the established pattern where independent router modules are imported and mounted in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) (lines 38-62). This guide demonstrates the exact implementation steps used in the `lfnovo/open-notebook` repository to ensure consistency with existing endpoints.

## Understanding the Router Architecture

Open Notebook follows a modular FastAPI architecture that separates routing concerns into focused, reusable components.

### Modular Router Organization

The codebase organizes API endpoints by domain in separate files within `api/routers/`. Existing implementations like [`sources.py`](https://github.com/lfnovo/open-notebook/blob/main/sources.py) and [`notebooks.py`](https://github.com/lfnovo/open-notebook/blob/main/notebooks.py) demonstrate the standard pattern: each module declares a module-level `router = APIRouter()` instance and defines async handler functions decorated with `@router.get()`, `@router.post()`, or other HTTP verbs. This separation keeps the main application file clean while allowing domain-specific logic to remain encapsulated.

### Main Application Integration

The central FastAPI application instance lives in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), where all routers are imported and mounted under the `/api` prefix. According to the source code in lines 38-62, the application uses `app.include_router()` to register each router module, ensuring consistent URL prefixes and tag metadata for OpenAPI documentation. Global exception handlers defined in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) (such as `custom_http_exception_handler` at lines 49-64) automatically wrap router-level `HTTPException` instances with proper CORS headers.

## Creating a Custom Router Module

To add new functionality, you must create a router file that follows the established architectural patterns.

### Step 1: Initialize the Router File

Create a new Python file in `api/routers/` (for example, [`hello.py`](https://github.com/lfnovo/open-notebook/blob/main/hello.py)). Begin by importing `APIRouter` from FastAPI and instantiating a module-level router object:

```python

# api/routers/hello.py

from fastapi import APIRouter, HTTPException, Query
from loguru import logger

from api.models import HelloResponse

router = APIRouter()

```

This `router` object serves as the decorator factory for your endpoint functions, exactly as implemented in existing modules like [`api/routers/sources.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/sources.py).

### Step 2: Define Pydantic Schemas

All request and response validation in Open Notebook uses Pydantic models centralized in [`api/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/models.py). For your custom endpoint, add the appropriate schema class to ensure consistent validation:

```python

# api/models.py

from pydantic import BaseModel

class HelloResponse(BaseModel):
    message: str

```

Reusing existing models from [`api/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/models.py) is recommended when possible to maintain consistency across the API surface.

### Step 3: Implement Endpoint Handlers

Define async functions decorated with the appropriate HTTP method and route path. The implementation should follow the error-handling patterns used throughout the repository, utilizing `HTTPException` for client errors and `loguru` for logging:

```python

# api/routers/hello.py

@router.get("/hello", response_model=HelloResponse)
async def hello(name: str = Query("World", description="Name to greet")):
    """
    Simple example endpoint that returns a greeting.
    """
    logger.info(f"Received greeting request for name={name}")
    if not name.isidentifier():
        raise HTTPException(status_code=400, detail="Invalid name")
    return HelloResponse(message=f"Hello, {name}!")

```

The router handles path operation definitions while the global exception handlers in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) manage cross-cutting concerns like CORS headers.

## Registering the Router in the Application

After creating the router module, you must explicitly register it with the FastAPI application instance.

### Include Router Configuration

Open [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) and import your new router module alongside existing imports. Add the router to the application using `include_router()`:

```python

# api/main.py

from api.routers import hello  # Import the new router module

# ... existing router imports ...

app.include_router(hello.router, prefix="/api", tags=["hello"])

```

This registration pattern (observed in lines 38-62 of [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py)) mounts your endpoints at `/api/hello` and includes them in the auto-generated OpenAPI documentation under the specified tag.

### Prefix and Tags Configuration

The `prefix="/api"` parameter ensures consistency with the existing API structure, while `tags=["hello"]` organizes endpoints in the interactive documentation. You can inspect the registration of built-in routers like `sources` and `notebooks` in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) to verify the exact syntax used in the repository.

## Error Handling and Data Persistence

Custom endpoints must integrate with Open Notebook's data access patterns and error handling infrastructure.

### Global Exception Handling

The [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) file defines global exception handlers that automatically process `HTTPException` instances raised by your router. When you raise `HTTPException(status_code=400, detail="Invalid name")` in your endpoint, the `custom_http_exception_handler` (lines 49-64) wraps the response with appropriate CORS headers, ensuring frontend compatibility without requiring manual header management in each router.

### Domain Layer Integration

For endpoints requiring database interaction, the repository provides two primary patterns:

- **Direct Repository Access**: Use [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) for generic CRUD operations
- **Service Layer Pattern**: Follow the example in [`api/sources_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/sources_service.py) where business logic is abstracted from the router

For simple operations, you may interact directly with domain objects in `open_notebook/domain/`, while complex workflows should implement a service layer to maintain separation of concerns.

## Testing Your Custom Endpoint

The repository includes a test suite using FastAPI's `TestClient`. Create test files in the `tests/` directory following this pattern:

```python

# tests/test_hello.py

from fastapi.testclient import TestClient
from api.main import app

client = TestClient(app)

def test_hello_endpoint():
    resp = client.get("/api/hello?name=Bob")
    assert resp.status_code == 200
    assert resp.json() == {"message": "Hello, Bob"}

```

Execute tests using `uv run pytest tests/test_hello.py` to verify your endpoint responds correctly and integrates properly with the FastAPI application lifecycle.

## Summary

- **Create router modules** in `api/routers/` using `APIRouter()` and standard FastAPI decorator patterns
- **Centralize schemas** in [`api/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/models.py) using Pydantic for request/response validation
- **Register routers** in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) via `app.include_router()` with the `/api` prefix (lines 38-62)
- **Leverage global exception handlers** in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) (lines 49-64) for automatic CORS header management
- **Persist data** through [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) or domain objects in `open_notebook/domain/`
- **Test endpoints** using FastAPI's `TestClient` in the `tests/` directory

## Frequently Asked Questions

### How do I add authentication to my custom FastAPI endpoint in Open Notebook?

The repository handles authentication at the application level in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py). When you register your router using `app.include_router()`, it inherits the global authentication middleware and dependency injection patterns configured in the main application. Check existing routers like [`api/routers/sources.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/sources.py) for examples of how to use FastAPI's `Depends` with authentication schemes already implemented in the codebase.

### Can I use synchronous functions instead of async in my custom router?

Yes, FastAPI supports both async and sync endpoints. However, for consistency with the existing Open Notebook codebase—which uses async patterns for database operations and external service calls—implementing `async def` endpoints is recommended. If you must use synchronous code that blocks the event loop, run it in a thread pool or follow the pattern used in [`api/sources_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/sources_service.py) for background processing.

### Where should I store business logic for my custom API endpoints?

Business logic should reside in the service layer (similar to [`api/sources_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/sources_service.py)) or directly in the domain layer (`open_notebook/domain/`), not in the router module itself. The router (`api/routers/*.py`) should handle HTTP concerns like request validation, response serialization, and error translation, while delegating business operations to domain objects or repository methods. This maintains the separation of concerns visible in the existing codebase.

### How do I handle file uploads in my custom FastAPI router?

Follow the implementation pattern in [`api/routers/sources.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/sources.py), which demonstrates handling file uploads with FastAPI's `UploadFile` and `File` dependencies. The router should accept the file parameter, potentially process it through a service layer, and store it using the repository pattern or domain logic. Ensure you handle temporary file cleanup and validation errors using `HTTPException` to maintain consistency with the global error handling in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py).