Users

An end-user is one person in your app. poolse knows them by two ids:

  • id: poolse's UUID for the user. Token minting, mentions, call history and membership removal use it.
  • external_id: your own id for the user, any string up to 255 characters, unique within your tenant. Conversation and member creation use it.

Everything on this page is called from your backend with an API key.

There is no API to list, fetch or delete users with an API key. A user can read their own record with GET /v1/me.

Provision or update a user

POST/v1/users
API key

Creates the user if external_id is new in your tenant. Otherwise updates the existing user. Any valid API key works; no scope is needed.

On an existing user, only the fields you send change. A field you leave out keeps its stored value. So {"external_id": "u_42"} is a safe "make sure this user exists" call: it never clears a profile or unblocks a blocked user.

You do not have to call this before a user appears in a conversation. Unknown external_ids passed to POST /v1/conversations (member_external_ids) or POST /v1/conversations/:conversation_id/members (external_ids) are created automatically, with only external_id set. Call this endpoint when you want to set a profile, block someone, or get the poolse id to mint a token.

Request body

FieldTypeRequiredDescription
external_idstringyesYour id for the user. 1–255 characters.
display_namestring | nullnoUp to 255 characters. poolse puts it in call summaries and call media tokens.
avatar_urlstring | nullnoUp to 2048 characters.
custom_dataobjectnoFree-form JSON. Replaces the stored object as a whole. It is not merged.
is_blockedbooleannotrue blocks the user: requests with their JWTs get 401 user_blocked, the call grant endpoints refuse their ring grants and control tokens (401 unauthorized, even grants issued earlier), and their open realtime sockets are closed. Reply grants already sent in notifications are refused too (401 user_blocked on /v1/notifications/reply and /read). false unblocks them.

Response

200, for both a new and an existing user:

{
  "id": "8a7e2f9c-1234-4d5e-8f90-a1b2c3d4e5f6",
  "tenant_id": "65787825-3152-4c1e-9b8a-0d6f4e2a7c11",
  "external_id": "u_42",
  "display_name": "Ada",
  "avatar_url": null,
  "custom_data": {},
  "last_seen_at": null,
  "is_blocked": false,
  "inserted_at": "2026-06-02T08:14:00.123456Z",
  "updated_at": "2026-06-02T08:14:00.123456Z"
}
curl -X POST https://api.poolse.dev/v1/users \
  -H "Authorization: Bearer $POOLSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"u_42","display_name":"Ada"}'

Errors

CodeStatusWhen
validation_failed422external_id missing or empty, or a field too long. details names the field.

Plus the API-key errors listed on Authentication.


Mint a user token

POST/v1/users/:user_id/tokens
API keyusers:manage

Mints a 1-hour JWT for one user. Your app sends it as Authorization: Bearer <token> to the end-user endpoints and the realtime socket. Each call returns a new token. Earlier tokens keep working until they expire.

Path parameters

NameTypeDescription
user_iduuidThe user's poolse id from POST /v1/users. Not your external_id.

No request body.

Response

200:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
  "token_type": "bearer",
  "expires_in": 3600,
  "expires_at": "2026-06-02T09:14:00Z"
}
FieldTypeDescription
tokenstringThe JWT. Treat it as a credential.
token_typestringAlways "bearer".
expires_inintegerSeconds until it expires.
expires_atstringISO 8601 expiry time.
curl -X POST https://api.poolse.dev/v1/users/$POOLSE_USER_ID/tokens \
  -H "Authorization: Bearer $POOLSE_API_KEY"

Errors

CodeStatusWhen
forbidden403The API key lacks users:manage.
user_not_found404No user with this id in your tenant.
not_found404user_id is not a UUID, for example when you pass an external_id by mistake.
rate_limited429More than 60 mints in a minute with this API key. Wait for Retry-After seconds. Requests refused for a missing scope count too.

A token minted for a blocked user is refused on use with 401 user_blocked, and with 401 unauthorized on the call grant endpoints (answer, decline, hang up, reconnect, ring).


Revoke a user token

POST/admin/v1/tokens/revoke
API keytokens:admin

Revokes one JWT before it expires. Every later request with it gets 401 token_revoked. The user's other tokens keep working.

Revoking also closes all of the user's open realtime sockets, because a socket does not record which token opened it. Clients that hold another valid token reconnect with it. To cut off a user completely, set is_blocked: true with POST /v1/users instead.

Request body

FieldTypeRequiredDescription
tokenstringyesThe JWT to revoke. It must belong to your tenant and must not have expired.

Response

200:

{
  "jti": "2tq8f0r9b6m1k3p5s7u9w1y3",
  "expires_at": "2026-06-02T09:14:00Z"
}
FieldTypeDescription
jtistringThe token's id, now on the revocation list.
expires_atstringThe token's own expiry. The revocation lasts until then.
curl -X POST https://api.poolse.dev/admin/v1/tokens/revoke \
  -H "Authorization: Bearer $POOLSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"token\":\"$USER_JWT\"}"

Errors

CodeStatusWhen
missing_token400No token in the body.
invalid_token400The token does not verify, has already expired, or lacks a jti.
forbidden403The API key lacks tokens:admin.
cross_tenant403The token belongs to another tenant.
Users · API · poolse docs