Architecture Overview
Lattice is a two-app monorepo: the gateway is the thing applications and providers actually talk to, and the dashboard is an admin surface built entirely on top of the gateway's own management API.
Two trust boundaries, two APIs
/v1/** | /management/v1/** | |
|---|---|---|
| Who calls it | Company applications | The dashboard (or any admin script) |
| Auth | Per-application lattice_sk_… key | A separate management API key |
| Scope | Chat completions, model listing — scoped to the calling application's policy | Provisioning and observability across every application |
| Middleware | 02.auth.ts → 03.rate-limit.ts | 04.management-auth.ts |
Provider credentials (OPENAI_API_KEY, etc.) live only in the gateway's environment. Neither an application nor the dashboard can ever see them — the dashboard reaches the gateway the same way an application would, over the network, with a bearer key, just against /management/v1 instead of /v1.
Runtime stack
- Gateway: Nitro v3 + h3, running on Bun's
bunpreset. Postgres via Drizzle ORM (drizzle-orm/bun-sql). - Dashboard: Nuxt 4 + Nuxt UI, session auth via better-auth, data fetching/caching via Pinia Colada.
- Shared: Valibot for every schema (requests, responses, environment variables) in both apps and in
packages/api-contract.
Why two separate databases
The gateway's Postgres database owns applications, API keys, usage counters, rate-limit counters, request logs, and provider health state — the operational data of the gateway itself. The dashboard's Postgres database owns exactly one thing: better-auth's session/user/account tables for the humans who sign into the dashboard. They can be the same physical Postgres server (different logical databases) or entirely separate servers; nothing in either app assumes otherwise. This keeps "who can sign into the admin UI" architecturally separate from "what the gateway is doing," so rotating or restoring one never touches the other.
Where to go next
- Request Lifecycle — what happens between a client's request and its response.
- Model Registry & Routing — how a model name resolves to a provider call.
- Failover & Circuit Breaking — what happens when a provider is down.
- Data Model — every table, every column.
- Security — key hashing, the pepper, and the two trust tiers.