Files
platform/docs/per-user-api-keys.md
T

4.1 KiB

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

{
  "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 <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 resolutionresolveApiKeyForModel(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