How Custom Exception Types Map to HTTP Status Codes in FastAPI Handlers

Open Notebook registers dedicated exception handlers in api/main.py that automatically translate domain-specific Python exceptions into appropriate HTTP status codes, ensuring that raising NotFoundError returns a 404, AuthenticationError returns a 401, and other custom errors map to their respective status codes without additional boilerplate in route handlers.

In the Open Notebook project, custom exception types defined in open_notebook/exceptions.py create a clean domain-driven error hierarchy. The FastAPI application centralizes HTTP response mapping in api/main.py, where each exception type registers a specific handler that converts Python errors into properly formatted JSON responses with correct status codes and CORS headers.

The Exception Hierarchy in open_notebook/exceptions.py

All domain-specific exceptions inherit from OpenNotebookError, which extends the base Python Exception class. This hierarchy allows the API layer to distinguish between application-specific errors and unexpected Python exceptions while enabling granular error handling.

The following concrete exception types represent distinct failure modes:

  • NotFoundError – Raised when a requested resource does not exist
  • InvalidInputError – Raised when request validation fails
  • AuthenticationError – Raised when credentials are missing or invalid
  • RateLimitError – Raised when rate limiting thresholds are exceeded
  • ConfigurationError – Raised when the application configuration is invalid
  • NetworkError – Raised when upstream network requests fail
  • ExternalServiceError – Raised when external AI services return errors

# open_notebook/exceptions.py

class OpenNotebookError(Exception):
    """Base exception for all Open Notebook errors."""
    pass

class NotFoundError(OpenNotebookError):
    """Raised when a requested resource is not found."""
    pass

class AuthenticationError(OpenNotebookError):
    """Raised when authentication fails."""
    pass

Registering Exception Handlers in api/main.py

The FastAPI application registers explicit exception handlers for each custom type using the @app.exception_handler() decorator. According to the Open Notebook source code, each handler constructs a JSONResponse with the appropriate HTTP status code, a detail field containing the exception message, and CORS headers via the _cors_headers(request) helper.

Client Errors (4xx)

Domain exceptions indicating client-side problems map to standard 4xx status codes according to the following implementation in api/main.py:

  • 404 Not Found: NotFoundError (lines 217-224)
  • 400 Bad Request: InvalidInputError (lines 226-233)
  • 401 Unauthorized: AuthenticationError (lines 235-242)
  • 429 Too Many Requests: RateLimitError (lines 244-251)
  • 422 Unprocessable Entity: ConfigurationError (lines 253-260)

# api/main.py

@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),
    )

@app.exception_handler(AuthenticationError)
async def authentication_error_handler(request: Request, exc: AuthenticationError):
    return JSONResponse(
        status_code=401,
        content={"detail": str(exc)},
        headers=_cors_headers(request),
    )

Server and External Errors (5xx)

Exceptions indicating infrastructure or upstream failures map to 5xx status codes:

  • 502 Bad Gateway: NetworkError (lines 262-269) and ExternalServiceError (lines 271-278)
  • 500 Internal Server Error: Generic OpenNotebookError catch-all (lines 280-287)

# api/main.py

@app.exception_handler(NetworkError)
async def network_error_handler(request: Request, exc: NetworkError):
    return JSONResponse(
        status_code=502,
        content={"detail": str(exc)},
        headers=_cors_headers(request),
    )

@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),
    )

Ensuring CORS Compliance with _cors_headers

All exception handlers delegate to a shared helper function _cors_headers(request) defined earlier in api/main.py. This ensures that error responses include proper CORS headers even when the exception occurs before the CORSMiddleware processes the request, preventing browser-side errors during cross-origin requests.


# api/main.py

def _cors_headers(request: Request):
    # Returns appropriate CORS headers based on the request

    return {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
    }

Practical Usage in Route Handlers

With the mapping centralized in api/main.py, route handlers remain clean and declarative. Developers simply raise the appropriate domain exception, and FastAPI automatically invokes the registered handler.


# api/routers/sources.py

from open_notebook.exceptions import NotFoundError

@router.get("/{source_id}")
async def get_source(source_id: str):
    try:
        return await Source.get(source_id)
    except NotFoundError:
        # Automatically returns HTTP 404 with JSON detail

        raise

When NotFoundError bubbles up from the service layer, FastAPI invokes not_found_error_handler at lines 217-224 of api/main.py, returning a 404 response with JSON content {"detail": "Resource not found"} and proper CORS headers.

Summary

  • Open Notebook defines a hierarchy of domain-specific exceptions in open_notebook/exceptions.py inheriting from OpenNotebookError.
  • Exception handlers in api/main.py map each custom type to a specific HTTP status code using @app.exception_handler() decorators.
  • Status code mappings include 404 for NotFoundError, 401 for AuthenticationError, 429 for RateLimitError, 422 for ConfigurationError, 502 for network/external errors, and 500 for generic failures.
  • CORS compliance is ensured via the _cors_headers(request) helper, which adds headers to all error responses.
  • Route handlers remain clean by raising domain exceptions directly, letting the registered handlers manage HTTP translation automatically.

Frequently Asked Questions

How does FastAPI know which status code to return for custom exceptions?

FastAPI uses the @app.exception_handler() decorator to register specific functions for each exception type. When an exception is raised, FastAPI checks the registry and invokes the matching handler. In Open Notebook, handlers in api/main.py explicitly set the status_code parameter when constructing JSONResponse objects, creating a direct mapping between exception types and HTTP codes.

Can I raise these exceptions from anywhere in the application?

Yes. As long as the exception inherits from the custom classes defined in open_notebook/exceptions.py, raising it from service layers, repositories, or utility functions will trigger the same handler. The exception propagates up the call stack until FastAPI catches it and invokes the registered handler in api/main.py.

Why map both NetworkError and ExternalServiceError to 502?

Both exceptions indicate upstream failures that are outside the client's control. According to the Open Notebook source code, NetworkError handles generic connectivity issues while ExternalServiceError specifically handles AI service failures. Using 502 Bad Gateway for both communicates that the API itself is functioning, but an upstream dependency is unavailable or returning errors.

What happens if I raise an exception not in the custom hierarchy?

Any exception that does not inherit from OpenNotebookError (or one of its specific subclasses) will not match the custom handlers in api/main.py. FastAPI will return a standard 500 Internal Server Error without the custom JSON formatting or CORS headers, unless you have configured additional default exception handlers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →