Both are secrets. Neither belongs in browser code, mobile apps, or logs.
REST API keys
Sending the key
Pass the key in thex-api-key header or in Authorization with the Bearer,
ApiKey, or Api-Key scheme:
fm_ are rejected. Only the key’s SHA-256 hash is
stored - FaceMode never keeps the plaintext key.
Creating a key
Create keys in the FaceMode console, or programmatically withPOST /api/keys (requires the keys:write scope):
Scopes
Each key carries a set of scopes. A call without the required scope fails with403 and code AUTH_INSUFFICIENT_SCOPE.
Default scopes for a new key:
sessions:create, sessions:read,
sessions:end, avatars:read, usage:read. Request additional scopes at key
creation; grant only what your backend needs.
Key errors
Session WebSocket tokens
POST /api/sessions (and /reconnect) returns ingestion.wsToken - a signed
JWT that authorizes exactly one WebSocket connection to the session’s ingestion
endpoint.
Connecting
Send the token as a WebSocket subprotocol with thefacemode. prefix:
Sec-WebSocket-Protocol: facemode.<wsToken>; the
server echoes the same subprotocol back on accept. Clients that cannot set
subprotocols (some browsers, embedded SDKs) may pass the same JWT as a query
parameter instead:
Only the
facemode. subprotocol prefix is accepted. Other prefixes are
rejected before the handshake completes.Token rules
- 15-minute expiry. The JWT is signed with
exp15 minutes from issue. - Single use. A successful handshake consumes the token. Never reconnect with a consumed token - it will be rejected.
- Fresh token on reconnect.
POST /api/sessions/:id/reconnectreturns a newingestion.wsTokeneach call. Reconnect as many times as needed. - Scoped to one session. A token only opens the WebSocket URL issued with it; it cannot be reused across sessions.
Security rules
Error envelope
Failed REST calls return a consistent shape:{"type": "error", "code": "...", "message": "...", "fatal": bool}. A fatal
error closes the connection; reconnect with a fresh wsToken.