4.1 KiB
4.1 KiB
Per-User API Keys — Architecture Analysis
Current State
- Single shared
auth.jsonat~/.pi/agent/auth.json(server-side) - Pi CLI supports
--api-keyflag — we already use this inpi-bridge.tsviaresolveApiKeyForModel() - 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()inpi-bridge.tschecks user keys first, falls back to systemauth.json- Key passed to Pi via
--api-keyCLI 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/enabledModelssettings fields drive filtering - Model listing becomes a union: system models + user's provider models
Key Design Decisions
- No new DB table — use existing
user_settingsJSONB column - No per-user auth.json files — all key resolution server-side via
--api-keyflag - Eventually remove PI_CONFIG_DIR mount — auth.json in container no longer needed for keys (still needed for local providers/models.json)
- 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
{
"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)
- Determine model from user settings or default
resolveApiKeyForModel(model)extracts provider fromprovider/model-name- Looks up key in auth.json (currently system-level only)
- Passes
--api-key <key>when spawning Pi process - Container never sees raw credentials
Implementation Steps
- Add key storage — extend
UserSettingstype, add API endpoint for CRUD - Update key resolution —
resolveApiKeyForModel(model, userId?)checks user DB first - Update model listing — merge system models with user-unlocked provider models
- Build settings UI — API key input per provider in AI settings page
- Remove PI_CONFIG_DIR mount (later) — once models.json is also handled server-side