# How Error Handling Works in the Open Notebook API: Architecture and Implementation

> Understand Open Notebook API error handling. Learn how FastAPI exception handlers and a custom hierarchy deliver automatic JSON error responses with CORS headers.

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

---

**The Open Notebook API implements centralized error handling through a custom exception hierarchy registered with FastAPI exception handlers that automatically return JSON error responses with proper CORS headers.**

The Open Notebook project exposes a FastAPI-based HTTP API that requires robust error handling strategies to deliver consistent client experiences. Instead of exposing raw Python tracebacks, the codebase implements a domain-specific exception hierarchy with centralized handlers that transform errors into standardized JSON payloads. This architecture ensures that every error response—including cross-origin requests—returns appropriate HTTP status codes and descriptive messages while keeping implementation details encapsulated.

## Domain-Specific Exception Hierarchy

All API-level errors inherit from the base `OpenNotebookError` class defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py). This design creates a typed error system that distinguishes between different failure modes without leaking internal implementation details to API consumers.

The hierarchy includes specific exception types for distinct error scenarios:

- `AuthenticationError` – Credential and token validation failures, typically raised in authentication middleware
- `InvalidInputError` – Malformed request data and validation issues
- `NotFoundError` – Missing resources and failed database lookups
- `ConfigurationError` – Environment and setup problems
- `RateLimitError` – Request quota exceeded scenarios
- `NetworkError` and `ExternalServiceError` – Downstream connectivity and third-party service failures

This inheritance structure allows the FastAPI exception handlers to catch specific error types or the generic base class depending on the required granularity of the response.

## Centralized Error Handling Implementation

The FastAPI application registers dedicated handlers for each exception type in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py). These handlers intercept raised exceptions and convert them into `JSONResponse` objects with appropriate HTTP status codes and CORS headers.

Each handler follows a consistent pattern, mapping specific exception types to standard HTTP status codes:

```python
@app.exception_handler(NotFoundError)
async def not_found_error_handler(request: Request, exc: NotFoundError):
    return JSONResponse(
        status_code=404,
        content={"detail": str(exc)},
        headers=_cors_headers(request),
    )

```

Similar handlers exist for `InvalidInputError` (400), `AuthenticationError` (401), `RateLimitError` (429), `ConfigurationError`, `NetworkError`, `ExternalServiceError` (typically 500-level), and the generic `OpenNotebookError` base class as a fallback:

```python
@app.exception_handler(OpenNotebookError)
async def open_notebook_error_handler(request: Request, exc: OpenNotebookError):
    return JSONResponse(
        status_code=500,
        content={"detail": str(exc)},
        headers=_cors_headers(request),
    )

```

The system also intercepts FastAPI's built-in `HTTPException` (Starlette defaults) to apply CORS headers to standard HTTP errors like 413 "Payload Too Large" during file uploads.

## CORS-Aware Error Responses

A critical implementation detail involves the `_cors_headers(request)` helper function defined in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py). Every exception handler invokes this function to inject `Access-Control-Allow-*` headers into error responses. This prevents browsers from blocking cross-origin error bodies during failed requests, ensuring that client-side JavaScript can read error details even when requests fail preflight or authentication checks.

Without this pattern, browser-based clients would receive opaque network errors for cross-origin failures, making debugging impossible. The Open Notebook implementation ensures CORS compliance for both successful and error responses consistently.

## Error Handling Patterns in the Service Layer

Domain logic raises these custom exceptions to signal specific failure states rather than returning `None` or generic exceptions. In [`open_notebook/domain/base.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/base.py), repository methods raise `NotFoundError` when database lookups return empty results:

```python
if not record:
    raise NotFoundError(f"{table_name} with id {id} not found")

```

Router modules such as [`api/routers/chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/chat.py) and [`api/routers/source_chat.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/source_chat.py) propagate these exceptions directly to the FastAPI handlers rather than catching them locally. The [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) module raises `AuthenticationError` when middleware detects invalid API tokens:

```python
if not user.is_authorized:
    raise AuthenticationError("Invalid API token")

```

This pattern ensures consistent 401 responses across all protected endpoints without duplicating error response logic in individual routes.

Example usage in an endpoint:

```python
from open_notebook.exceptions import NotFoundError

@router.get("/{notebook_id}")
async def get_notebook(notebook_id: str):
    notebook = await notebook_service.get(notebook_id)
    if notebook is None:
        raise NotFoundError(f"Notebook {notebook_id} not found")
    return notebook

```

## Summary

- **Domain-specific exceptions**: All errors inherit from `OpenNotebookError` in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) to create a typed error system that distinguishes between authentication, input validation, and resource availability failures.
- **Centralized handlers**: [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) registers FastAPI exception handlers using `@app.exception_handler()` decorators that map exceptions to JSON responses with correct HTTP status codes.
- **CORS compliance**: The `_cors_headers()` helper ensures all error responses include necessary `Access-Control-Allow-*` headers for browser clients, preventing opaque cross-origin errors.
- **Service layer integration**: Repository code in [`open_notebook/domain/base.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/base.py) and authentication logic in [`api/auth.py`](https://github.com/lfnovo/open-notebook/blob/main/api/auth.py) raise specific exceptions like `NotFoundError` and `AuthenticationError` to trigger standardized error responses automatically.

## Frequently Asked Questions

### What is the base exception class used in the Open Notebook API?

The base class is `OpenNotebookError`, defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py). All domain-specific exceptions such as `AuthenticationError` and `NotFoundError` inherit from this class, enabling centralized handling through FastAPI's exception handler system while maintaining type safety throughout the codebase.

### How does the API handle CORS headers in error responses?

The API calls `_cors_headers(request)` within every exception handler in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) to inject `Access-Control-Allow-*` headers into error responses. This ensures browser clients can read error details during cross-origin requests rather than receiving opaque network errors, which is critical for single-page applications consuming the API.

### Where are the FastAPI exception handlers registered?

Exception handlers are registered in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) using the `@app.exception_handler()` decorator. Each custom exception type—including `NotFoundError`, `InvalidInputError`, and `AuthenticationError`—has a dedicated handler function that returns a `JSONResponse` with the appropriate HTTP status code and error message.

### How should service modules signal that a resource was not found?

Service modules should raise `NotFoundError` imported from `open_notebook.exceptions`. Repository code in [`open_notebook/domain/base.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/base.py) demonstrates this pattern by raising the exception when database queries return no records, which automatically triggers a 404 response via the registered handler without manual status code management.