API KEY AUTHENTICATION
API keys authenticate CLI commands and programmatic API requests. There are two types: personal keys and collective tokens.
Key formats
| Type | Prefix | Example |
|---|---|---|
| Personal key | swamp_ |
swamp_a1b2c3d4e5f6... (32 hex) |
| Collective token | swamp_org_ |
swamp_org_a1b2c3d4e5... (32 hex) |
Authentication headers
API requests accept either header:
x-api-key: swamp_a1b2c3...Authorization: Bearer swamp_a1b2c3...The CLI sends Authorization: Bearer when the user is logged in. The
x-api-key header remains accepted for backwards compatibility and is still
used internally on specific per-call paths (e.g. extension push, yank). When
both are present, Authorization takes precedence.
Device identity
In addition to the auth header, the CLI sends Swamp-Distinct-Id on every
request to /api/*:
Swamp-Distinct-Id: c2f3eae0-d758-4174-9e8d-7b3f9a000001The value is a per-device UUID lazy-created and stored by the CLI at
~/.config/swamp/identity.json. It enables stable per-device attribution for
telemetry on anonymous CLI traffic (extension search, info, pulls without login)
— without it the server falls back to a per-request anon_* hash that changes
on every call from the same machine.
The header is untrusted:
- It never authenticates or authorizes a request.
- It never resolves to a user record server-side.
- A session or API key always wins over the header when computing the
distinct_idfor an event. - The server validates the value as a canonical UUID and falls back to the
anon_*IP+UA hash on any other input.
Credential storage
The CLI stores credentials in ~/.config/swamp/auth.json (or
$XDG_CONFIG_HOME/swamp/auth.json). File permissions are set to 0o600. Writes
are atomic (temporary file, then rename) to prevent corruption.
The stored structure contains:
| Field | Description |
|---|---|
serverUrl |
Server the key authenticates against |
apiKey |
The raw API key (swamp_ prefix) |
apiKeyId |
Key ID for revocation tracking |
username |
Authenticated username |
collectives |
Cached collective membership slugs |
Credential precedence
SWAMP_API_KEYenvironment variable (highest priority)~/.config/swamp/auth.jsonfile- Default server:
https://swamp-club.com
When SWAMP_API_KEY is set, swamp auth login and swamp auth logout are
disabled.
Environment variables
| Variable | Purpose |
|---|---|
SWAMP_API_KEY |
API key for authentication (overrides auth.json) |
SWAMP_CLUB_URL |
Server URL override |
Scope
Personal keys create a full user session. The authenticated user's identity, roles, and collective memberships are all available. Most CLI commands that require authentication work with personal keys.
Collective tokens create a collective context only. They do not create a
user session. The middleware sets collectiveTokenContext with the collective
ID, slug, and token ID. Actions performed with a collective token are attributed
to the collective, not to any individual user. Some commands — such as
swamp extension push — accept collective tokens for CI/CD use cases where no
individual operative is involved.
Personal keys grant access to all collectives the user belongs to. Collective tokens are scoped to a single collective.
Collective token scopes
| Scope | Grants access to |
|---|---|
extensions:push |
Push extensions on behalf of the collective |
extensions:promote |
Promote extension versions |
extensions:read |
Read extension metadata |
extensions:* |
All extension operations |
lab:read |
Read lab issues |
lab:write |
Create and update lab issues |
lab:* |
All lab operations |
profile:read |
Read operative profiles |
profile:write |
Update operative profiles |
profile:* |
All profile operations |
collective:read |
Read collective metadata |
collective:write |
Update collective settings |
collective:* |
All collective operations |
billing:manage |
Manage billing and subscriptions |
feed:write |
Submit feed posts |
notifications:read |
Read notifications |
notifications:write |
Mark notifications as read |
notifications:* |
All notification operations |
oauth:manage |
Manage OAuth applications and consents |
serve:* |
Start swamp serve with --auth-mode token or --auth-mode oauth |
datastore:* |
Set up external datastores (S3, GCS, extension-provided backends) |
vault:* |
Create and manage non-local vault configurations |
A personal key carries all scopes implicitly. A collective token must include the required scope explicitly; without it the server returns HTTP 403.
Role requirements
| Operation | Required role |
|---|---|
| Create personal key | Any authenticated user |
| Create collective token | Owner or admin |
| Revoke collective token | Owner or admin |
| Delete collective token | Owner or admin |
Limits
Each collective can have a maximum of 25 API tokens.
Expiration
Collective tokens support an optional expiration date set at creation time. Expiration is enforced at verification — an expired token is rejected even if it is still enabled. Personal keys created by the CLI do not expire.
Key lifecycle
- Created — key is generated, raw value shown once, hash stored
- Enabled — key authenticates requests normally
- Revoked — key is disabled and can no longer authenticate; remains visible in the UI
- Deleted — key is permanently removed
Revoked keys cannot be re-enabled.
Security
Keys are hashed with SHA-256 (base64url encoding) before storage. The raw key is returned only at creation time. The key hash is never exposed in API responses or the web UI.
API endpoints
Personal keys (BetterAuth)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/api-key/create |
Create a key |
| GET | /api/auth/api-key/list |
List keys |
| POST | /api/auth/api-key/update |
Update a key |
| POST | /api/auth/api-key/delete |
Delete a key |
These endpoints require an active session (cookie or session token).
Collective tokens
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/collectives/{slug}/api-tokens |
List tokens |
| POST | /api/v1/collectives/{slug}/api-tokens |
Create a token |
| PATCH | /api/v1/collectives/{slug}/api-tokens/{tokenId} |
Revoke a token |
| DELETE | /api/v1/collectives/{slug}/api-tokens/{tokenId} |
Delete a token |
These endpoints require an active session with owner or admin role on the collective.
CLI Commands
swamp auth login
Authenticate with a Swamp server.
| Flag | Description |
|---|---|
--server |
Server URL (env: SWAMP_CLUB_URL) |
--username |
Username or email |
--password |
Password (omit to prompt) |
--no-browser |
Disable browser login, use username/password instead |
Default behavior opens a browser for device-flow authentication.
swamp auth logout
Remove stored authentication credentials. No flags.
swamp auth whoami
Show current authenticated identity. Aliased as swamp auth status. No flags.
Use --json for structured output.
Personal key output:
stack72 (paul@swamp-club.com) on https://swamp-club.com
Plan: Team
Collectives:
swamp Team active
system-initiative Free
stack72 Free trial: 13 days left (ends 2026-08-19)Plan is a roll-up: the strongest plan across every collective you belong to.
It answers "am I paying for anything?" but not "which collective needs
upgrading?" — that is what the per-collective list is for. Billing attaches to a
collective, never to a user, so a private extension push is refused on the
namespace's plan, not on yours.
The third column shows the collective's billing status when it is paying, and its trial when it is not. Three details worth knowing:
- Billing status is shown to owners and admins only. Plain members see the plan and its name but not the raw status, matching the access rules on the collective's billing page. When triaging a report from a member, expect the field to be missing rather than null.
- A paying collective never shows a trial, even if its 30-day clock is still
running. The trial is still present in
--json. - The trial grants no entitlement. Reaching day 31 changes what you are told, not what you can do.
If the server sends no entitlement — a self-hosted or older swamp-club — whoami
falls back to the single Collectives: a, b, c line.
Collective token output:
Collective token: myorg on https://swamp-club.com
Scopes: extensions:push, extensions:read
Collectives: myorgCollective token JSON output (--json):
{
"authenticated": true,
"serverUrl": "https://swamp-club.com",
"collectiveToken": true,
"collectiveSlug": "myorg",
"scopes": ["extensions:push", "extensions:read"],
"collectives": ["myorg"]
}The collective token response omits the id, username, email, and name
fields that appear in the personal key response.
swamp auth token create
Create a scoped API token for a collective from the CLI.
| Flag | Description |
|---|---|
--collective <slug> |
Collective to create the token for (required) |
--scopes <comma-separated> |
Scopes to grant, e.g. extensions:push,serve:* (required) |
--name <label> |
Token label; defaults to cli-<hostname>-<timestamp> (optional) |
--json |
Output in JSON format |
Requires personal authentication (swamp auth login) — collective tokens cannot
create other tokens. Only collective owners and admins can create tokens.
The token key is shown once at creation time and cannot be retrieved later. Maximum 25 tokens per collective.
swamp auth server-login
Store a server token for a swamp serve instance — subsequent --server
commands use it automatically.
| Flag | Description |
|---|---|
--server |
Server URL to associate the token with (required unless SWAMP_SERVE_URL is set) |
--token |
Server token in <name>.<secret> format (required for token auth; omit for OAuth/device flow) |
CLI commands that require authentication
| Command | Key type |
|---|---|
swamp auth whoami |
Personal or Collective |
swamp auth token create |
Personal |
swamp extension push |
Personal or Collective |
swamp extension yank |
Personal |
swamp extension unyank |
Personal |
swamp extension deprecate |
Personal |
swamp extension undeprecate |
Personal |
swamp issue submit |
Personal |
swamp issue ripple |
Personal |
swamp issue get |
Personal |
swamp datastore setup extension |
Personal or Collective (datastore:*) |
swamp vault create <type> |
Personal or Collective (vault:*); local_encryption is exempt |
swamp serve --auth-mode token |
Personal or Collective (serve:*) |
swamp serve --auth-mode oauth |
Personal or Collective (serve:*) |
When a collective token is used with a command that requires a specific scope, the server returns HTTP 403 with:
{ "error": "This token requires the <scope> scope" }