Documentation menu

Billing: subscription or API key

Claude Code, Codex and Gemini CLI can run on two kinds of credentials:

  • subscription: the plan you are logged in with in the CLI (Claude Pro/Max/Team via claude.ai, a ChatGPT plan for Codex, a Google account login for Gemini). It is a flat fee with usage limits.
  • api: a metered API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY). You pay per token.

nid picks one per session, so a stray ANTHROPIC_API_KEY in your shell doesn't silently bill your API account while you meant to use your Max plan. It also works the other way: you can run selected sessions on an API key while the rest use the subscription.

Modes#

ModeWhat nid does
auto (default)Use the subscription login when the CLI has one; otherwise use an API key; otherwise fail the start and say how to log in.
subscriptionStart the agent with its API-key variables removed, so the CLI uses its login. Fails the start when there is no subscription login.
apiStart the agent with an API key from the daemon's environment or from nid auth set. Fails the start when there is no key.

How each mode is applied:

Agentsubscriptionapi
clauderemoves ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN; login checked with claude auth status (a claude.ai account, not an API key or Bedrock/Vertex)sets ANTHROPIC_API_KEY
codexremoves OPENAI_API_KEY and CODEX_API_KEY, selects the built-in openai provider with forced_login_method="chatgpt", checks account/read and reports the ChatGPT planadds a model provider nid-openai-api (env_key = "OPENAI_API_KEY", requires_openai_auth = false) and selects it, so Codex reads the key without replacing your stored ChatGPT login (~/.codex/auth.json)
gemini (preset)removes GEMINI_API_KEY and GOOGLE_API_KEY; login = ~/.gemini/oauth_creds.jsonsets GEMINI_API_KEY (untested; Gemini also follows its own selectedAuthType setting)
opencode, kiminot managed: they bill through their own provider configuration (opencode auth login; kimi login / kimi provider)not managed
other custom agentsnot managed: launched unchangednot managed
mock (test driver)simulated: recorded, nothing changessimulated

Unmanaged agents accept only auto. Asking for subscription or api explicitly fails the start with the reason.

nid decides the billing every time an agent starts (spawn, resume, crash restart, rotation). A new login or a new key therefore applies on the next resume. The result is stored on the session (Session.billing) and logged as a notice, for example billing: subscription (claude.ai max).

Native takeover (attach --native) keeps the session's resolved mode. The attaching CLI resolves credentials locally; keys are never transferred in the takeover RPC response. For API mode, the attaching shell must have a key or access to the same nid secret store. If it cannot resolve the mode, it fails and hands the session back to the daemon. Native turns remain outside nid's usage accounting.

Where to set it#

Most specific wins: session > group > ~/.nid/config.yaml > auto.

# ~/.nid/config.yaml: default for everything
billing: auto
# ~/.nid/config.yaml: or per agent, with an optional default
billing:
  default: subscription
  codex: api
# nid.yaml
groups:
  web:
    billing: { claude: subscription, codex: auto }   # group setting (same shapes as above)
    sessions:
      - claude:lead
      - agent: codex
        name: bulk
        billing: api                                  # this session only
nid spawn claude:lead --billing subscription
nid group set web billing='{"codex":"api"}'       # change a running group's setting

The dashboard's Spawn form has a Billing field. Session pages show the billing, and the group table marks API-billed sessions with an api badge.

API keys#

nid uses an API key from the daemon's environment first, for example ANTHROPIC_API_KEY exported in the shell that started the daemon. Otherwise it uses a key stored with:

nid auth set claude --api-key          # prompts without echo; or pipe it: echo $KEY | nid auth set claude
nid auth set codex --api-key sk-...    # inline (ends up in your shell history)
nid auth rm claude

Where stored keys live:

  • macOS: the login keychain (service nid, account = agent name).
  • Linux: the Secret Service via secret-tool when it works (GNOME Keyring, KWallet).
  • Windows: ~/.nid/secrets/<agent>.dpapi, encrypted with DPAPI for the current Windows user.
  • Unix fallback when the keychain tool fails: ~/.nid/secrets/<agent>.key, mode 0600. The confirmation names the plaintext file explicitly. Windows DPAPI failures abort; nid does not fall back to plaintext there, and an existing key is kept.

The default home's keyring service is nid. A custom NID_HOME gets a service name derived from that directory, so disposable homes and separate installations cannot read or delete each other's keys. auth rm removes that home's file and keyring entry. Confirming data removal during uninstall --purge also clears its managed keyring entries. A locked/unavailable keyring with recorded entries stops the purge so it can be retried.

Keys go to keychain helpers on stdin, not their command lines (an inline --api-key still appears in the nid command line and shell history). The daemon reads the key when a session starts and passes it through the agent environment. Vendor child processes can inherit that environment. nid does not put the key in session billing metadata, notices or generated MCP configuration; arbitrary agent output is not a secret-redaction boundary.

Falling back to the API key on rate limits (opt-in)#

When a subscription plan hits its usage limit, nid normally holds the session until the window resets (see provider rate limits). With fallback: api it keeps working on your API key instead:

# nid.yaml (per group) or ~/.nid/config.yaml (default for every group)
fallback: api        # api | off (default off)

nid group set web fallback=api turns it on for a running group. When a session that runs on its subscription becomes rate-limited, nid:

  1. checks that an API key is available for the agent (environment or nid auth set) and that the group's budget is not spent; otherwise it says why in the session log and waits as usual;
  2. restarts the agent on the same conversation with API billing (after the current turn);
  3. records the switch as a session notice, a board note, a human inbox message and a limit notification;
  4. when the subscription window resets (the reset time the provider reported), switches back to the subscription the same way, between turns, and records that too.

Turns made during the fallback are billed spend and count against budgetUsd; once the budget is spent automatic turns pause as usual. Session.billing.fallback ({until, since, reason}) shows an active fallback; a daemon restart keeps it until until. Only sessions currently billed on a subscription switch: sessions already on api and unmanaged agents (opencode, kimi, custom) are never switched. The fallback is per session; other sessions of the group keep their own billing.

Checking#

nid auth status     # per agent: what a new session would get, the subscription login, where an API key would come from
nid doctor          # includes the same lines for installed agents
nid ls              # BILLING column: sub, api (highlighted) or - (unmanaged / not started)
nid status          # lists API-billed sessions per group (api:<session>)

Notes#

  • auto with both a login and a key present uses the subscription, and the key in the environment is removed for that agent. Use api to force the key.
  • nid auth status runs claude auth status and codex login status. Checks are fresh for each launch; a different custom command or login directory cannot reuse an old result.
  • nid never logs you in or out of a CLI and never edits the CLI's own credential files. Switching billing only changes how nid launches the agent.
  • Billing modes steer the supported CLI launch paths, not arbitrary vendor configuration. Explicit extraArgs can override nid's flags. Vendor settings, API key helpers, cloud providers and Gemini's selectedAuthType can also affect authentication; avoid conflicting settings and check the vendor's reported mode. Gemini remains unverified.

Codex launch settings were checked against the 0.160.1 configuration schema.