Generated from Postwind commit a19b2e4 OpenAPI 0.1.0 synced

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>:

  • adm or prj tells the kind of key.
  • prefix is 8 lowercase hex characters. It is not secret; key lists show it.
  • secret is 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:

  1. 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.
  2. A project key without the scope of the operation: 403 forbidden. The detail names the missing scope.
  3. No such project: 404 not_found.
  4. 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 gets 403 whatever its scopes.
  • Inside a project, POST /api/v1/projects/{project_id}/subscribers/{subscriber_id}/reactivate (reactivate_subscriber). A project key of that project gets 403 whatever 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_at is optional. A key without it never expires.
  • Revoke a key with DELETE: revoke_api_key for a project key, revoke_admin_key for 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 runs postwind keys create-admin on 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.