Skip to content

How the platform works

FastAPI app factory, middleware order, lifespan startup, and API router registration.

Intended audience: Stakeholders, Business analysts, Solution architects, Developers, Testers

Redis, Postgres, and optional RabbitMQ behavior is defined at startup and in middleware; see also Configuration and Monitoring guides.

Learning outcomes by role

Stakeholders

  • Explain how shared infrastructure (databases, cache, optional broker) underpins uptime and cost trade-offs for a multi-tenant deployment.
  • Relate middleware ordering to customer-visible failures (auth, rate limits, sessions) when prioritizing incidents.

Business analysts

  • Document prerequisites and dependencies for operational runbooks (which subsystems must be healthy before API calls succeed).
  • Align acceptance language with observable HTTP outcomes (401, 403, rate limiting) tied to documented middleware behavior.

Solution architects

  • Map inbound middleware order, lifespan initialization, and router registration to integration boundaries and NFRs (security headers, CORS, scaling).
  • Justify where Redis, Postgres, and RabbitMQ sit in deployment diagrams relative to the FastAPI process.

Developers

  • Trace configure_* registration in cadence.main against the inbound execution order when debugging request.state and errors.
  • Locate register_api_routers and lifespan steps when extending startup or adding routers.

Testers

  • Predict failure modes from middleware order (session before rate limit, public paths, missing Redis) and design negative tests accordingly.
  • Correlate 401 versus 403 scenarios with authentication versus tenant and permission layers after this page.

Cadence is a FastAPI service: startup wires databases and optional brokers; each HTTP request passes through middleware (errors, auth, tenant session, rate limits) before a route handler runs.

  • Single deploy, many tenants — One API process serves all organizations; isolation is enforced after authentication in tenant and permission layers (see Multi-tenancy).
  • Operational leverage — Health and behavior depend on PostgreSQL, Redis (sessions and rate limiting), and optionally RabbitMQ for orchestrator-related messaging; gaps there surface as structured errors or degraded features, not silent data mixing.
  • Risk posture — Middleware order is fixed in code: changes to infrastructure or config alter when failures appear (e.g. before or after rate limiting), which matters for incident triage.
  • Single source for “what runs first” — Product and operations can reference this page instead of ad-hoc diagrams when writing prerequisites (“Redis required for interactive JWT sessions and rate limits”).
  • Observable outcomes — Link user stories to HTTP status patterns: missing or invalid credentials (401), known user but disallowed org or role (403), and rate limiting (too many requests) as separate acceptance themes.
  • Scope boundaries — OpenAPI describes routes; effective rate limits and session behavior follow middleware and tier rules documented here and in feature pages.

You need one place to see middleware order, startup steps, and router mounting without opening half a dozen modules. That mental model matters when debugging 401/403, missing sessions, or rate-limit surprises.

flowchart LR
  Client[Client] --> MW[Middleware stack]
  MW --> R[Route handler]
  R --> MW
  MW --> Client

Inbound requests hit the outermost middleware first (see below), then deeper layers, then your route.

Inbound middleware stack (first layer at top) Request enters at ErrorHandlerMiddleware, then AuthenticationMiddleware, TenantContextMiddleware, RateLimitMiddleware, security headers, and CORS, matching cadence.main registration order inverted for inbound traffic. Client (HTTP) ErrorHandlerMiddleware AuthenticationMiddleware TenantContextMiddleware RateLimitMiddleware Security headers CORS

Inbound order (top first). Registration order in cadence.main is the reverse; see Middleware chain.

main.py registers middleware in this registration order (first line runs first):

  1. configure_cors_middleware
  2. configure_security_headers_middleware
  3. configure_rate_limiting_middleware
  4. configure_tenant_context_middleware
  5. configure_authentication_middleware
  6. configure_error_handlers_middleware

In Starlette/FastAPI, the last add_middleware runs first on each incoming HTTP request. So the outermost layer (first to see the request) is ErrorHandlerMiddleware, then AuthenticationMiddleware, then TenantContextMiddleware, then RateLimitMiddleware, then security headers, then CORS.

The inline comment in main.py summarizes the middle of the stack: inbound flow among auth, tenant, and rate limiting is authentication → tenant context → rate limiting — JWT/API key validation and request.state preparation happen before tenant session hydration and before Redis-backed rate limits.

Layer (incoming order, after network)Purpose
Error handlingError handler middleware assigns X-Request-ID, catches uncaught exceptions, returns JSON error payloads; avoids double-send if response already started.
AuthenticationAuthentication middleware applies a public path allowlist; Authorization: Bearer for JWTs; X-API-KEY for API keys; sets request.state.api_key_row when a key is used.
Tenant contextTenant context middleware loads TokenSession from Redis (JWT jti) or synthesizes from API key row.
Rate limitingRate limiting middleware uses a Redis sliding window; per org/user/IP; tier from cache; no-op if Redis client is missing.
Security headersSecurity headers middleware sets nosniff, DENY frame, Referrer-Policy, Permissions-Policy; HSTS when environment is production.
CORSCORSMiddleware — origins from settings; logs if unset.

cadence.main builds a single FastAPI app with lifespan, OpenAPI customization, middleware, and router registration. The call order for middleware registration is explicit in source:

Middleware registration order
app.openapi = build_openapi_schema_generator(app)
# ...
cors_origins = app_settings.cors_origins or []
configure_cors_middleware(app, cors_origins)
configure_security_headers_middleware(app, app_settings)
# Middleware runs outer-first (last registered). Order here is inner→outer:
# rate → tenant → auth → error, so inbound is auth → tenant → rate (session
# exists before rate limiting; API keys validated before tenant builds session).
configure_rate_limiting_middleware(app)
configure_tenant_context_middleware(app, app_settings)
configure_authentication_middleware(app, app_settings)
configure_error_handlers_middleware(app, app_settings)
register_api_routers(app)
  • lifespan=create_lifespan_handler(app_settings) — Async startup/shutdown (Lifespan).
  • app.openapi = build_openapi_schema_generator(app) — Custom OpenAPI generation.
  • register_api_routers(app) — Mounts HTTP routers.

uvicorn cadence.main:app is the normal process entry; the if __name__ == "__main__" block is for local development.

create_lifespan_handler runs before any request is served:

  1. Config validationsettings.validate_production_config(); in production, insecure config raises and aborts startup.
  2. PostgreSQL + Redisinitialize_database_clients connects pools; clients attached to app.state.
  3. Repositories and stores_create_repositories builds Postgres repos, SessionStoreRepository (Redis), MessageRepository, PluginStoreRepository (optional S3), bootstraps global_settings keys (token TTLs, OAuth defaults) if missing.
  4. RBACBuiltInRBACProvider wired with role repositories and Redis async client.
  5. Telemetry — OpenTelemetry from DB settings; FastAPI/SQLAlchemy/Redis instrumentation; LLM instrumentors after hot-tier load.
  6. ServicesTenantService, AuthService, OAuthService, ConversationService, PluginService, CentralPointService, etc. on app.state.
  7. Orchestrator poolOrchestratorPool + factory; OrchestratorService attached.
  8. RabbitMQ (optional) — If RabbitMQClient.connect() succeeds: publisher + consumer for orchestrator events; on failure, logs a warning and leaves app.state.rabbitmq_client as None.
  9. Pluginsensure_all_catalog_plugins_local pulls catalog plugins to local cache when S3 is configured.
  10. Hot tierload_hot_tier_instances loads active hot-tier orchestrators into the pool.

Shutdown (after yield): stop event consumer, disconnect RabbitMQ, shutdown_orchestrator_pool, disconnect Postgres/Redis, shutdown_telemetry.

register_api_routers includes routers in this order (path prefixes live on each router):

API router registration
def register_api_routers(application: FastAPI) -> None:
"""Register all API route controllers with the application."""
application.include_router(health.router)
application.include_router(oauth2.router)
application.include_router(auth.router)
application.include_router(api_key.router)
application.include_router(chat.router)
application.include_router(orchestrator.router)
application.include_router(engine.router)
application.include_router(plugins.router)
application.include_router(tenant.router)
application.include_router(admin.router)
application.include_router(telemetry.router)
application.include_router(stats.router)

Summarized: healthoauth2authapi_keychatorchestratorenginepluginstenantadmintelemetrystats. Order only matters when two routes could match the same path pattern; in practice each module owns distinct prefixes.

  • Rate limiting needs Redis in normal operation; if the client is missing, the middleware skips limiting (see RateLimitMiddleware.dispatch).
  • RabbitMQ is optional: without the broker, orchestrator event messaging is off; the rest of the API can still run.