# model authentication model authentication is supplied by model-provider extensions rather than the daemon configuration loader. ## credentials Every secret albedo keeps lives in `$ALBEDO_HOME/creds.json`, mode `0600`, and only the daemon reads or writes it. `accounts` holds OAuth and key accounts by store key, as one object or an array. Key accounts contain `type: "key"`, `apiKey`, `endpoint`, `label`, and `_albedo_account_id`; generic compatible keys use `accounts.openai`, OpenCode Go uses `accounts.opencode-go`, Perplexity uses `accounts.perplexity`, and Mistral uses `accounts.mistral`. `providers` holds legacy profile keys for other extensions; `mcp` holds each MCP server's `bearerToken`, `headers`, `env`, a configured OAuth client (`oauthClient`) and its OAuth sign-in (`oauth`, see [mcp](mcp.md#sign-in-with-oauth)). All credential and settings mutations share one per-home daemon lock. OAuth refreshes re-read under that lock and preserve unrelated sections. A hand-written config `apiKey` wins at runtime: the next start moves it into a key account and pins the profile for `openai`, `opencode-go` and `mistral`, and leaves it in the profile for a provider that reads its own (`bedrock`, `vertex`, `perplexity`). Account limit marks under `accounts` are recorded per model-family meter for Google Antigravity (`limitedMeters: {"claude": , ...}` with meter keys `gemini`, `claude`, `gpt-oss`, falling back conservatively to legacy `limitedUntil` when `limitedMeters` is absent). Codex and saved API keys use account-wide `limitedUntil`. Environment keys keep transient marks for the daemon's lifetime. At start the daemon moves secrets still kept elsewhere into `creds.json`: `auth.json`'s accounts, `mcp-credentials.json`'s servers, and the `apiKey` of a `config.json` profile whose provider keeps its keys as accounts (`openai`, Alibaba profiles included as they convert to it, `opencode-go`, and `mistral`). A provider that reads its own profile key (`bedrock`, `vertex`, `perplexity`) keeps it there, so a `config.json` may still hold one after a start. It writes `creds.json` first, then moves the old files into `backups/` as `-before-creds-` with every mode bit cleared, so an interrupted start loses nothing and the next one finishes. Not even their owner can read the backups without `chmod 600` first, so tools and agents running as the user never read them by accident; `rm -f` deletes them. Clients save provider profiles and MCP configuration through the [settings API](settings.md). Reads disclose credential presence, never secret values. The model gets the same view from `commands.accounts()` (the `/accounts` command, see [commands](commands.md)), which adds each account's Google project and per-meter limits; the kernel's `files.read` and `files.find` refuse `creds.json` itself (see [files](files.md)). `PUT /auth/keys/` saves key accounts; profile patches select them through `account_id` or `account_ids`. The write-only profile `api_key` remains a compatibility path: for a provider whose keys are accounts (`openai`, `opencode-go`, `perplexity`, `mistral`), it saves or reuses a key account and pins the profile. MCP secrets belong to `PATCH /settings?group=mcp`. Public and private settings changes share one journalled commit. `GET /server` includes credential migration notices for files moved during this daemon's start. Reading a notice does not consume it. Clients dismiss notices through `ui.dismissed_notices`. Original files remain sealed in `backups/`. ## openai-compatible api keys The enabled-by-default `openai` extension requires `models`. It handles existing named OpenAI-compatible profiles and both Responses and Chat Completions. The Responses stream decoder accepts servers that leave `output_index` off their events (llama.cpp's llama-server) by placing each event with its `item_id`, and treats a missing `content_index` as 0. Model discovery asks the profile's endpoint for `/models` and falls back to the models.dev cache (see [provider extensions](models.md#provider-extensions)). Older `config.json` profiles without an `extension` field remain OpenAI profiles; newly saved profiles include `"extension": "openai"`. Key accounts carry their own endpoint, including when an explicit rotation spans endpoints. Compatible API 429s containing `insufficient_quota`, `exceeded your current quota`, `exhausted`, `Arrearage`, `Overdue`, or `billing` record a 15-minute limit; other 429s record 30 seconds. Rotation uses only chosen members. When no other member is usable, a request waits out a brief limit on the same key, retrying after 4, 8, 15 and 30 seconds; [cache-warming](cache-warming.md) pings do not wait. A lasting limit on one plain key fails at once without trying another saved key. Environment accounts have ids such as `env:OPENROUTER_API_KEY`, label `$OPENROUTER_API_KEY`, `kind: "key"`, and `source: "env"`. The cached models.dev provider whose API host matches the endpoint supplies environment variable names. Saved profile references to `env:` also advertise that variable at the same store and endpoint, so migrated references remain usable. Unset variables are skipped within rotations; an unset pin fails with the variable's name. No environment value is written to `creds.json`. ## chatgpt codex oauth The enabled-by-default `codex` extension requires `openai`, so it reuses the OpenAI Responses encoder, SSE transport, replay format, and model metadata. Its picker shows the models the ChatGPT backend offers the signed-in account (see [codex models](models.md#codex-models)); API-key OpenAI profiles still show the full OpenAI catalog. `/login` can add ChatGPT Plus/Pro accounts. A fresh install can enter `codex` as the provider name; an existing install picks the ChatGPT row under **new profile**. The daemon runs the login (see [sign-in api](#sign-in-api)) with the Codex CLI browser flow: PKCE S256, the allowlisted `http://localhost:1455/auth/callback`, state validation, and a manual callback-url/code input that races the local callback. The token exchange uses OpenAI's public Codex client id and stores access, rotating refresh, expiry, ChatGPT workspace id, seat id when present, and email when present. Credentials live in `creds.json`'s accounts under the `openai-codex` key. One account uses the legacy object shape; two or more use an array. A login replaces the same seat/account identity and appends a different account. `/login` lists saved providers and each stored account; `ctrl+d` on either row asks to remove it. Removing the active provider makes the first remaining provider by name active. A Codex provider with no accounts left reads `signed out` and starts a sign-in when chosen. Access tokens refresh under the credential lock 60 seconds before expiry, with a re-read under the lock so concurrent sessions do not overwrite a newer refresh. A profile pins one account with `account_id`, rotates only through an explicit `account_ids` list, or remains plain. A plain sign-in profile uses its selected stored account, else its first stored account; limits and authentication failures never select a sibling. Explicit rotations keep session-sticky ordering and skip limited members. A plain Codex profile retains a rejected account and reports `run /login`; a rotating profile removes the rejected account and drops its id from saved rotations in the same commit. Requests use `https://chatgpt.com/backend-api/codex/responses` with Bearer auth, `chatgpt-account-id`, Codex experimental Responses headers, and the session request id. Codex request bodies force `store: false`, automatic parallel tools, low text verbosity, encrypted reasoning replay, and nullable tool strictness. The oauth callback binds only to `127.0.0.1`. Port 1455 is fixed because OpenAI allowlists the exact redirect URI; a busy port fails instead of silently changing redirects. ## profiles in /login `/login` lists profiles, accounts (signed-in accounts plus saved and env API keys) and new-profile presets; Alibaba, OpenRouter, and Ollama Cloud are presets of `openai`, with no dedicated extension. A profile either pins one account or rotates an explicitly ticked set; nothing joins a rotation silently. Choosing a credential for a profile opens a chooser: enter pins the highlighted account, space ticks accounts, enter with two or more ticked saves a rotation (`account_ids`), and enter on "another key/account" adds one and, for a rotating profile, asks "add