Authentication and scopes
Every request to /api/v1 carries an API key in the Authorization header:
Authorization: Bearer pw_prj_1a2b3c4d_…
The same keys work for MCP.
The key string
A key string has the form pw_<adm|prj>_<prefix>_<secret>:
admorprjtells the kind of key.prefixis 8 lowercase hex characters. It is not secret; key lists show it.secretis 32 random bytes from the system CSPRNG, in base64url (43 characters).
The key string is shown once, in the answer that creates the key. The node stores the prefix and a SHA-256 hash of the secret, never the key string. A lost key string cannot be read again: revoke the key and create a new one.
The key string and the secret never appear in logs, in error bodies or in debug output.
Two kinds of key
| Kind | kind |
What it may do |
|---|---|---|
| Admin key | admin |
Everything, in every project, including projects and admin keys |
| Project key | project |
One project, within its scopes |
GET /api/v1/whoami (whoami) tells which kind you hold, its project (null for an admin key), its scopes and its expiry.
The first admin key comes from postwind keys create-admin on the node, or from security.bootstrap_admin_key_file; see HTTP API. More admin keys come from POST /api/v1/admin/keys (create_admin_key). Project keys come from POST /api/v1/projects/{project_id}/keys (create_api_key).
Scopes
A project key holds one or more of four scopes. An admin key holds all four.
| Scope | Allows |
|---|---|
read |
Reading |
write |
Creating and changing entities |
send |
Launching campaigns, test sends, state transitions |
keys |
Managing the keys of its own project |
A key with keys issues only what it holds itself:
- The new key’s scopes are a subset of its own scopes.
- If the issuing key expires, the new key expires no later. Such a key cannot issue a key without expiry.
- Otherwise the answer is
403 forbidden.
There is no hierarchy of keys. Revoking a key does not revoke the keys it issued.
Project isolation
Project resources live under /api/v1/projects/{project_id}/…. A project key works only with its own project_id. For every operation inside a project the checks go in this order:
- A project key and another project’s
project_id:404 not_found. The body is the same as for a project that does not exist, and this check comes before scopes. So the answer tells nothing about other projects, whatever the key’s scopes. - A project key without the scope of the operation:
403 forbidden. Thedetailnames the missing scope. - No such project:
404 not_found. - The operation changes something and the project is deleted:
409 conflict.
An admin key passes steps 1 and 2.
The keys of a deleted project stop working: authentication rejects them with 401 key_revoked before these checks.
Operations for admin keys only
- Projects:
list_projects,create_project,update_project_settings,delete_project. - Admin keys under
/api/v1/admin/keys:list_admin_keys,create_admin_key,revoke_admin_key. A project key gets403whatever its scopes. - Inside a project,
POST /api/v1/projects/{project_id}/subscribers/{subscriber_id}/reactivate(reactivate_subscriber). A project key of that project gets403whatever its scopes.
get_project is open to both kinds: an admin key reads any project, a project key with scope read reads its own.
The four 401 codes
Every 401 carries WWW-Authenticate: Bearer.
code |
When |
|---|---|
key_missing |
No Authorization header, a scheme other than Bearer, or nothing after Bearer |
key_invalid |
Not a key string, an unknown prefix, or a wrong secret. The three cases give the same answer on purpose, so that an answer does not tell which prefixes exist |
key_revoked |
The secret is right, but the key is revoked or its project is deleted; detail tells which |
key_expired |
The secret is right, but expires_at has passed |
key_revoked and key_expired come back only after the secret matched, that is only to someone who knows the secret.
A request with 401 does not count against the rate limit and does not reach idempotency.
Expiry and revocation
expires_atis optional. A key without it never expires.- Revoke a key with
DELETE:revoke_api_keyfor a project key,revoke_admin_keyfor an admin key. A key may revoke itself. - The last usable admin key cannot be revoked: the answer is
409 conflict. Without an admin key, projects and admin keys cannot be created through the API until the operator runspostwind keys create-adminon a node. - Each node keeps keys in a cache for
storage.clickhouse.cache_ttl_secs(15 s). A revoked key stops working on every node within that time; on the node that revoked it, at once.
Where each operation shows what it needs
Each operation page has an access line taken from the operation’s description: the kind of key and the scope. Each MCP tool says the same in its Needs … sentence.