ADR-005: FastAPI as unified API gateway
Status: Proposed Date: 2026-03-05
Context
pluginlake needs to support multiple client types (web UI, CLI, external integrations, other pluginlake stations) while maintaining centralized authentication, authorization, and audit logging. Dagster OSS does not provide built-in auth or a data-serving API. We plan to build a custom portal UI and eventually run Dagster in headless mode.
Decision
Use FastAPI as the single entry point for all external access to a pluginlake instance. All clients (UI, CLI, external systems) interact exclusively through the FastAPI API. No downstream service (Dagster, DuckLake, storage) is exposed directly.
FastAPI auto-generates an OpenAPI specification from the endpoint definitions. This gives us a machine-readable API contract that can be used to generate client SDKs, keep documentation in sync, and validate requests and responses, all without maintaining a separate spec file.
Architecture
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ Web UI │ │ CLI │ │External svc │
└────┬────┘ └────┬────┘ └──────┬───────┘
│ │ │
└────────────┼──────────────┘
│
┌───────▼────────┐
│ FastAPI │
│ (authN/authZ │
│ audit, rate │
│ limiting) │
└───┬───┬───┬────┘
│ │ │
┌────────┘ │ └────────┐
▼ ▼ ▼
DuckLake Dagster Audit log
(catalog (orchestration (JSONL)
+ data) via GraphQL)
API surface
Example API endpoints:
| Domain | Endpoint | Purpose |
|---|---|---|
| Catalog | GET /api/catalog/schemas |
List DuckLake schemas |
| Catalog | GET /api/catalog/tables |
List tables |
| Catalog | GET /api/catalog/tables/{schema}/{table} |
Query table data |
| Orchestration | GET /api/runs |
List Dagster runs |
| Orchestration | GET /api/runs/{id} |
Run status and logs |
| Orchestration | POST /api/assets/{key}/materialize |
Trigger asset materialization |
| Orchestration | GET /api/assets |
List registered assets |
| Lineage | GET /api/assets/{key}/lineage |
Asset-level dependency graph |
| Lineage | GET /api/assets/{key}/lineage/columns |
Column-level lineage for an asset |
| System | GET /health |
Health check (no auth) |
| Docs | GET /docs |
Interactive OpenAPI documentation (Swagger UI) |
| Docs | GET /openapi.json |
Machine-readable OpenAPI spec |
Why FastAPI gateway over exposing Dagster directly
| Concern | Dagster UI direct | FastAPI gateway |
|---|---|---|
| Authentication | None in OSS | Centralized (API key, OAuth, etc.) |
| Authorization | None in OSS | Role-based, per-endpoint |
| CLI / scripting access | Requires Dagster CLI + gRPC | Any HTTP client |
| Data serving | Not supported | DuckLake reads with auth |
| Audit logging | Limited | Full control via middleware |
| Rate limiting | None | Standard middleware |
| Multi-station federation | Each station exposes own UI | Uniform API contract across stations |
| API documentation | None | Auto-generated OpenAPI spec, interactive Swagger UI |
Rollout phases
- Gateway API: Build
src/pluginlake/api/with catalog browsing, health, and API key auth. Dagster UI remains available for development. - Orchestration proxy: Add Dagster GraphQL proxy endpoints (runs, materialization, schedules).
- Client layer: Build UI and/or CLI that consume only
/api/*. Frontend choice (SPA, Streamlit, TUI) is a pure presentation concern. Client SDKs can be generated from the OpenAPI spec. - Headless Dagster: Remove
dagster-webserverfrom production. Keep onlydagster-daemon+dagster-code-server. The FastAPI service is the sole user-facing entry point.
Consequences
- One auth boundary to maintain, not per-service.
- UI and CLI are thin clients with no direct database or orchestrator access.
- Adding new clients (mobile, partner integrations) requires zero backend changes.
- Dagster becomes an internal implementation detail, replaceable without breaking consumers.
- OpenAPI spec is always in sync with the code, so documentation and client SDKs never drift.
- Adds one service to deploy and monitor (acceptable given it already exists in the stack).