# FastAPI Exception Handling in Open Notebook: Mapping Custom Errors to HTTP Status Codes

> Discover how Open Notebook handles FastAPI exceptions by mapping custom errors to HTTP status codes 400-502 using JSONResponse in api main.py. Learn efficient error management.

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

---

**Open Notebook centralizes its FastAPI exception handling in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) by registering dedicated handlers that catch custom domain exceptions and return `JSONResponse` objects with specific HTTP status codes ranging from 400 to 502.**

Open Notebook demonstrates a production-grade approach to API error management using **FastAPI exception handling** patterns. The application decouples business logic failures from HTTP transport details by defining a custom exception hierarchy in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) and mapping each exception type to a specific status code through centralized handlers. This architecture ensures that raising an exception anywhere in the codebase—from service layers to route handlers—automatically produces the correct HTTP response without manual status code management.

## Centralized Handler Registration in api/main.py

All exception handling logic resides in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), where the FastAPI application registers specific handlers for each custom exception type. According to the Open Notebook source code, handlers are defined between lines 99–246, covering both Starlette's built-in HTTP exceptions and the application's custom domain errors.

### Handling StarletteHTTPException

The base handler for standard FastAPI/Starlette HTTP errors appears at lines 99–104. This handler catches `StarletteHTTPException` and returns the exception's native `status_code` (such as 404, 403, or 500) while injecting CORS headers for browser safety.

### Domain-Specific Exception Handlers

Below the base handler, individual functions map specific custom exceptions to their corresponding HTTP status codes. Each handler follows a consistent pattern: catching a specific exception class, logging the error, and returning a `JSONResponse` with the appropriate status code and CORS headers.

## Exception-to-Status Code Mapping

The following table details how Open Notebook maps each custom exception defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) to its HTTP response:

| Custom Exception | HTTP Status | Handler Location in api/main.py |
|------------------|-------------|----------------------------------|
| `StarletteHTTPException` | `exc.status_code` (dynamic) | Lines 99–104 |
| `NotFoundError` | **404** | Lines 176–183 |
| `InvalidInputError` | **400** | Lines 185–192 |
| `AuthenticationError` | **401** | Lines 194–201 |
| `RateLimitError` | **429** | Lines 203–210 |
| `ConfigurationError` | **422** | Lines 212–219 |
| `NetworkError` | **502** | Lines 221–228 |
| `ExternalServiceError` | **502** | Lines 230–237 |
| `OpenNotebookError` (catch-all) | **500** | Lines 239–246 |

## Custom Exception Hierarchy

All domain exceptions inherit from `OpenNotebookError` in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py). This inheritance enables the catch-all handler at lines 239–246 to return a **500** status for any unhandled domain errors, while specific subclasses trigger their dedicated handlers for more precise status codes.

## Practical Implementation Examples

Raising these exceptions in router modules automatically triggers the appropriate handler. You do not need to manually set response codes or import `JSONResponse` in your endpoint logic.

### Returning 404 for Missing Resources

When a repository query returns `None`, raise `NotFoundError` to trigger the 404 handler:

```python

# src/api/routers/notebooks.py (simplified)

from fastapi import APIRouter, Depends
from open_notebook.exceptions import NotFoundError
from open_notebook.domain.notebook import NotebookRepository

router = APIRouter()

@router.get("/{notebook_id}")
async def get_notebook(notebook_id: str):
    notebook = await NotebookRepository.get(notebook_id)
    if notebook is None:
        # Raises 404 via the handler in api/main.py lines 176-183

        raise NotFoundError(f"Notebook {notebook_id} not found")
    return notebook

```

This returns a JSON payload `{"detail": "Notebook abc123 not found"}` with HTTP status **404** and proper CORS headers.

### Returning 400 for Invalid Input

Validate request parameters and raise `InvalidInputError` to generate a **400** response:

```python
from open_notebook.exceptions import InvalidInputError

@router.post("/search")
async def search(query: str):
    if not query.strip():
        # Triggers the 400 handler at api/main.py lines 185-192

        raise InvalidInputError("Search query cannot be empty")
    # Proceed with search logic...

```

## CORS Header Injection for Error Responses

Every handler calls the private helper `_cors_headers(request)` before returning the `JSONResponse`. This ensures that error responses include the necessary `Access-Control-Allow-Origin` and related headers, preventing cross-origin browser blocking even when requests fail validation or authentication checks.

## Summary

- **FastAPI exception handling** in Open Notebook is centralized in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py), where handlers registered at lines 99–246 map specific exception types to HTTP status codes.
- Custom exceptions defined in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) inherit from `OpenNotebookError`, enabling both specific mappings (404, 400, etc.) and a catch-all **500** handler for unexpected domain errors.
- Router modules raise these exceptions directly; the framework automatically converts them to `JSONResponse` objects with appropriate status codes and CORS headers.
- The system covers standard HTTP errors (`StarletteHTTPException`) alongside domain-specific failures like `RateLimitError` (**429**) and `ExternalServiceError` (**502**).

## Frequently Asked Questions

### Where are FastAPI exception handlers defined in Open Notebook?

All exception handlers are defined and registered in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) between lines 99 and 246. This file contains specific handlers for `StarletteHTTPException`, custom domain exceptions like `NotFoundError` and `InvalidInputError`, and a catch-all handler for the base `OpenNotebookError` class.

### What HTTP status code does Open Notebook return when a resource is not found?

Open Notebook returns HTTP **404** when a `NotFoundError` is raised. The handler at lines 176–183 in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) catches this specific exception and returns a JSON response with the 404 status code.

### How does Open Notebook ensure CORS headers are present on error responses?

Every exception handler in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) calls the private helper function `_cors_headers(request)` and includes these headers in the returned `JSONResponse`. This ensures that browser clients receive proper `Access-Control-Allow-Origin` headers even when the API returns 4xx or 5xx error statuses.

### Can I add new custom exceptions with specific HTTP status codes to Open Notebook?

Yes. Define a new exception class in [`open_notebook/exceptions.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/exceptions.py) that inherits from `OpenNotebookError`, then register a corresponding handler in [`api/main.py`](https://github.com/lfnovo/open-notebook/blob/main/api/main.py) using the `@app.exception_handler()` decorator. Return a `JSONResponse` with your desired status code and include CORS headers via `_cors_headers(request)` to maintain consistency with the existing error handling patterns.