Authentication

Every request sends one credential as Authorization: Bearer <credential>. Which credential an endpoint takes is fixed; each endpoint's page says which. A missing header returns 401 missing_authorization. A header that is not Bearer <something> returns 401 malformed_authorization.

API keys

For your backend only. Never ship an API key to a browser or an app.

Authorization: Bearer pk_live_<prefix>_<secret>

Keys are created in the dashboard (see API keys). A key looks like pk_live_… or, for a test key, pk_test_…. The full key is shown once, when you create it. Afterwards only its prefix is visible. If you lose a key, revoke it and create a new one.

FailureResponse
Key unknown, malformed, or wrong secret401 invalid_api_key
Key revoked401 revoked_api_key
Key lacks the scope the endpoint needs403 forbidden. The message names the missing scope.

Scopes

A key carries a list of scopes, chosen when you create it. Endpoints not listed here need no scope: any valid key works.

ScopeUnlocks
users:managePOST /v1/users/:user_id/tokens
webhooks:adminEvery /v1/webhooks… endpoint
tokens:adminPOST /admin/v1/tokens/revoke
audit:readGET /v1/audit_logs

No scope needed: GET /v1/whoami, POST /v1/users, GET /v1/calls, GET /v1/calls/:id.

New keys created in the dashboard start with users:manage selected. If you clear it, that key cannot mint user tokens. Use GET /v1/whoami to see the scopes on a key.

Revoking and rotating

Revoke a key in the dashboard. The next request with it gets 401 revoked_api_key. User JWTs are not tied to the key that minted them: they keep working until they expire.

To rotate, create the new key, deploy it, then revoke the old one. See Token rotation.

End-user JWTs

One per signed-in end-user. Your backend mints it with POST /v1/users/:user_id/tokens (API key with users:manage) and hands it to your app.

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…
  • It lasts 1 hour. The mint response gives expires_in (seconds) and expires_at. There is no refresh endpoint: mint a new one.
  • It acts as exactly one user in exactly one tenant. Its sub claim is the user's poolse id and tnt is the tenant id.
  • The same token opens the realtime socket. See Socket & channels.
FailureResponse
Bad signature, expired, or the user no longer exists401 invalid_user_token
Revoked with POST /admin/v1/tokens/revoke401 token_revoked
The user has is_blocked: true401 user_blocked
The user is new this month and the plan's monthly active users are full402 mau_cap_exceeded (details)

Typical flow

1. Your app → your backend (your own session)
2. Your backend → POST /v1/users            (API key; ensure the user exists)
3. Your backend → POST /v1/users/:user_id/tokens   (API key, users:manage)
4. Your backend → your app                  (the JWT)
5. Your app → poolse REST + socket          (the JWT)

Step 2 returns the user's poolse id, which step 3 needs. Store it next to your own user record so you can skip step 2 later.

Revoking a user's access

  • One token: POST /admin/v1/tokens/revoke with the token. Needs tokens:admin. See Users.
  • Every token, for good: set is_blocked: true with POST /v1/users. Every request with that user's tokens then gets 401 user_blocked. Set it back to false to restore access.

Both also close the user's open realtime sockets at once. After a revoke, a client holding another valid token reconnects. A blocked user cannot reconnect.

Call grants and control tokens

Calls use two short-lived tokens of their own, so a phone that is locked or closed can act on a call without a user session:

TokenWhere you get itLifetimeAccepted by
Ring grantThe grant field of a ring push180 secondsPOST /v1/calls/:id/answer, POST /v1/calls/:id/decline
Call-control tokencontrol_token in every call session response24 hours (the call ending ends it sooner)POST /v1/calls/:id/hangup, reconnect, ring

Each works only for the call it was issued for. A user JWT is not accepted where a grant is required, and the reverse, except that decline also takes a user JWT. Failures return 401 unauthorized.

On the grant endpoints (answer, decline, hang up, reconnect, ring), every credential is checked against its user on each request. If the user is blocked, ring grants, control tokens and the user JWT accepted by decline are all refused with 401 unauthorized and the message User is blocked., including grants issued before the block. If the user no longer exists, they are refused with 401 unauthorized and Invalid or expired token. See Calls.

Reply grants

A message notification push carries a reply_grant. It lets the phone reply to that one conversation or mark it read, for 3 days, without a session. See Notification actions.

Authentication · API · poolse docs