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
- CORS — only explicit origins are allowed (no wildcard).
- Rate limiting — slowapi, backed by Redis, applies per-route limits and returns 429 when exceeded.
- Authentication & tenant context —
PlatformAuthMiddleware(src/api/auth.py) extracts and validates the JWT via Keycloak, reads thetenant_id(andsub,roles) claims, and stores anAuthContextonrequest.state. - RLS binding — the tenant is set on the database session (
SET app.current_tenant), so every subsequent query is filtered by Row-Level Security. - Dependencies —
require_tenantraises 401 if there is no tenant context; repositories are injected viaDepends. - Handler — runs domain logic through repositories; all SQL is automatically tenant-scoped.
- 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.