# Error Handling and Exception Management in Shadowbroker's FastAPI Backend

> Discover Shadowbroker's robust error handling using custom exceptions, FastAPI HTTP exceptions, and strict logging for observable failure management in your FastAPI backend.

- Repository: [Shadowbroker/Shadowbroker](https://github.com/BigBodyCobain/Shadowbroker)
- Tags: how-to-guide
- Published: 2026-05-07

---

**Shadowbroker’s backend implements a three-layer error-handling strategy that uses domain-specific custom exceptions, converts them to FastAPI HTTPException for clients, and enforces strict logging rules to ensure every failure is observable.**

The BigBodyCobain/Shadowbroker repository contains a Python backend built on FastAPI that treats error handling and exception management as a first-class concern. Rather than relying on generic try/except blocks, the codebase structures failure handling across service, API, and middleware layers to maintain both operational observability and consistent client error responses.

## Domain-Specific Custom Exceptions

The service layer in Shadowbroker defines explicit exception types to represent distinct failure modes. In [`backend/services/unusual_whales_connector.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/services/unusual_whales_connector.py), the code declares classes like `FinnhubConnectorError`, `ShodanConnectorError`, and `PrivacyCoreError` for domain-specific failures.

These custom exceptions wrap low-level HTTP errors from external APIs, allowing the service layer to propagate meaningful error context upward without leaking implementation details. When a connector encounters an `httpx.HTTPError`, it raises a specific domain exception that includes the original cause, preserving the stack trace while translating the error into backend-internal terminology.

## FastAPI HTTPException for Client-Facing Errors

The API layer in [`backend/wormhole_server.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/wormhole_server.py) bridges service-level exceptions to HTTP responses. When a domain exception reaches an endpoint, the code converts it to a `fastapi.HTTPException` with an appropriate status code.

```python

# backend/wormhole_server.py

from fastapi import APIRouter, HTTPException
from .services.unusual_whales_connector import (
    FinnhubConnectorError,
    fetch_financial_data,
)

router = APIRouter()

@router.get("/api/finance/{symbol}")
async def get_finance(symbol: str):
    try:
        data = fetch_financial_data(symbol)
    except FinnhubConnectorError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    return {"symbol": symbol, "quote": data}

```

This pattern ensures that clients receive structured HTTP error codes—such as `502 Bad Gateway` for third-party failures—while the backend maintains internal exception hierarchies.

## Centralized Logging and Exception Visibility

Every exception handler in the Shadowbroker backend must capture the exception object as `exc`. The codebase strictly prohibits bare `except:` clauses. In [`backend/services/wormhole_supervisor.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/services/wormhole_supervisor.py), unexpected exceptions are logged with full stack traces before conversion to HTTP responses.

```python

# backend/services/wormhole_supervisor.py

import logging
from fastapi import HTTPException

log = logging.getLogger("wormhole_supervisor")

def run_supervisor():
    try:
        # Core supervisory logic ...

        pass
    except Exception as exc:
        log.error("Supervisor failure", exc_info=exc)
        raise HTTPException(status_code=500, detail="Internal supervisor error") from exc

```

Log messages emit at **DEBUG** level for silent failures that are re-raised, and **ERROR** level for hard failures that terminate requests. This guarantees that no exception passes through the system without generating observability data.

## Graceful Degradation for Optional Features

The backend handles optional enrichment services using defensive try/except blocks that log failures but allow the main request to continue. In [`backend/services/region_dossier.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/services/region_dossier.py), optional lookups wrap their calls to prevent a single service outage from breaking the entire response.

This approach isolates non-critical dependencies, ensuring that auxiliary data sources—such as open-source intelligence fetches—fail independently without cascading to the primary request flow.

## Test-Driven Exception Hygiene

The repository enforces exception handling standards through automated testing. The file [`backend/tests/test_2c_exception_visibility.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/tests/test_2c_exception_visibility.py) scans the source tree to verify two rules:

1. Every `except Exception` clause must capture the exception object (`except Exception as exc:`)
2. No bare `except Exception: pass` statements exist

This test-driven approach prevents "silent" exception swallowing that could hide production bugs.

## Uniform JSON Response Format

FastAPI global exception handlers in [`backend/app.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/app.py) ensure all errors—handled and unhandled—return a consistent JSON structure. The response includes `detail`, `code`, and `type` fields, making client-side error handling predictable.

```python

# backend/app.py

from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
import logging

app = FastAPI()
log = logging.getLogger("shadowbroker")

@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
    log.debug("HTTPException from %s: %s", request.url.path, exc.detail)
    return JSONResponse(
        status_code=exc.status_code,
        content={"detail": exc.detail, "code": exc.status_code, "type": "http_error"},
    )

@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
    log.error("Unhandled exception on %s", request.url.path, exc_info=exc)
    return JSONResponse(
        status_code=500,
        content={
            "detail": "An unexpected error occurred",
            "code": 500,
            "type": "internal_error",
        },
    )

```

## Code Examples

### Defining a Domain-Specific Exception

The following pattern from [`backend/services/unusual_whales_connector.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/services/unusual_whales_connector.py) demonstrates wrapping third-party HTTP errors in custom exceptions:

```python

# backend/services/unusual_whales_connector.py

class FinnhubConnectorError(Exception):
    """Raised when the Finnhub API returns an unexpected response."""
    pass

def fetch_financial_data(symbol: str) -> dict:
    try:
        resp = httpx.get(f"https://finnhub.io/api/v1/quote?symbol={symbol}")
        resp.raise_for_status()
    except httpx.HTTPError as e:
        raise FinnhubConnectorError(f"Finnhub request failed: {e}") from e
    return resp.json()

```

### Converting Service Errors to HTTP Responses

This snippet from [`backend/wormhole_server.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/wormhole_server.py) shows the translation layer between domain exceptions and client-facing HTTP errors:

```python

# backend/wormhole_server.py

@router.get("/api/finance/{symbol}")
async def get_finance(symbol: str):
    try:
        data = fetch_financial_data(symbol)
    except FinnhubConnectorError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    return {"symbol": symbol, "quote": data}

```

## Summary

- **Domain-specific exceptions** like `FinnhubConnectorError` isolate service-layer failures from transport-layer details
- **FastAPI HTTPException** converts internal errors to standardized HTTP responses with appropriate status codes
- **Mandatory exception capture** (`except Exception as exc`) and structured logging ensure complete observability
- **Graceful degradation** in optional services prevents cascading failures from non-critical components
- **Automated testing** enforces that no exceptions are silently swallowed via bare except clauses

## Frequently Asked Questions

### What custom exceptions does Shadowbroker define?

Shadowbroker defines domain-specific exceptions in [`backend/services/unusual_whales_connector.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/services/unusual_whales_connector.py), including `FinnhubConnectorError`, `ShodanConnectorError`, and `PrivacyCoreError`. These represent failure modes specific to external data connectors rather than generic Python built-ins.

### How does Shadowbroker prevent silent exception swallowing?

The repository includes [`backend/tests/test_2c_exception_visibility.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/tests/test_2c_exception_visibility.py), which statically analyzes the codebase to enforce that every exception handler captures the exception object as `exc`. This prevents bare `except Exception: pass` patterns that could hide runtime errors from logs and monitoring systems.

### Why does Shadowbroker use FastAPI HTTPException instead of returning error responses directly?

Using `fastapi.HTTPException` allows the framework to handle JSON serialization, status code assignment, and header management automatically. It also ensures that all error responses flow through the same middleware pipeline, maintaining uniform response formatting across the API surface.

### How does Shadowbroker handle failures in optional enrichment services?

Optional features like region dossier lookups in [`backend/services/region_dossier.py`](https://github.com/BigBodyCobain/Shadowbroker/blob/main/backend/services/region_dossier.py) wrap external calls in try/except blocks that log the failure at debug level but continue execution. This pattern provides graceful degradation, ensuring that auxiliary data sources failing do not break the primary request flow.