Skip to main content

Request lifecycle

Every API request passes through the same ordered chain of middleware before reaching a handler, and the same error mapping on the way out. Understanding this chain explains how tenancy, rate limiting, and security are enforced uniformly.

Step by step

  1. CORS — only explicit origins are allowed (no wildcard).
  2. Rate limiting — slowapi, backed by Redis, applies per-route limits and returns 429 when exceeded.
  3. Authentication & tenant contextPlatformAuthMiddleware (src/api/auth.py) extracts and validates the JWT via Keycloak, reads the tenant_id (and sub, roles) claims, and stores an AuthContext on request.state.
  4. RLS binding — the tenant is set on the database session (SET app.current_tenant), so every subsequent query is filtered by Row-Level Security.
  5. Dependenciesrequire_tenant raises 401 if there is no tenant context; repositories are injected via Depends.
  6. Handler — runs domain logic through repositories; all SQL is automatically tenant-scoped.
  7. Error mapping — exceptions are translated to status codes by src/api/error_handlers.py.

A concrete read

A concrete write that starts async work

The same pattern — return quickly, poll for completion — applies to report generation and batch risk evaluations. See Temporal workflows for what happens after the workflow starts.