# Authentication

This page covers where provider credentials come from, where they are stored, and how to import them from other CLIs you are already logged in to.

OpenProgram requires safe, lawful, and compliant use of Anthropic services. Its Anthropic integration uses the official API with legitimately obtained API keys and API-account billing. All calls must comply with Anthropic's terms of service and usage policy.

## Storage location

All credentials live in one credential store: `~/.openprogram/auth/<provider>/<account>.json` (permissions 0600; with `--profile <name>` the workspace root directory becomes `~/.openprogram-<name>/`).

At runtime, keys are **read from the credential store only — environment variables are not read directly**. A key in an environment variable (such as `OPENAI_API_KEY`) must be imported first (see discover below); changing the environment variable afterwards does not affect the imported credential. The two exceptions are cloud credential chains: Amazon Bedrock (`AWS_PROFILE` / access keys / bearer token, etc.) and Google Vertex (ADC), both detected automatically at runtime.

## Credential sources

### API key login

```bash
openprogram providers login deepseek                       # interactive input
printf %s "$KEY" | openprogram providers login deepseek --api-key-stdin   # scripts
```

`--api-key` also accepts the value directly, but it ends up in shell history; prefer `--api-key-stdin` in scripts.

For Anthropic, use `openprogram providers login anthropic` and enter your Anthropic API key.

### OAuth login

Subscription providers log in via browser or device code; `login` picks the method automatically (`--method` forces one):

- `openai-codex`: ChatGPT subscription PKCE login in the browser; an existing `codex` CLI login can instead be imported via `discover` / `adopt` below
- `gemini-subscription`: imports `~/.gemini/oauth_creds.json` — log in with the Gemini CLI first
- `github-copilot`: GitHub device-code login in the browser (or import a `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` env var); the short-lived Copilot token is exchanged on demand and never written to disk

### Importing credentials already on this machine

```bash
openprogram providers discover        # scan and list only, writes nothing
openprogram providers adopt codex_cli # import one entry; --all imports everything
```

Scanned sources:

| Source | Location | Imported into |
|---|---|---|
| Codex CLI | `~/.codex/auth.json` | `openai-codex` |
| Qwen CLI | `~/.qwen/oauth_creds.json` | `qwen` |
| gh CLI | `~/.config/gh/hosts.yml` | `github` |
| Environment variables | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc. in the process environment | The matching provider |

Imports take one of two forms: while the external CLI is still on the machine, a pointer is stored (the external file is re-read on every call, so tokens the external CLI refreshes itself take effect automatically); otherwise the token is copied into the credential store and OpenProgram handles refreshing.

The Gemini CLI login state does not go through discover: the `google-gemini-cli` provider reads `~/.gemini/oauth_creds.json` directly — install the Gemini CLI, log in, and it works.

### Helper command (external process)

Some corporate setups issue tokens through a vendor or in-house CLI (`aws`, `sso-helper`, `token-fetcher`) rather than an API key you can paste. Add such a provider in Settings → Providers with type `credential_process`: the command's argv, how to read its stdout, and a cache window.

| Field | Meaning | Default |
|---|---|---|
| `command` | Helper argv, as a list. Run directly — no shell, so there is nothing to escape | required |
| `parses` | `json` to parse stdout as JSON, `text` to take the stripped stdout as the token | `json` |
| `json_key_path` | Key path to walk into the JSON, e.g. `["creds", "token"]`. Empty means the document itself must be a string | `[]` |
| `cache_seconds` | How long one token is reused before the helper runs again. `0` runs the helper on every call | `300` |
| `timeout_seconds` | Wall-clock limit for one helper run | `60` |

The helper runs on every API call whose token is not within the cache window, and the login smoke test runs it once so a broken command is reported while you are still configuring it.

A helper that fails — non-zero exit, timeout, unparseable output, or a missing key path — makes the request fail with an error naming the command and its stderr. It does not fall back to another credential: you configured this helper deliberately, so a token quietly sourced from somewhere else would hide the breakage. Fix the helper, or remove the credential to use a different source.

Enterprise SSO (`sso`) is not implemented. The credential kind is reserved, and both the API and the resolver reject it explicitly rather than accepting configuration that could never take effect.

## Management and troubleshooting

```bash
openprogram providers status <provider>    # are the current credentials usable
openprogram providers doctor               # expiry, refresh failures, cooldown, conflicts
openprogram providers logout <provider>    # delete credentials
openprogram providers use <provider> [account]   # switch between multiple accounts
openprogram providers list                 # list credential pools by account
```

Every provider supports multiple named accounts, and one account's credential pool can hold multiple API keys. A key that returns 401 / 402 / 429 / 503 is put on a cooldown and the pool hands the next healthy key to the following request — automatically, with selectable strategies (`fill_first` is the default "backup key" behavior; `round_robin`, `random`, and `least_used` are also available). Rotating across whole accounts (instead of the single active one) is a separate per-provider switch, off by default.

### Codex browser sign-in

Codex model calls inside a Program use the provider's active account, including an account selected with `openprogram providers use openai-codex <account>`. A script can pin a different account with `create_runtime(provider="openai-codex", profile="<account>")`. Terminal authentication failures and other provider errors explicitly marked non-retryable stop the internal call after one request; their error category is preserved through the Program runtime. Ordinary callback failures without an explicit retry verdict retain the Program's bounded recovery.

The local callback listener starts before the authorization page opens. Keep the provider panel open until it reports success. If the page does not open, use **Open sign-in page**. If the callback cannot reach the app, paste the callback URL into the login panel; it must belong to the current sign-in attempt.

A token-exchange HTTP 403 means OpenAI refused the exchange, but does not by itself identify whether account access or the network caused it. The panel distinguishes recognized authorization errors and shows connection retries. Start a fresh sign-in after an expired or already-used authorization code. Cancelling or leaving the panel ends that attempt.
