API tokens#
Admin → API tokens issues the credentials that scripts, CI jobs and configuration tools use. The page's subtitle: "Tokens for scripts and automation to use the API, each with its own role." A token carries one of the four roles, an expiry if you give it one, and the label you gave it. Only a hash of the token is stored, and the token itself is shown exactly once.
The page is admin-only. Nothing here goes to the pending changes: issuing and revoking take effect at once, and both are written to the audit log.
The web interface uses the same API, so a token can do what a signed-in person with the same role can do, without a browser.
The table#
The Tokens panel has one row per token. The token itself is never in it.
| Column | What it shows |
|---|---|
| Label | What you named it. |
| ID | Twelve hexadecimal characters. This identifies the token; it is not the token. |
| Role | Viewer, Approver, Operator or Admin. |
| Issued by | Who issued it. |
| Issued | How long ago. |
| Last used | How long ago, or Never. It is updated at most once a minute per token. |
| Expires | A date, or Never. Shown in amber within seven days of the date. |
| Revoke — see Revoking. |
With no tokens, the panel says "No API tokens" and "Issue a token for a script or an automation system to use the API."
Issuing one#
Issue token opens the Issue API token panel.
| Field | What it takes |
|---|---|
| Label | The hint: "Name the system that will use this token." — terraform-prod, backup-cron — rather than the person. Required: Issue token stays disabled without one, and the agent refuses a token without a label too. |
| Role | Viewer: reads everything, Approver: applies changes others prepare, Operator: prepares and applies changes, or Admin: also manages users, tokens and settings. |
| Expires | Never, In 30 days, In 90 days or In a year. |
Choosing Admin adds a line in amber: "An admin token can add users and issue more tokens." Most automation needs no more than Operator, which covers changing the router and nothing that changes who may.
When the token is issued, a panel titled New token: followed by the label appears above the table:
Copy the token now. It is not shown again.
The token is below it, with a copy button. Done closes the panel, and the token is gone from the page: a lost token is revoked and a new one issued, not recovered. In Advanced mode the panel also shows an Example request built for this router:
curl -H "Authorization: Bearer wh_…" https://<router>:8443/api/config | jq .What a token looks like, and what is kept#
- A token is
wh_followed by 48 hexadecimal characters — 24 random bytes. Thewh_prefix makes a leaked token easy to find in a log and to catch with a secret scanner. - What is stored is the SHA-256 of the token, its ID, its label, its role, who issued it and when, when it was last used and when it expires.
- A token is looked up by comparing hashes in constant time, and an expired token is treated as no token at all.
The file that holds all of this is readable by root alone, in the agent's data directory.
Using one#
R=https://<router>:8443
T=wh_…
curl -sk -H "Authorization: Bearer $T" $R/api/firewall/rules | jq .A token is sent in the Authorization header. Two things follow from that:
- A token needs no CSRF header. The CSRF requirement belongs to the browser's cookie, which a browser sends automatically; a script sends a header on purpose. So a token can add to the pending changes with a single request, and a cookie cannot.
- A token in the URL is accepted on one route only.
/api/streamtakes?token=, because a browser WebSocket cannot set headers. Everywhere else the agent refuses it and says why: a credential in a URL ends up in proxy logs, browser history andRefererheaders.
Adding to the pending changes and applying them with a token takes the same requests the web interface makes — here with the auto-revert window of two minutes, then keeping the change:
curl -sk -X POST $R/api/stage -H "Authorization: Bearer $T" \
-H 'Content-Type: application/json' \
-d '[{"op":"set","path":["firewall","ipv4","forward","filter","rule","110","action","accept"]}]'
curl -sk -X POST $R/api/commit -H "Authorization: Bearer $T" \
-H 'Content-Type: application/json' -d '{"confirm_minutes":2}'
curl -sk -X POST $R/api/commit/confirm -H "Authorization: Bearer $T"The first request needs an operator token. The other two need at least an approver token.
What a token may do#
Exactly what its role may do — see Roles. The same rules, and the same 402
when the router has no usable licence and the request would change the router.
Two differences from a browser session:
- A token is never held up by the two-factor requirement. Require two-factor authentication applies to browser sessions. A token, or the break-glass token, is the way back when somebody has locked themselves out of the web interface.
- A token is its own principal. It appears in the audit log as
token:<label>, not as the person who issued it — which is why the label matters, and why account names beginningtoken:are refused.
Revoking#
Revoke asks "Revoke <label>?" before it acts — "Anything using this token stops
working at once" when you hover over it. Whatever was using the token is refused on its
next request, and there is no way to bring the token back. The message afterwards says the
label can no longer be used, and the audit log records revoke-token with the token's ID.
Tokens are independent of accounts. Deleting the account that issued a token does not revoke it; the token keeps its own role until it is revoked or expires. Check both lists when somebody leaves.
The break-glass token is not one of these#
The agent can be given a break-glass token: a file holding a token that authenticates as an
admin with no account behind it. It is not issued here, does not appear in this table, and
cannot be revoked from the page. To remove it, delete the file at the router's console, then
restart the agent — Restart agent on Agent settings
does the restart. The installed service looks for /config/wheelhouse/admin-token, and
nothing creates that file, so a new router has no break-glass token at all.
For automation#
GET /api/admin/tokens lists the tokens, without the tokens themselves.
POST /api/admin/tokens issues one — label, role, and expires_days (0 for never) — and
its answer is the only place the token ever appears. DELETE /api/admin/tokens/{id}
revokes one, and answers 404 for an ID that is not there. All three are admin-only.
See also#
- Roles — what each role can do.
- Users — accounts, which are the other kind of principal.
- Account — sessions, CSRF and the cookie a browser uses instead.
- Authenticating — the header, the CSRF rule and the stream exception.
- The HTTP API — what a token can call.
- What the licence gates — why a change can answer
402.