Token rotation
There are two kinds of credentials, and they rotate differently:
- API keys (
pk_live_…) live on your backend until you revoke them. - End-user JWTs live for one hour. Your backend mints a new one each time the SDK asks.
Rotating an API key
Rotate on a schedule, and at once if a key leaks. You can have several active keys, so there is no downtime.
1. Create the new key
On the dashboard, open API keys and create a key.
- Give it the same scopes as the old key, or fewer. A key that mints user
tokens needs
users:manage. - Put the date in the name so you can tell the keys apart.
- Copy the full key; it is shown once.
See API keys.
2. Deploy it
Put the new key in your backend's secret store (for example the
POOLSE_API_KEY environment variable) and deploy. From now on both keys
work.
3. Check that the old key is unused
The Last used column on the API keys page shows when each key last made a request. Wait until the new key shows activity and the old key's time stops moving.
4. Revoke the old key
Click Revoke on the old key. The check happens on every request, so the
next request with that key fails with 401 revoked_api_key.
Tokens minted with the old key
Revoking an API key doesn't invalidate JWTs it already minted. They keep working until they expire (at most one hour), because the server checks a JWT's signature and expiry, not the key that minted it.
To end a specific JWT earlier, revoke it.
Revoking an end-user JWT
/admin/v1/tokens/revokecurl -X POST https://api.poolse.dev/admin/v1/tokens/revoke \
-H "Authorization: Bearer $POOLSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"token":"eyJhbGciOi…"}'
{ "jti": "9b3c…", "expires_at": "2026-10-27T15:42:10Z" }
The token is blocked for the rest of its lifetime. A blocked token is refused
on REST requests with 401 token_revoked, and when it is used to open the
socket.
Revoking also closes every open socket of that user, because a socket
doesn't record which token opened it. A device that holds a different, valid
token reconnects on its own, and from 3.1 useMessages and
useConversations reload what was sent in the gap (see
Reconnects). On a device whose token was the
revoked one, the socket handshake is refused; from 3.1 the SDK then asks your
getToken for a new token (at most every 30 seconds) and reconnects with it,
so if your route still issues tokens for that user the device recovers on its
own. Its next REST call does the same after the 401.
| Status | Code | When |
|---|---|---|
| 400 | missing_token | The body has no token field. |
| 400 | invalid_token | The token is malformed, already expired, or lacks the required claims. |
| 403 | forbidden | The key lacks the tokens:admin scope. |
| 403 | cross_tenant | The token belongs to another workspace. |
Revoking a token doesn't stop your backend from minting a new one for the same user. To keep someone out for good, block them.
Blocking a user
Set is_blocked with the user upsert:
curl -X POST https://api.poolse.dev/v1/users \
-H "Authorization: Bearer $POOLSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"external_id":"user_123","is_blocked":true}'
While a user is blocked:
- Every REST request with their token fails with
401 user_blocked, including tokens minted before the block. - Opening the socket is refused.
- Their open sockets are closed when the block is saved.
- Every call action is refused: starting, ringing or joining calls and rooms,
and answering, declining, hanging up or ringing people in with a call's own
grant (the token the phone gets for a ringing or live call). This includes
grants issued before the block. Those requests get
401 unauthorizedwith the message "User is blocked.". POST /v1/users/:id/tokensstill mints a token for them. The token is refused as soon as it is used, but your token route should stop asking for one anyway.
To unblock, send the upsert again with "is_blocked": false. Leaving the
field out keeps the current value.
Rotating end-user JWTs
You don't need to. They expire after an hour, and the SDK asks your
getToken for a new one shortly before that (see
Token caching). Make sure your token route
mints a new JWT on each call instead of returning a cached one.
Audit log
Creating and revoking keys on the dashboard is recorded in your workspace's
audit log as dashboard.api_key.created and dashboard.api_key.revoked. Read
it with GET /v1/audit_logs, using a key with the audit:read scope; see
Audit logs. Revoking a JWT isn't recorded there.
When to rotate at once
- The key appeared in a Git push, a CI log, a screenshot or a ticket.
- A laptop or server that held the key was lost or compromised.
- Someone who had access to the key left.