API key vs OAuth
Two ways to authenticate to CMDOP — pick by who’s calling and how often.
TL;DR
| Use case | Pick |
|---|---|
| Interactive human (terminal, desktop) | OAuth |
| CI / unattended agent | Fleet API key |
| Bot / scheduled script | Fleet API key |
| SDK during development | OAuth (your login) |
| SDK in production | Fleet API key |
| Mixed (script today, you tomorrow) | Both — resolver picks |
OAuth
OAuth tokens come from cmdop login (device flow). Properties:
- Identity — your personal account.
- Fleets — every fleet you belong to.
- Lifetime — short access token (typically 1 hour) auto-refreshed 5 minutes before expiry.
- Storage —
token_<mode>.jsonin CMDOP’s config dir (token_prod.json,token_dev.json). - Revocation — sign out, rotate the OAuth secret, or wait for refresh expiry.
OAuth is the right pick when a human is around to walk through device flow and the action is “yours” in the audit log.
Fleet API key
API keys are long-lived bearer tokens scoped to one fleet. Properties:
- Identity — the fleet, plus a name you set at creation.
- Fleets — exactly one.
- Lifetime — never expires unless you set an expiry. Defaults to “no expiry” — set one anyway if you can.
- Storage —
ssh_workspaces.json(mode 0600). - Revocation — explicit revoke from Fleet settings.
API keys survive member churn — a CI pipeline does not break because someone left the company.
Side by side
| Property | OAuth token | Fleet API key |
|---|---|---|
| Bound to | Person | Fleet |
| Multi-fleet | Yes | No (one key per fleet) |
| Refresh | Automatic (5 min before expiry) | None — long-lived |
| Setup time | Device flow + browser | Cabinet click → copy secret |
| Revoke | Logout / rotate | Cabinet → revoke |
| Best for | Humans | Machines |
| Audit attribution | [email protected] | apikey:gha-deploy |
Credential resolver order
When you run cmdop ..., the resolver picks credentials in this order (internal/connect/workspace/Resolver.ResolveCtx):
--api-keyflag (explicit per-call override).CMDOP_API_KEYenvironment variable.--workspace=<name>flag — uses the named fleet’s stored key (the flag keeps its legacy name).- Active fleet key from
ssh_workspaces.json. - Legacy
cfg.Chat.GrpcAPIKey(one-shot migration). - OAuth access token (fallback — “just works”).
Each Source is tagged in error messages so you know what was picked when something fails.
Rotating an API key without downtime
- Issue a new key with the same scopes in the cabinet.
- Roll the new value out to consumers (CI secret, deployment env).
- Watch audit log for the old key’s last use.
- Once you see the new key in audit (and the old key idle), revoke the old key.
Mixing in CI
Common pattern:
# In CI, where humans aren't around:
export CMDOP_API_KEY="<fleet key>"
cmdop connect prod-1 exec 'systemctl status myapp'
# At your desk:
unset CMDOP_API_KEY
cmdop login # OAuth
cmdop connect prod-1 exec 'systemctl status myapp'The resolver prefers the env var, so CI uses the API key and your laptop falls back to OAuth.
API keys are fleet-bound. To use machines in fleet B you need a key for fleet B (or sign in with OAuth as a member of B).