Authentication and API keys
Session tokens versus API keys, how to mint a key, and what a key can never do.
Two credentials reach the API, and they are not interchangeable.
| Session JWT | API key | |
|---|---|---|
| How you get it | POST /api/auth/login | Settings → API keys in the dashboard |
| Lives for | 15 minutes; the refresh cookie extends it | Up to 365 days, or until revoked |
| Reaches | Every endpoint your account can | Only endpoints explicitly opened to keys, and only within the key’s scopes |
| Second factor | Yes, if you enabled it | No — which is why a key is scoped and expiring |
Both travel in the same header:
curl -H "Authorization: Bearer $DOCKBOARD_API_KEY" \
https://panel.example.com/api/applicationsA key is recognisable by its dbk_ prefix, and the API routes on that prefix — there is no separate header or endpoint to learn.
Minting a key
Settings → API keys → New key. Or over the API, with a session — see why key management is session-only:
curl -X POST https://panel.example.com/api/api-keys \
-H "Authorization: Bearer $SESSION_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Actions — staging deploys",
"scopes": ["project:apps:deploy", "project:deployments:view"],
"projectIds": ["prj_staging"],
"allowedIps": ["203.0.113.0/24"],
"expiresAt": "2027-01-01T00:00:00.000Z"
}'{
"key": {
"id": "…",
"prefix": "dbk_AbC12345",
"scopes": ["project:apps:deploy", "project:deployments:view"]
},
"secret": "dbk_…"
}The fields
| Field | Required | Notes |
|---|---|---|
name | yes | ≤ 100 characters. Shown in the key list and every audit line. |
scopes | yes | See scopes. An unknown scope is dropped; if nothing valid survives, the mint is refused rather than producing a weaker key than you asked for. |
projectIds | no | Restricts the key to these projects. Omit for “every project the owner can reach”. An empty list is refused — it reads as “nothing” and would behave as “everything”. |
allowedIps | no | IPs or CIDRs, v4 and v6. A key presented from outside its allowlist is refused exactly as an unknown key is. |
expiresAt | no | ISO-8601, in the future, within 365 days. Omit for no expiry. |
kind | no | PERSONAL (default) or MACHINE. SUPERADMIN only for MACHINE. |
ownerId | no | Mint a key that acts as another user. SUPERADMIN only. |
PERSONAL versus MACHINE: a personal key acts as you and is yours to manage. A MACHINE key is org-owned automation that outlives whoever created it — which is why only a SUPERADMIN can mint one, and why minting one for another user is the same privilege.
The other calls
GET /api/api-keys # your keys; secrets are never returned
GET /api/api-keys/scopes # every grantable scope, for a picker
POST /api/api-keys/:id/revoke # stops it working, keeps the row for the audit trail
DELETE /api/api-keys/:id # drops the row too — revoke is usually what you wantOne account may hold 50 live keys at a time. Revoked and expired ones do not count, so rotating never runs you into the cap — only accumulating does.
What a key can never do
Some capabilities are absent from the scope catalog entirely, so no key can hold them whatever its owner’s rights:
- Deleting a project, transferring its ownership, managing its members or its roles.
- Managing API keys — see below.
These stay reachable through the dashboard, where a human session and 2FA stand in the way.
Why key management is session-only
/api/api-keys/* carries no scope declaration at all, which means an API key is refused there even if it holds every scope in the catalog. A key that can mint keys is a key that can escape its own scopes and outlive its own revocation. Minting requires a session.
Stopping a key you do not own
The calls above are scoped to the caller, which leaves one state unanswerable: a MACHINE key is org-owned automation, and someone can leave with it in their pocket. So platform admins get a separate, session-only surface:
GET /api/admin/api-keys # every key on the install
GET /api/admin/api-keys?userId=… # narrowed to one owner
POST /api/admin/api-keys/:id/revoke
DELETE /api/admin/api-keys/:idListing needs users:view; revoking and deleting need `users:manage` — the same permission as suspending the account, because killing someone’s credential is the same class of act. Both write an audit line naming the key *and its owner*, since after a delete there is nothing left to ask.