# Per-User API Keys — Architecture Analysis ## Current State - Single shared `auth.json` at `~/.pi/agent/auth.json` (server-side) - Pi CLI supports `--api-key` flag — we already use this in `pi-bridge.ts` via `resolveApiKeyForModel()` - User settings have unused fields: `ai.enabledModels`, `ai.enabledProviders`, `ai.disabledProviders` - 13 providers supported: anthropic, openai, google, groq, mistral, xai, openrouter, minimax, huggingface, azure-openai-responses, opencode, zai, cerebras ## Architecture ### Layer 1: Key Resolution **Precedence:** User key > System key > fail - Store user keys in `user_settings.ai.apiKeys` (JSONB column, no new DB table needed) - Format: `{ "ai": { "apiKeys": { "openai": "sk-...", "anthropic": "sk-ant-..." } } }` - `resolveApiKeyForModel()` in `pi-bridge.ts` checks user keys first, falls back to system `auth.json` - Key passed to Pi via `--api-key` CLI flag (already implemented for system keys) - Keys never touch the container filesystem — all resolution is server-side ### Layer 2: Model Visibility - System access policy defines base available providers/models - User's own provider keys unlock additional providers - Existing `enabledProviders`/`enabledModels` settings fields drive filtering - Model listing becomes a union: system models + user's provider models ## Key Design Decisions 1. **No new DB table** — use existing `user_settings` JSONB column 2. **No per-user auth.json files** — all key resolution server-side via `--api-key` flag 3. **Eventually remove PI_CONFIG_DIR mount** — auth.json in container no longer needed for keys (still needed for local providers/models.json) 4. **Model listing = union** of system + user models ## What This Enables - Admin deploys with their keys → all users can chat - User adds their own key → gets access to that provider - User's key takes priority (user pays for their own usage) - No credential leakage (keys in DB, passed as CLI args, never on container filesystem) ## Files to Change | File | Change | |------|--------| | `src/servers/api/pi/pi-bridge.ts` | Update `resolveApiKeyForModel()` to check user settings first | | `src/servers/api/settings/settings.ts` | API endpoint for saving/deleting user API keys (encrypted at rest ideally) | | `src/workspaces/state/src/useSettings.ts` | Add `ai.apiKeys` to `UserSettings` type | | `src/servers/api/pi/list-models.ts` | Union system + user provider models | | `src/workspaces/officerdev/src/hooks/usePiModels.ts` | Filter models based on user's available providers | | `src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/AIModels.tsx` | UI for managing per-user API keys | | `src/servers/api/server-settings/pi-mono.ts` | Distinguish admin vs user key management | ## Deep-Dive: Pi CLI & Auth ### CLI Flags - `--provider` — force provider - `--model` — force model (format: `provider/model-name`) - `--api-key` — force API key (takes precedence over auth.json and env vars) - `--system-prompt` — system prompt ### Supported Environment Variables `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `XAI_API_KEY`, `OPENROUTER_API_KEY`, etc. ### auth.json Format ```json { "provider_id": { "type": "api_key", "key": "sk-..." } } ``` ### Key Resolution Precedence (inside Pi) `--api-key` flag > auth.json entry > environment variable > built-in providers ### Pi Spawn Flow (pi-bridge.ts) 1. Determine model from user settings or default 2. `resolveApiKeyForModel(model)` extracts provider from `provider/model-name` 3. Looks up key in auth.json (currently system-level only) 4. Passes `--api-key ` when spawning Pi process 5. Container never sees raw credentials ## Implementation Steps 1. **Add key storage** — extend `UserSettings` type, add API endpoint for CRUD 2. **Update key resolution** — `resolveApiKeyForModel(model, userId?)` checks user DB first 3. **Update model listing** — merge system models with user-unlocked provider models 4. **Build settings UI** — API key input per provider in AI settings page 5. **Remove PI_CONFIG_DIR mount** (later) — once models.json is also handled server-side