# Authentication and providers

> Login and API-key paths, multi-provider selection, OAuth examples, auth error recovery, and BYOK provider boundaries.

- Repository: PrimeIntellect-ai/prime-agent
- GitHub: https://github.com/PrimeIntellect-ai/prime-agent
- Human docs: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1
- Complete Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/llms-full.txt

## Source Files

- `packages/coding-agent/examples/sdk/09-api-keys-and-oauth.ts`
- `packages/coding-agent/test/suite/regressions/4435-auth-error-login-guidance.test.ts`
- `packages/coding-agent/test/suite/regressions/4573-prime-inference-login-default.test.ts`
- `packages/coding-agent/test/suite/regressions/4575-model-auth-selection.test.ts`
- `packages/ai/src/providers/amazon-bedrock.ts`
- `packages/coding-agent/test/agent-session-dynamic-provider.test.ts`

---

---
title: "Authentication and providers"
description: "Login and API-key paths, multi-provider selection, OAuth examples, auth error recovery, and BYOK provider boundaries."
---

Prime Agent resolves model credentials through `AuthStorage` and `ModelRegistry`. Interactive sessions use `/login` and `/logout`; the CLI accepts `--api-key` as a non-persisted runtime override; the SDK constructs the same stack with `AuthStorage.create()` and optional custom paths. Credentials live under `~/.prime/agent/auth.json` by default (override with `PRIME_AGENT_CODING_AGENT_DIR`), file mode `0600`. Provider catalog and keys stay BYOK/BYOC: each provider supplies its own key, OAuth token, or ambient cloud credentials; nothing requires a single hosted model vendor.

## Credential surfaces

| Surface | Path / flag | Persisted | Notes |
|---------|-------------|-----------|--------|
| Auth file | `~/.prime/agent/auth.json` | Yes | API keys (`type: "api_key"`) and OAuth tokens (`type: "oauth"`) |
| Models file | `~/.prime/agent/models.json` | Yes | Custom providers; optional `apiKey` fallback |
| Prime CLI config | `~/.prime/config.json` | Yes | Prime Inference key, team selection, base URLs |
| Environment | Provider-specific vars | No | See table below |
| Runtime | `--api-key` / `setRuntimeApiKey` | No | Highest priority; process-scoped |
| Ambient cloud | AWS / GCP ADC | No | Bedrock, Vertex; not plain API-key env vars |

:::files
~/.prime/
├── config.json          # Prime CLI / Inference (api_key, team_id, urls)
└── agent/
    ├── auth.json        # Provider API keys + OAuth credentials
    ├── models.json      # Custom / local providers (optional keys)
    └── settings.json    # Default provider/model after login
:::

## Credential resolution order

`AuthStorage.getApiKey()` walks sources in order and skips sources marked **stale** after repeated auth failures.

### Most providers

1. Runtime override (`--api-key` / `setRuntimeApiKey`)
2. Stored credential in `auth.json` (API key or OAuth, with locked refresh when expired)
3. Environment variable(s) from `env-api-keys.ts`
4. Fallback resolver (custom provider keys from `models.json`)

### Prime Inference (`prime-inference`)

1. Runtime override
2. `PRIME_API_KEY` environment variable
3. Prime CLI config (`~/.prime/config.json` `api_key`)
4. Stored `auth.json` entry
5. Fallback resolver

<Note>
For non-Prime providers, a stored `auth.json` key wins over the matching environment variable. For Prime Inference, env and Prime CLI config win over `auth.json`.
</Note>

### `auth.json` key field formats

| Form | Example | Behavior |
|------|---------|----------|
| Literal | `"sk-ant-..."` | Used as-is |
| Env name | `"MY_ANTHROPIC_KEY"` | Reads `process.env[name]`, else treats string as literal |
| Shell command | `"!op read 'op://...'"` | Executes command, uses stdout; cached for process lifetime |

## Interactive login and logout

### `/login`

Runs `ProviderAuthFlows.runLogin()`:

1. Provider selector lists OAuth subscriptions, API-key model providers, and service credentials (Serper, MCP `mcp:<server>`).
2. Selection routes to the matching flow:
   - **OAuth** → browser / device flow via registered OAuth provider
   - **`prime-inference`** → browser challenge + optional pasted key + team picker
   - **`amazon-bedrock`** → setup guidance only (external AWS credentials)
   - **Other API-key providers** → paste key into login dialog
3. On success: credentials saved, model registry refreshed, host `onAuthChanged` / `onLoginCompleted` run.

After a successful **Prime Inference** login, the session refreshes available models, selects the default `z-ai/glm-5.2`, and persists provider/model so the next process starts with a valid model.

### `/logout`

Removes credentials **saved by `/login`** only. Environment variables and `models.json` keys are unchanged. If nothing is stored, status explains that limitation.

### Auth status labels

`getAuthStatus(provider)` returns non-secret metadata:

| `source` | Meaning |
|----------|---------|
| `runtime` | `--api-key` / SDK runtime override |
| `stored` | Entry in `auth.json` |
| `environment` | Known env var (or ambient cloud marker) |
| `prime_cli` | `~/.prime/config.json` for Prime Inference |
| `fallback` | Custom provider config (`models.json`) |
| `stale` | Prior source invalidated after auth failure (`label: "expired"`) |

## OAuth (subscription) providers

Built-in OAuth providers (registry in `@earendil-works/pi-ai` OAuth module):

| Provider ID | Display name | Notes |
|-------------|--------------|--------|
| `openai-codex` | ChatGPT Plus/Pro (Codex Subscription) | ChatGPT subscription OAuth |
| `anthropic` | Anthropic (Claude Pro/Max) | Subscription tokens; third-party harness usage is billed as extra usage per token |
| `github-copilot` | GitHub Copilot | github.com or Enterprise Server domain |

Tokens land in `auth.json` as `type: "oauth"`. Expired tokens refresh under a file lock so concurrent Prime Agent processes do not race. Failed refresh returns no key for that provider until `/login` succeeds; credentials remain on disk for retry.

Extensions and MCP can call `registerOAuthProvider()` (MCP IDs use `mcp:<server>`). Those appear as **service** credentials in the login selector and do not drive model selection.

### Anthropic subscription warning

When the active model is `anthropic` and credentials are OAuth or an `sk-ant-oat…` key, the UI can surface:

> Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits.

## API-key providers

Use `/login` → API key provider, or set environment variables before launch:

```bash
export ANTHROPIC_API_KEY=sk-ant-...
prime-agent
```

| Provider ID | Environment variable(s) |
|-------------|-------------------------|
| `anthropic` | `ANTHROPIC_OAUTH_TOKEN`, then `ANTHROPIC_API_KEY` |
| `openai` | `OPENAI_API_KEY` |
| `azure-openai-responses` | `AZURE_OPENAI_API_KEY` |
| `prime-inference` | `PRIME_API_KEY` |
| `deepseek` | `DEEPSEEK_API_KEY` |
| `google` | `GEMINI_API_KEY` |
| `google-vertex` | `GOOGLE_CLOUD_API_KEY` or ADC + project + location |
| `mistral` | `MISTRAL_API_KEY` |
| `groq` | `GROQ_API_KEY` |
| `cerebras` | `CEREBRAS_API_KEY` |
| `xai` | `XAI_API_KEY` |
| `openrouter` | `OPENROUTER_API_KEY` |
| `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` |
| `zai` | `ZAI_API_KEY` |
| `opencode` / `opencode-go` | `OPENCODE_API_KEY` |
| `huggingface` | `HF_TOKEN` |
| `fireworks` | `FIREWORKS_API_KEY` |
| `kimi-coding` | `KIMI_API_KEY` |
| `minimax` / `minimax-cn` | `MINIMAX_API_KEY` / `MINIMAX_CN_API_KEY` |
| `moonshotai` / `moonshotai-cn` | `MOONSHOT_API_KEY` |
| `xiaomi` | `XIAOMI_API_KEY` |
| `xiaomi-token-plan-cn` / `-ams` / `-sgp` | matching `XIAOMI_TOKEN_PLAN_*` |
| `cloudflare-ai-gateway` / `cloudflare-workers-ai` | `CLOUDFLARE_API_KEY` (+ account/gateway env) |
| `github-copilot` | `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `GITHUB_TOKEN` |

Example stored shape:

```json
{
  "anthropic": { "type": "api_key", "key": "sk-ant-..." },
  "openai": { "type": "api_key", "key": "sk-..." },
  "prime-inference": { "type": "api_key", "key": "..." }
}
```

## Prime Inference login path

Provider ID: `prime-inference`. Default inference URL: `https://api.pinference.ai/api/v1`.

<Steps>
  <Step title="Reuse or browser login">
    `loginPrimeInference` first checks an existing Prime CLI key for inference write scope via `/api/v1/user/whoami`. If missing or insufficient, it runs an RSA challenge: open `frontendUrl/dashboard/tokens/challenge?code=…`, poll encrypted result, decrypt API key.
  </Step>
  <Step title="Manual paste fallback">
    If browser login fails, the dialog accepts a pasted API key and re-checks inference access.
  </Step>
  <Step title="Persist and team">
    Key is written via Prime CLI config when enabled (default for normal CLI), team selection optional (`PRIME_TEAM_ID` freezes team from env). Team header: `X-Prime-Team-ID` when a team is active.
  </Step>
  <Step title="Model default">
    Post-login selects and persists `prime-inference` / `z-ai/glm-5.2` when that model is available after refresh.
  </Step>
</Steps>

Related service credential: `prime-agent-traces` for trace upload scope (`agent_traces` write permission).

## Cloud and ambient providers

### Amazon Bedrock (`amazon-bedrock`)

Not a single API-key login. `/login` shows setup info; detection uses ambient AWS credentials:

- `AWS_PROFILE`
- `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY`
- `AWS_BEARER_TOKEN_BEDROCK`
- ECS task roles (`AWS_CONTAINER_CREDENTIALS_*`)
- IRSA (`AWS_WEB_IDENTITY_TOKEN_FILE`)

```bash
export AWS_REGION=us-west-2   # default us-east-1 if unset
prime-agent --provider amazon-bedrock --model us.anthropic.claude-sonnet-4-20250514-v1:0
```

Proxy / edge knobs: `AWS_ENDPOINT_URL_BEDROCK_RUNTIME`, `AWS_BEDROCK_SKIP_AUTH`, `AWS_BEDROCK_FORCE_HTTP1`, `AWS_BEDROCK_FORCE_CACHE`.

### Google Vertex AI (`google-vertex`)

ADC via `gcloud auth application-default login` or `GOOGLE_APPLICATION_CREDENTIALS`, plus `GOOGLE_CLOUD_PROJECT` (or `GCLOUD_PROJECT`) and `GOOGLE_CLOUD_LOCATION`. Successful ambient setup surfaces as configured with a non-secret `<authenticated>` marker.

### Azure OpenAI Responses

`AZURE_OPENAI_API_KEY` plus base URL / resource name env vars (see in-repo providers docs for deployment maps).

### Cloudflare AI Gateway (BYOK modes)

| Mode | Request auth | Upstream auth |
|------|--------------|---------------|
| Workers AI | Cloudflare token only | Cloudflare-native |
| Unified billing | Cloudflare token only | Cloudflare deducts credits |
| Stored BYOK | Cloudflare token only | Keys stored in AI Gateway dashboard |
| Inline BYOK | Cloudflare token + upstream `Authorization` | Request supplies provider key (e.g. via `models.json` header override) |

Prefer unified billing or stored BYOK for normal CLI use. Inline BYOK is an explicit multi-credential boundary: Cloudflare token plus upstream provider key.

## Custom and local providers (BYOC)

**`models.json`** — register Ollama, vLLM, LM Studio, or any supported API (`openai-completions`, OpenAI Responses, Anthropic Messages, Google Generative AI):

```json
{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai-completions",
      "apiKey": "ollama",
      "models": [{ "id": "llama3.1:8b" }]
    }
  }
}
```

`apiKey` is required by schema; local servers often ignore it. Keys resolve through the AuthStorage fallback path (`source: "fallback"`).

**Extensions** — `pi.registerProvider(...)` overrides `baseUrl` / headers / models, or registers a new provider. Overrides apply at factory load, `session_start`, or command time without requiring process restart. Dynamic registration is provider-neutral: the extension is a portable skill/source file, not a hard dependency on one vendor.

## Multi-provider selection

- **`/model`** lists available models. Authenticated providers sort above unauthenticated ones; unauthenticated public rows can show `sign in` while remaining selectable.
- After auth changes, connection model catalog refreshes (`refreshConnectionModelsAfterAuthChange`) so configured-provider sets match live credentials.
- Scoped sessions may expose both scoped-only models and the full public catalog in autocomplete.
- CLI: `prime-agent --provider <id> --model <id>`; optional `--api-key <key>` requires an explicit model (or `--models`) and is bound to that model’s provider only.

## CLI and SDK wiring

### CLI

```bash
# Env BYOK
export OPENAI_API_KEY=sk-...
prime-agent --provider openai --model gpt-5.4

# Runtime key (not written to disk)
prime-agent --provider anthropic --model claude-opus-4-7 --api-key sk-ant-...
```

`--api-key` without a resolvable model is an error: *`--api-key requires a model to be specified via --model, --provider/--model, or --models`*.

### SDK

```typescript
import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";

const authStorage = AuthStorage.create(); // ~/.prime/agent/auth.json
const modelRegistry = ModelRegistry.create(authStorage);

// Optional process-scoped override
authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");

// Custom paths (app-owned storage)
const customAuth = AuthStorage.create("/tmp/my-app/auth.json");
const customRegistry = ModelRegistry.create(customAuth, "/tmp/my-app/models.json");

// Built-in models only (no models.json)
const simpleRegistry = ModelRegistry.inMemory(authStorage);

await createAgentSession({
  sessionManager: SessionManager.inMemory(),
  authStorage,
  modelRegistry,
});
```

## Auth errors and recovery

### Detection

`isLikelyAuthenticationError` treats messages as auth failures when they match `401`/`403`, unauthorized/forbidden, invalid API key, expired/invalid token, access denied, and related phrases.

### User guidance

| Condition | Message fragment |
|-----------|------------------|
| Auth failure | `Run /login to update credentials.` (`LOGIN_RECOVERY_MESSAGE`) |
| No models | Points at `/login` and docs `providers.md` / `models.md` |
| No API key for provider | `No API key found for <provider>.` + login help |
| No model selected | Login help + `/model` |

Guidance is appended once; messages that already mention `/login` are left unchanged.

### Stale credentials after repeated 401

On structured provider auth failures (`diagnostics.kind: "auth"`, status 401) or bare `401` status errors:

1. Auto-retry may attempt once (when retry is enabled).
2. Current auth source token is marked stale (`markAuthSourceStale`).
3. Session emits `auth_stale` (includes `sourceTokens` for daemon clients).
4. `hasAuth(provider)` becomes false; `getAuthStatus` reports `{ configured: false, source: "stale", label: "expired" }`.
5. Final assistant error includes `/login` recovery text.

Setting a new key (`set`, `setRuntimeApiKey`, Prime CLI save, etc.) clears the matching stale source so the new credential is usable.

```text
request → 401 auth failure → optional single auto-retry
         → mark current source token stale
         → emit auth_stale
         → surface "Run /login to update credentials."
```

Non-auth errors (for example 500) do not mark auth stale.

## Failure modes

| Symptom | Likely cause | Recovery |
|---------|--------------|----------|
| No models available | No resolved credentials for any provider | `/login` or set env keys |
| Authentication failed for `"provider"` | Expired OAuth / invalid key / network | `/login`; check network |
| Provider stuck after 401 | Source marked stale | Update credentials; do not reuse the same failed value |
| Bedrock “not detected” | No AWS ambient credentials | Configure profile, IAM keys, bearer token, or role env |
| Prime Inference access denied | Token lacks `inference` write scope | Browser login with inference-capable token |
| Logout did nothing | Only env / `models.json` credentials present | Unset env or edit `models.json` |
| `--api-key` rejected | No model specified | Pass `--model` / `--provider` |

## Boundaries (BYOK / BYOC)

- **Provider-neutral core**: `AuthStorage` + `ModelRegistry` do not assume a single paid gateway; each provider ID maps to its own credential channel.
- **Logout boundary**: stored login credentials only; ambient and file-based custom keys remain.
- **Prime Inference vs other providers**: different resolution order and optional team header; still an API-key-shaped provider ID in the catalog.
- **Service vs model credentials**: Serper and MCP OAuth do not change the selected model after login (`kind: "service"`).
- **Custom providers**: local/self-hosted endpoints via `models.json` or extensions keep keys and base URLs in user-owned config.

## Related pages

<CardGroup cols={2}>
  <Card title="Settings and provider keys" href="/settings-providers">
    Provider registration, model selection, API key and OAuth wiring, dynamic updates, and 401 stale-provider recovery.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    First interactive session, onboarding, and health checks after credentials are configured.
  </Card>
  <Card title="Session configuration" href="/session-configuration">
    Session config keys and defaults that interact with provider and model choice.
  </Card>
  <Card title="Minimal SDK agent" href="/sdk-minimal">
    Bootstrap `createAgentSession` with auth and model wiring.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, provider 401s, and related runtime recovery probes.
  </Card>
  <Card title="Extensions and custom tools" href="/extensions">
    `registerProvider` and custom OAuth/API implementations for private endpoints.
  </Card>
</CardGroup>
