Secure realtime authentication for browser and mobile apps
Use client tokens for browser and mobile realtime connections.Client tokens are short-lived keys you create on your backend, then pass to your frontend for client.realtime.connect().
Never expose your permanent API key (dct_...) in client-side code. Use client tokens (ek_...) for all browser and mobile realtime sessions.
All options are optional. Without options, tokens use a 60-second TTL and are unrestricted.
Parameter
Type
Description
expiresIn
number
TTL in seconds (1–3600, default 60)
allowedModels
string[]
Restrict which models the key can access (max 20)
allowedOrigins
string[]
Restrict which web origins can use the key (max 20, see Origin scoping)
constraints
object
Operational limits (see below)
metadata
object
Custom key-value pairs to attach to the token
Constraints object:
{ "realtime": { "maxSessionDuration": 120 // max seconds per WebSocket session (min 10) }}
expiresIn vs maxSessionDuration — these control different things.
expiresIn sets how long the token can be used to start new connections. Once a realtime session is established, the token’s expiration does not terminate it.
maxSessionDuration caps how long an individual realtime session can remain active, regardless of token expiration.
Use both together for full control: e.g. a 5-minute token window with a 2-minute max per session.
Pass allowedModels to restrict which models a token can be used with. The bouncer verifies model permissions when the client connects — if the model isn’t in the allowed list, the connection is rejected.Tokens created without allowedModels are unrestricted and work with any model.
Pass allowedOrigins to pin a token to a specific list of web origins. When the token is later used to open a realtime session, the connection is accepted only if the browser-issued WebSocket Origin header matches one of the listed origins. Mismatched or missing origins are rejected with close code 1008 and {"type":"error","error":"Origin not allowed"}.Each entry must be a canonical origin so it compares byte-for-byte to what browsers send. The mint endpoint enforces this and returns a 400 (with the canonical form in the message) when input doesn’t match:
scheme http:// or https://
lowercase scheme and host
no trailing slash, path, query, or fragment
no userinfo (credentials)
no default port (:443 for https, :80 for http)
max 253 chars per entry, max 20 entries per token
Examples:
Input
Outcome
https://app.example.com
accepted
http://localhost:3000
accepted (non-default port preserved)
https://app.example.com/
rejected — trailing slash
https://app.example.com:443
rejected — default port
https://EXAMPLE.com
rejected — mixed case
https://user@example.com
rejected — credentials
example.com
rejected — missing scheme
Tokens created without allowedOrigins are unrestricted (no origin enforcement).
Defense-in-depth, not hermetic. Origin enforcement is browser-driven: it materially raises the cost of stolen-token replay from a different web origin, but does not protect against an attacker who controls a non-browser HTTP client and can spoof the Origin header. Treat it as one layer alongside short TTLs and model scoping, not a sole boundary.