Mint an anonymous Portal JWT
Mints an anonymous user JWT (claims.anon = true). Two ways to call it,
both returning the same token shape:
(a) Secret key — server-side. Send Authorization: Bearer sk_.... You
may set channels grants and a custom ttl. Browser requests are rejected
(a secret key must never reach a browser).
(b) Publishable key — browser-side, zero backend. Send
Authorization: Bearer pk_... or the x-portal-key: pk_... header. The
body may contain only anonId — channels and ttl are rejected with
validation_failed; the platform default TTL (one hour) and default grants
apply. This path is the only route on the control plane that is
browser-CORS-enabled, scoped to the environment's registered allowed
origins (loopback is always allowed). It is coarsely rate-limited per key.
An Origin that resolves to a real environment but isn't registered for it
gets 403 origin_not_allowed instead of a token (see the Forbidden
response).
anonId (optional, both paths) must match the anon_... id format
exactly. When present it is reused as the token subject, so an SDK can
re-mint on expiry for the same anonymous identity. When absent, a fresh
id is generated and returned as the token subject.
A browser preflight (
OPTIONS) never carries the credential's value (only the header NAME it intends to send), so the environment can't be resolved there — preflight is permissive instead (204+ ACAO for any Origin). Enforcement is entirely on the actualPOST, where the credential is present in a header (?key=pk_...also still works there, kept for compatibility, though it was never an auth path — only a CORS-resolution one).
Query Parameters
- Type: stringkey
Publishable key (
pk_...) — a CORS-resolution fallback on the actualPOST, checked alongside the header. Never used on the preflight (which is unconditionally permissive) and never an auth credential on its own:keywith no header still fails auth, even though CORS gating may still resolve the environment from it.
Body·
- Type: stringanon
Id Optional, both paths. Must match the
anon_...id format exactly. When present, it is reused as the token subject so an SDK can re-mint on expiry for the same anonymous identity. When absent, a fresh id is generated. - Type:channelsProperties: 1
Secret-key path only. Rejected on the publishable-key path.
- Type: stringttl
Secret-key path only. Rejected on the publishable-key path.
Responses
- application/json
- application/json
- application/json
- application/json
- application/json
curl https://api.useportal.co/v1/tokens/anonymous \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_SECRET_TOKEN' \
--data '{
"channels": {
"chat-general": [
"connect",
"publish"
]
},
"ttl": "30m",
"anonId": "anon_Vk8v2pBpHx7mJ9sXkFq3Qw"
}'
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImVudl8xMjMifQ.eyJzdWIiOiJhbm9uX1ZrOHYycEJwSHg3bUo5c1hrRnEzUXciLCJhbm9uIjp0cnVlLCJpYXQiOjE3NTI5MTIwMDAsImV4cCI6MTc1MjkxNTYwMH0.signature",
"expiresAt": "2026-07-19T03:00:00.000Z"
}