Authentication Methods
API Keys
The traditional approach. Set an environment variable or configure it inpolpo.json:
OAuth Login
Use your existing subscription (Claude Pro/Max, ChatGPT Plus/Pro, GitHub Copilot, Google) directly. No separate API key needed.~/.polpo/auth-profiles.json with automatic token refresh.
OAuth credentials are encrypted at rest with 0600 file permissions (owner read/write only) and the
~/.polpo/ directory uses 0700 permissions (owner only). Writes are atomic (temp file + rename) to prevent corruption.OAuth Providers
Five providers support OAuth login. Each provider page has a complete, self-contained OAuth guide — the summary below gives you the quick reference.| Provider | Subscription | Flow | Port | Provider Guide |
|---|---|---|---|---|
| Anthropic | Claude Pro / Max | Auth code + PKCE | — (manual paste) | Anthropic |
| OpenAI Codex | ChatGPT Plus / Pro | Auth code + PKCE | 1455 | OpenAI |
| GitHub Copilot | Copilot Individual / Business / Enterprise | Device code | — (no server) | GitHub Copilot |
| Google Gemini CLI | Google account | Auth code + PKCE | 8085 | Google Gemini |
| Google Antigravity | Google account | Auth code + PKCE | 51121 | Google Gemini |
Anthropic (Claude Max)
The most common use case. Your Claude Max subscription gives you high-rate API access without a separate billing account.- Polpo prints a URL — open it in your browser
- Authorize with your Anthropic account
- Copy the authorization code (format:
code#state) - Paste it back in the terminal
Full Anthropic OAuth guide
Supported subscriptions, token refresh, multiple accounts, billing disable, and session pinning for Anthropic.
OpenAI Codex (ChatGPT Plus/Pro)
localhost:1455 captures the code automatically. If the callback fails, you can paste the redirect URL manually.
Full OpenAI Codex OAuth guide
Supported subscriptions, provider ID differences, token refresh, and session pinning for OpenAI Codex.
GitHub Copilot
The simplest flow — no browser redirect needed:- Polpo shows a device code and a URL
- Go to the URL, enter the code
- Polpo polls for authorization automatically
Full GitHub Copilot OAuth guide
Supported subscriptions, device code flow details, multiple accounts, and session pinning for GitHub Copilot.
Google Gemini CLI / Antigravity
Full Google OAuth guide
Both Gemini CLI and Antigravity flows, multiple accounts, token refresh, and session pinning for Google.
CLI Commands
polpo auth login [provider]
Login to an OAuth provider. Without a provider argument, shows an interactive picker:
polpo auth status
Show all stored auth profiles and their status:
*(green) — active, ready to use~(yellow) — expired but auto-refresh availablex(red) — expired, no refresh token, re-login required
polpo auth logout [provider]
Remove stored credentials:
API Key Resolution Order
When Polpo needs an API key for a provider, it checks these sources in order:- Config overrides everything — if you set
apiKeyinpolpo.json, OAuth profiles are ignored for that provider - Env vars override OAuth — useful for CI/CD where you use a service API key
- OAuth is the fallback — perfect for local development where you just want to use your subscription
Auth Profiles
Profile IDs
Each stored credential gets a profile ID in the formatprovider:identifier:
Profile Rotation
When multiple profiles exist for a provider, Polpo selects the best one using this algorithm:- OAuth profiles before API key profiles — OAuth gets priority because it supports auto-refresh
- Oldest
lastUsedfirst — round-robin to distribute usage evenly across profiles - Skip profiles in cooldown — transient errors (rate limit, auth failure) trigger temporary cooldown
- Skip billing-disabled profiles — insufficient credits trigger a longer disable period
Token Refresh
OAuth tokens expire. Polpo handles this automatically:- Before each API call, Polpo checks if the token is expired
- If expired and a refresh token exists, it refreshes automatically
- If expired and no refresh token — the profile is skipped with a warning, and Polpo tries the next profile or falls back to other auth methods
- Refreshed tokens are saved back to
auth-profiles.json
Billing Disable
Billing errors (insufficient credits, quota exceeded) are handled separately from transient errors like rate limits. This is because billing issues are persistent — retrying in 1 minute won’t help if your credits are depleted.How It Works
When a billing error is detected:- The profile is marked as billing-disabled with a longer backoff
- The initial disable period is 5 hours
- On subsequent billing failures, the period doubles: 5h -> 10h -> 20h -> 24h cap
- The billing error counter resets after 24 hours without a billing failure
vs. Regular Cooldown
| Regular Cooldown | Billing Disable | |
|---|---|---|
| Triggers | Rate limit, auth error, server error | Insufficient credits, quota exceeded |
| Backoff | 1min -> 5min -> 25min -> 1h | 5h -> 10h -> 20h -> 24h |
| Assumption | Transient — will resolve quickly | Persistent — needs payment or plan upgrade |
| Profile impact | Deprioritized in rotation | Moved to end of rotation queue |
Configuration
Billing disable timing can be configured inpolpo.json:
Session Stickiness
When a model is selected for a session (chat, task execution), Polpo can pin the auth profile to that session. This avoids switching models or credentials mid-conversation.Auto-Pin (Soft)
By default, Polpo auto-pins the first profile it uses in a session. If that profile enters cooldown or billing disable, Polpo rotates to another available profile transparently.User-Pin (Hard)
When you explicitly select a model and profile via/model, it’s a hard pin. If the pinned profile fails, Polpo does NOT rotate to another profile — it fails over to the next model in the fallback chain instead. This ensures you stay on the exact credential you chose.
- You run
/model resetor/model default - The session is reset
The /model Command
Switch models and profiles during a chat session:
| Command | Action |
|---|---|
/model or /model list | Show available models as a numbered list |
/model 3 | Select model #3 from the list |
/model anthropic/claude-opus-4-6 | Switch to a specific provider and model |
/model anthropic:claude-opus-4-6 | Same thing, with colon separator |
/model claude-opus-4-6 | Auto-infer provider from model name |
/model status | Show current model, fallbacks, and auth status |
/model reset or /model default | Clear override, return to default model |
/model claude-opus-4-6@anthropic:user@example.com | Switch model AND pin a specific auth profile |
Allowlist Enforcement
If amodelAllowlist is configured in polpo.json, /model only allows selecting models in the list:
polpo models CLI
polpo models list
List all available models from the pi-ai catalog:
polpo models status
Show resolved model configuration, fallbacks, and auth status:
--json— full JSON output (for CI/automation)--check— exit code 1 if auth missing, 2 if expiring soon (for CI health checks)--plain— print only the resolved primary model spec-d <path>— specify working directory
polpo models scan
Scan for locally running model servers:
--json— JSON output--timeout <ms>— connection timeout per endpoint (default: 3000ms)
Security
File Permissions
~/.polpo/directory: 0700 (owner only)auth-profiles.json: 0600 (owner read/write only)- Permissions are enforced on every read/write; if they’re wrong, Polpo fixes them automatically
Atomic Writes
Credential files are written atomically: data goes to a temp file first, then renamed in place. This prevents corruption from crashes or power loss.Input Validation
- Profile IDs are validated against a strict regex (alphanumeric, hyphens, underscores, dots,
@) - Prototype pollution prevention:
__proto__,constructor, and similar keys are rejected - Provider names must be lowercase alphanumeric with hyphens/underscores (max 64 chars)
- Tokens are validated for length (max 16KB) and control characters before storage
Path Safety
IfPOLPO_STATE_DIR is set, it must be:
- An absolute path
- Not pointing to sensitive system directories (
/etc,/usr,/proc, etc.) - Resolved path must match input (no
..traversal tricks)
~/.polpo/.