CLI credentials and scopes
The civitai CLI holds one credential at a time, and which one you have decides what you can do. Two commands can look identical and fail differently purely on this.
This page is about the CLI's stored credential. For passing a token to the REST API directly — headers, query parameters, what a 401 looks like — see Authentication.
Two kinds of credential
civitai login — the OAuth device flow
civitai login with no flags runs the OAuth device-authorization grant: it prints a URL and a short code, you approve in your browser, and the CLI stores a short-lived access token plus a refresh token that it rotates automatically before requests and once on a 401. The lifetimes are the server's, not the CLI's — the CLI stores whatever expiry the token response carries and refreshes against it.
By default it requests identity, Apps submit (which gates both app submit and the dev-token mint) and the on-site dev tunnel — the bitmask 100663297.
A default browser login cannot spend Buzz
That default deliberately omits AIServicesWrite. A plain civitai login drives the read/identity paths — viewer, catalog, app storage — but for a generation app it has its ai:write:budgeted scope stripped at mint time and cannot estimate, submit, or spend real Buzz. Re-running plain civitai login will not fix that.
civitai login --scopes generate — opting in
civitai login --scopes generate # additive: keeps submit + dev-tunnel, ADDS generationThat requests 100777985 — the default plus AIServicesRead, AIServicesWrite and BuzzRead — giving one credential that can both submit apps and run civitai generate.
--scopes takes a named set, never a raw bitmask; an unknown name is rejected with the list of valid ones. It applies only to the browser device login and is refused alongside --token.
The device-flow scope check is all-or-nothing
Requesting any bit the civitai-cli OAuth client's allowedScopes does not permit rejects the whole login with invalid_scope. On civitai.com that client's allowedScopes is 100777985, so --scopes generate works. Against a self-hosted or older auth server (a non-default CIVITAI_BASE_URL) that predates the widening it is rejected, and the CLI maps the rejection to a message telling you plain civitai login still works there.
A personal API key
A full-scope personal API key is the other way to get spend authority, and it is still the only credential carrying the rest of the Full scope mask. Create one at your account page, then:
civitai login --token <key> # stores a personal API key (no refresh)CIVITAI_TOKEN in the environment overrides the stored credential and is treated as a personal key.
What civitai whoami reports
whoami prints the authenticated user, then two sections: a Credential section carrying the one identity attribute (the credential type), and a Capabilities section of three verdicts.
$ civitai whoami
Logged in as ada (id 12345) at https://civitai.com
Credential:
Type: personal API key
Capabilities:
Read Buzz balance: yes
Spend Buzz (AI Services): yes
Submit Apps: yesThat is the whole of stdout for a full-scope personal key. A credential whose Spend Buzz row says no gets one more block appended below, naming the fix for that credential — civitai login --scopes generate for an OAuth login, a full-scope personal key otherwise.
civitai whoami --scopes additionally prints the full decoded scope list.
Submit Apps is a tri-state, and unknown is not no
The server scope-gates submit only on OAuth tokens (the opt-in AppBlocksSubmit bit, which the Full scope mask deliberately excludes); a personal API key is not scope-gated for submit at all. So:
| Credential | tokenScope reported | Submit Apps |
|---|---|---|
| Personal API key | either | yes — never scope-gated, so the mask is irrelevant |
| OAuth login | present | yes / no from the AppBlocksSubmit bit |
| OAuth login | absent | unknown — the bit is the answer, and the server did not report it |
No subject in the response | either | unknown — we cannot tell which gate applies |
When the server reports no scope mask, the two Buzz rows are omitted rather than printed as no, and the output says so. It is scoped to Buzz because the Submit Apps row above it may well be a known answer:
$ civitai whoami
Logged in as ada (id 12345) at https://civitai.com
Credential:
Type: OAuth login
Capabilities:
Submit Apps: unknown
(token scope not reported by the server — Buzz capabilities unknown)A yes means the credential's scope permits submit — not that the account is in the author cohort. The remaining author-cohort and not-banned gates are server-side and are not visible to the CLI.
whoami --json
--json is a stable, curated object of fourteen keys, not the server's raw /api/v1/me body. The only two fields the server sends that never appear are the PII fields email and emailVerified.
{
"base_url": "https://civitai.com",
"canReadBalance": true,
"canSpend": false,
"canSubmitApps": true,
"capabilities": { "can_read_buzz": true, "can_spend_buzz": false },
"credentialType": "personal API key",
"id": 12345,
"isMember": true,
"scopes": ["UserRead", "BuzzRead"],
"scopesKnown": true,
"status": "active",
"subscriptions": ["yellow"],
"tier": "silver",
"username": "ada"
}The account profile is null when unreported, never "" / false / []
tier, status, isMember and subscriptions are null when the server did not report them — the CLI never fabricates a value — so test for null before reading any of them. subscriptions is a list of strings: [] means reported and empty. The four come from one strict parse, so if any arrives in an unexpected shape, all four become null (identity and capabilities still print normally).
isMember is the one worth branching on: members and free accounts see different usable generation ecosystems, so it predicts whether civitai generate's defaults are available to this credential.
Two fields carry a third state
canSubmitAppsistrue/false/null.nullmeans unknowable (the twounknownrows in the table above).if (!j.canSubmitApps)treats it asfalseand reports a dead end that may not exist — testj.canSubmitApps === nullfirst.scopesis a list,[], ornull.nullmeans the scope mask was not reported (scopesKnown: false);[]means it was reported with no bits set.
canReadBalance and canSpend stay plain booleans: when scopesKnown is false, both are false because nothing is known, not because the capability was denied.
Where to go next
- Generating images from the CLI — which of these credentials can spend.
- Exit codes — exit
3and what it does not cover: see the CLI README. - CLI guide — installing the binary and the read commands.