Before you start
You need four things:- A FaceMode account - sign-up is waitlist-gated during the private beta; join at console.facemode.io/signup. Approved testers get 3,600 free credits (60 minutes).
- A FaceMode API key (
fm_...) from the dashboard. See Authentication. - An avatar ID for the avatar that should speak.
- A room you own on LiveKit or Daily, plus a token that lets FaceMode
join and publish. For LiveKit, mint a token with
room_join,can_publish, andcan_subscribegrants on the room name.
Beta limits: sessions auto-end after 10 minutes, one concurrent session per
account, and a session needs at least 60 credits of remaining balance to
start. Credits are metered per second (1 credit = 1 second of streamed
avatar video) and never expire. Credit purchases are disabled during the beta -
top-ups for extra credits open at general availability.
1
Create a session
Call Request body
POST /api/sessions with the avatar and the room the avatar should
publish into.A
201 response returns the session plus ready-to-use ingestion
credentials:string
required
The session ID. You need it for
start, reconnect, status polling, and
ending the session.boolean
true when ingestion.url and ingestion.wsToken are usable
immediately - no polling required.string
The ingestion WebSocket URL for this session.
string
A one-time, 15-minute JWT used as the WebSocket subprotocol credential.
object
Optional extra headers to forward on the WebSocket handshake when present.
string
The room URL the avatar will publish to, echoed back at the top level.
string
The publisher room token you supplied, echoed back at the top level.
string
Avatar session lifecycle:
PENDING, ASSIGNING, ASSIGNED, ACTIVE,
ENDING, ENDED, or FAILED.Unknown or legacy fields are rejected.
waitForIngestion returns
WAIT_FOR_INGESTION_REMOVED, and byolr, livekitUrl, or
livekitToken return ROOM_LEGACY_FIELDS - always use the room
object.2
Connect the ingestion WebSocket
Open a WebSocket to Clients that cannot set a subprotocol may instead append the same JWT as a
query parameter:
ingestion.url and authenticate with the
Sec-WebSocket-Protocol subprotocol facemode.<wsToken>:wss://.../ws/<session_id>?token=<wsToken>.3
Send start and wait for started
The first message on the socket must be Allow up to ~240 seconds for the handshake plus
start. It negotiates the audio
format for the whole session.FaceMode replies:
start exchange on cold
starts - a fresh session may still be starting up when you connect. The official
plugins use a 240-second open budget.4
Stream audio
With Utterance boundaries. Two styles are supported - pick one per session:
pcm_s16le or pcm_f32le, send each audio chunk as a raw binary
WebSocket frame. With base64_json, send JSON audio messages instead:- Inferred (recommended): send
start, then PCM frames. FaceMode detects the end of each utterance after the silence gap (utterance_silence_ms, default 800 ms). You getaudio_readywhen speech starts rendering andutterance_endedafter the gap. - Explicit: wrap each utterance in
start_utterance{seq}…end_utterance{seq}, and usecancel_utterance{seq}for barge-in.end_utteranceis acknowledged byutterance_ended{seq, cycles}once the session drains.
seq) are non-negative integers that must not move
backwards. Control frames carry {"type": "...", "seq": n}.5
Join the room from your frontend
Pass the room URL to your frontend along with a separate viewer token
you mint for the end user (subscribe-only for LiveKit). The While no one is speaking, viewers see the idle animation; when your audio
arrives the avatar crossfades into lip-synced speech.
roomToken
returned by the API is the publish credential you supplied for FaceMode -
do not hand it to browsers.Browser
6
End the session
-
Send
end_sessionon the WebSocket and wait for theendedacknowledgement: -
Call
POST /api/sessions/:id/end:200means the session is finalized.202("pending": true) means session cleanup is still in progress - keep polling. -
Poll
GET /api/sessions/:iduntilsession.statusisENDED:
The
ended frame is a protocol acknowledgement, not confirmation
that backend cleanup finished. Treat the session as done only when the
REST status reaches ENDED.Minimal full-session example
A complete session in one file: create, connect, start, stream one PCM file, end, and poll until terminal.Timing budgets
These are guidance values, not hard limits:Reconnecting
If the WebSocket drops mid-session, do not reuse the old token. CallPOST /api/sessions/:id/reconnect (optionally with a replacement room) to
mint a fresh wsToken, then reconnect with facemode.<newToken> and resend
start.
Errors
REST errors use a consistent envelope:ROOM_INVALID, ROOM_LEGACY_FIELDS, REQUEST_FIELD_UNKNOWN,
WAIT_FOR_INGESTION_REMOVED, INPUT_PROVIDER_INVALID, AVATAR_NOT_FOUND,
SESSION_NOT_FOUND, SESSION_ALREADY_ENDED.
WebSocket errors arrive as error frames:
A
fatal: true error ends the connection; reconnect with a fresh wsToken.