Apus supports OAuth ("subscription auth") for providers that offer it, notably OpenAI Codex (ChatGPT OAuth) and Anthropic Claude CLI reuse. For Anthropic, the practical split is:

Apus stores both OpenAI API-key auth and ChatGPT/Codex OAuth under the canonical provider id openai. Older openai-codex:* profile ids and auth.order.openai-codex entries are legacy state repaired by apus doctor --fix; use openai:* profile ids and auth.order.openai for new config.

This page covers:

Provider plugins that ship their own OAuth or API-key flow run through the same entry point:

apus models auth login --provider <id>

In the Control UI, open Settings → Models → Connect provider to save a supported account without requesting a model reply. Connect provider in Model Setup opens the same picker and connection flow. Choose Test & use when you want to verify a model and select it. Plugins that only support full setup keep their separate setup action.

Apus's browser callback pages follow your system's light or dark appearance. If a page says Authorization received, return to the terminal while Apus finishes the exchange and saves the account. The terminal or Control UI reports when sign-in is complete.

The token sink (why it exists)

OAuth providers commonly mint a new refresh token on every login/refresh. Some providers invalidate the previous refresh token when a new one is issued for the same user/app. Practical symptom: log in via Apus and via Claude Code / Codex CLI, and one of them randomly gets logged out later.

To reduce that, Apus treats the auth profile store as a token sink:

Storage (where tokens live)

Credentials use a shared read-through base, while each agent owns its local credential overrides and auth-routing state:

Personal accounts added from Profile or models accounts login use private identity-scoped records in the selected Gateway's shared state database: model-accounts owns the selected links, and each credential has its own model-account:<profile-id> record containing its secret and usage state. Only a selected personal profile is loaded for a run; ordinary shared-account reads never enumerate these records. Personal OAuth refresh writes back to that person's account record rather than a shared or agent-local credential.

Older installations may still contain auth-profiles.json, auth-state.json, or per-agent auth.json. Run apus doctor --fix once after upgrading. Doctor imports verified values, records a migration receipt, and renames the original file to a timestamped archive.

The older shared credentials/oauth.json importer has retired. Doctor leaves that file untouched and reports the upgrade through 2026.9.5 needed to import it before installing the latest release.

Runtime never uses credentials from these retired files. What happens when one of the supported import files is still present depends on whether SQLite can already serve credentials for that agent:

The database and migration sources respect $APUS_STATE_DIR. Full reference: /gateway/config-secrets-env#auth-storage

For static secret refs and runtime snapshot activation behavior, see Secrets Management.

When an agent has no local auth profile, Apus reads the shared auth store; it does not clone shared credentials into the agent database. OAuth refresh tokens are especially sensitive: normal copy flows skip them by default because some providers rotate or invalidate refresh tokens after use. Configure a separate OAuth login for an agent when it needs an independent account.

Anthropic Claude CLI reuse

Apus supports Anthropic Claude CLI reuse and claude -p as a sanctioned auth path. If you already have a local Claude login on the host, onboarding/configure can reuse it directly. Anthropic setup-token remains available as a supported token-auth path, but Apus prefers Claude CLI reuse when it is available.

Anthropic's public Claude Code docs say direct Claude Code use stays within Claude subscription limits, and Anthropic staff told us Apus-style Claude CLI usage is allowed again. Apus therefore treats Claude CLI reuse and claude -p usage as sanctioned for this integration unless Anthropic publishes a new policy.

For Anthropic's current direct-Claude-Code plan docs, see Using Claude Code with your Pro or Max plan and Using Claude Code with your Team or Enterprise plan.

If you want other subscription-style options in Apus, see OpenAI Codex, Qwen Cloud Coding Plan, MiniMax Coding Plan, and Z.AI / GLM Coding Plan.

OAuth exchange (how login works)

Apus's OAuth registry and adapters live in src/ai/llm/utils/oauth/. Shared provider helpers live in src/plugin-sdk/provider-oauth-runtime.ts and src/plugin-sdk/provider-auth-runtime.ts. The auth commands in src/surfaces/commands/models/auth.ts run the selected provider method and persist the returned profiles.

Restarting sign-in in Model Setup

In Model Setup, starting the same sign-in again replaces your unfinished attempt for the same agent and workspace. This includes Sign in with ChatGPT and other provider sign-in flows. Use the newest browser link; callbacks from the previous attempt are no longer accepted.

Another user's sign-in, a different setup flow, or an attempt already saving credentials or configuration stays protected. Let that operation finish before retrying.

Anthropic setup-token

Flow shape:

  1. create the token by running claude setup-token on any machine with Claude Code, then start Anthropic setup-token or paste-token from Apus
  2. Apus stores the resulting Anthropic credential in an auth profile
  3. model selection stays on anthropic/...
  4. existing Anthropic auth profiles remain available for rollback/order control

OpenAI Codex (ChatGPT OAuth)

OpenAI Codex OAuth is explicitly supported for use outside the Codex CLI, including Apus workflows.

The login command uses the canonical OpenAI provider id:

apus models auth login --provider openai

Use --profile-id openai:<name> for multiple ChatGPT/Codex OAuth accounts in one agent. Do not use openai-codex:<name> for new profiles. Doctor migrates that older prefix to a collision-free openai:* profile id; run apus models auth list --provider openai after repair before copying profile ids into auth.order or /model ...@<profileId>.

Flow shape (PKCE):

  1. generate a PKCE verifier/challenge and a random state
  2. open https://auth.openai.com/oauth/authorize?... (scope openid profile email offline_access)
  3. try to capture the callback on http://localhost:1455/auth/callback (the callback host defaults to localhost and only accepts loopback hosts; override with APUS_OAUTH_CALLBACK_HOST)
  4. if you can paste a code before the callback lands (or you are remote/headless and the callback can't bind), paste the redirect URL/code instead - manual paste races the browser callback and whichever completes first wins
  5. exchange the code at https://auth.openai.com/oauth/token
  6. extract accountId from the access token and store { access, refresh, expires, accountId }

Wizard path is apus onboard → auth choice openai.

Refresh + expiry

Profiles store an expires timestamp. At runtime:

The refresh flow is automatic; you generally do not need to manage tokens manually.

Multiple accounts (profiles) + routing

Three patterns:

1) Preferred: separate agents

If you want "personal" and "work" to never interact, use isolated agents (separate sessions + credentials + workspace):

apus agents add work
apus agents add personal

Then configure auth per-agent (wizard) and route chats to the right agent.

2) Advanced: multiple profiles in one agent

The auth profile store supports multiple profile IDs for the same provider. Pick which one is used:

Example (session override):

3) Multi-user: personal accounts

On a shared gateway, each verified person can save several accounts per provider in Settings → Profile → Connected accounts and choose one as their new-chat default. Add account and apus models accounts login use the same Gateway-owned provider and sign-in method catalog. Anthropic personal setup accepts an API key, not a Claude subscription token; system/agent auth remains a separate flow. Both sign-in surfaces show the Gateway, verified person, and Personal scope before requesting provider credentials. Gateway identity and provider sign-in are separate; a shared Gateway token does not identify a person. See personal-account CLI setup.

The model picker in New session or an existing chat can select an account for that chat without changing the default. Ordered shared accounts remain same-provider failover candidates; the selection is not a billing guarantee. Personal credentials stay outside the shared profile list. See Per-person model accounts.

List your saved personal accounts with:

apus models accounts list

For shared or agent-local profile IDs, use:

apus models auth list --provider <id>

Related docs:

Related