# How Open Notebook Maps Its Custom Exception Hierarchy to HTTP Status Codes

> Learn how Open Notebook maps custom Python exceptions to HTTP status codes using FastAPI exception handlers. Understand error handling from 400 to 502.

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

---

**Open Notebook converts domain-specific Python exceptions into standardized HTTP responses by registering FastAPI exception handlers in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) that map each exception type to a specific status code, ranging from 400 for invalid input to 502 for external service failures.**

The `lfnovo/open-notebook` project implements a robust error handling strategy that bridges Python's exception mechanism with HTTP semantics. In [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py), the codebase defines a **custom exception hierarchy**, while [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) registers FastAPI handlers that **map** these exceptions to **HTTP status codes** with appropriate CORS headers.

## The Domain-Specific Exception Hierarchy

The [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) module defines a base `OpenNotebookError` class that serves as the parent for all application-specific exceptions. This hierarchy includes specialized exceptions for different failure modes, from missing resources to external service outages.

Key exceptions include:

- **NotFoundError**: Raised when a requested resource (e.g., notebook or source) does not exist.
- **InvalidInputError**: Signals malformed or invalid client data.
- **AuthenticationError**: Indicates authentication failures or invalid credentials.
- **RateLimitError**: Triggered when clients exceed request quotas.
- **ConfigurationError**: Detects misconfiguration issues, such as missing environment variables.
- **NetworkError**: Captures underlying network failures when calling external services.
- **ExternalServiceError**: Raised when third-party AI services return errors.
- **DatabaseOperationError**, **FileOperationError**, **UnsupportedTypeException**, and **NoTranscriptFound**: Additional domain-specific errors that inherit from the base class.

## Mapping Exceptions to HTTP Status Codes

In [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), FastAPI exception handlers convert raised exceptions into HTTP responses using the `@app.exception_handler` decorator. The mapping follows REST conventions, using 4xx codes for client errors and 5xx codes for server failures.

### Client Error Responses (4xx)

The following mappings handle client-side issues:

- **404 Not Found**: `NotFoundError` handler at lines 17‑23.
- **400 Bad Request**: `InvalidInputError` handler at lines 26‑32.
- **401 Unauthorized**: `AuthenticationError` handler at lines 35‑41.
- **429 Too Many Requests**: `RateLimitError` handler at lines 44‑50.
- **422 Unprocessable Entity**: `ConfigurationError` handler at lines 53‑59.

### Server Error Responses (5xx)

Server and external service failures map to 5xx codes:

- **502 Bad Gateway**: Both `NetworkError` (lines 62‑68) and `ExternalServiceError` (lines 71‑77) return this status when external calls fail.
- **500 Internal Server Error**: The base `OpenNotebookError` handler (lines 80‑86) catches any uncaught application exceptions. **DatabaseOperationError**, **UnsupportedTypeException**, **FileOperationError**, and **NoTranscriptFound** fall back to this handler because they inherit from `OpenNotebookError` but lack specific handlers.

## How Exception Resolution Works

When an exception is raised inside a service or router, FastAPI traverses the exception hierarchy to find the most specific registered handler. If no handler exists for a specific subclass (such as `DatabaseOperationError`), FastAPI invokes the handler for its nearest ancestor, `OpenNotebookError`, resulting in a 500 response.

## Practical Implementation Example

Services raise domain exceptions without worrying about HTTP details, while routers automatically receive appropriate JSON responses.

```python

# src/open_notebook/services/some_service.py

from open_notebook.exceptions import NotFoundError, InvalidInputError

def get_notebook(notebook_id: str):
    notebook = db.fetch(notebook_id)
    if notebook is None:
        # This will be turned into a 404 response

        raise NotFoundError(f"Notebook {notebook_id} not found")
    if not isinstance(notebook_id, str):
        # This will be turned into a 400 response

        raise InvalidInputError("Notebook ID must be a string")
    return notebook

```

When the endpoint calls this service:

```python

# src/api/routers/notebooks.py

from fastapi import APIRouter, Depends
from open_notebook.services.notebook_service import get_notebook

router = APIRouter()

@router.get("/notebooks/{notebook_id}")
async def read_notebook(notebook_id: str):
    return get_notebook(notebook_id)

```

If `get_notebook` raises `NotFoundError`, FastAPI returns:

```json
{
  "detail": "Notebook abc123 not found"
}

```

with HTTP status **404** and appropriate CORS headers.

## Summary

- Open Notebook defines a custom exception hierarchy in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) with `OpenNotebookError` as the base class.
- FastAPI handlers in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) map specific exceptions to HTTP status codes: 404 for not found, 400 for invalid input, 401 for authentication failures, 429 for rate limits, 422 for configuration errors, and 502 for external service issues.
- Unhandled subclasses like `DatabaseOperationError` fall back to the base handler, returning 500 internal server errors.
- This separation allows business logic to raise domain-specific exceptions while the API layer handles HTTP translation automatically.

## Frequently Asked Questions

### What happens if I raise an exception that isn't registered in api/main.py?

If you raise a subclass of `OpenNotebookError` that lacks a specific handler (such as `DatabaseOperationError` or `FileOperationError`), FastAPI will use the base `OpenNotebookError` handler registered at lines 80‑86, returning a 500 Internal Server Error with JSON detail.

### Why does ConfigurationError return 422 instead of 500?

The `ConfigurationError` maps to 422 Unprocessable Entity (lines 53‑59) because it typically indicates a semantic error in the request or environment setup that the client might be able to correct, rather than a catastrophic server failure.

### How can I add a custom exception handler for a new exception type?

Define your exception in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) inheriting from `OpenNotebookError`, then add a new `@app.exception_handler` decorator in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) following the pattern used for existing handlers (lines 17‑77), returning the appropriate status code and CORS headers.

### Do these exception handlers include CORS headers in the response?

Yes, according to the source code in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), each exception handler returns responses that include appropriate CORS headers, ensuring browser clients can read the error responses even when making cross-origin requests.