POST /api/sessions or POST /api/sessions/{id}/reconnect - the ingestion object carries url, wsToken, and ready.
Connecting
- URL:
ingestion.url, in the formwss://<worker-host>/ws/{session_id}. - Auth: offer the token as a WebSocket subprotocol -
Sec-WebSocket-Protocol: facemode.<wsToken>. The server echoes that subprotocol in the handshake. - Query fallback: clients that cannot set subprotocols may pass
?token=<wsToken>on the URL instead. - Compression: do not negotiate
permessage-deflate; it is disabled.
wsTokenis a signed credential with a 15 minute expiry and is single-use - the first successful connection consumes it.- Only one WebSocket may be active per session; a second concurrent connect is rejected.
- After it expires or is consumed, call reconnect for a fresh one.
Pre-upgrade failures
If the handshake is rejected, the socket is closed before any application traffic. The rejection carries an application close code - your WebSocket client surfaces it as a failed upgrade or an immediate close, depending on the library:Handshake
The first application message must bestart; the server answers started. Until started arrives, do not send audio, utterance control, or pings - anything else first fails with NOT_NEGOTIATED.
server_sample_rate/server_channels describe FaceMode’s internal format - informational only; keep sending audio in the format you negotiated.
Client -> server messages
All control messages are JSON text frames. PCM audio may also travel as raw binary frames.start field details
audio_encoding:pcm_s16le,pcm_f32le, orbase64_json.sample_rate: 8000-48000 Hz. Preferred values: 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000. Other in-range rates work but are resampled.channels:1or2.metadata: free-form object. Forbase64_json,metadata.inner_encoding: "pcm_f32le"opts into float PCM inside the base64 payload (default is 16-bit PCM).utterance_silence_ms:0disables silence-based utterance detection;100-5000tunes the gap. See below.
Server -> client messages
Sequence numbers
seqis a non-negative integer maintained by the client.- Sequences must not move backwards: every sequenced message (
audio,start_utterance,end_utterance,cancel_utterance,end_session) must haveseq >=the last one sent. Violations fail withINVALID_FORMAT. - Binary PCM frames carry no
seq. utterance_ended.seqechoes theend_utteranceseq it answers - use it to match requests to drains. In inferred mode (below), the seq is assigned by the server.
Utterance boundaries: inferred vs explicit
FaceMode needs to know where one utterance ends so it can flush speech to avatar video. Two modes:- Inferred (default): the first audio frame opens an utterance; a silence gap of
utterance_silence_ms(server default ~800 ms) closes it and producesutterance_ended. Best for simple pipelines - just stream audio. - Explicit: drive
start_utterance/end_utterance/cancel_utteranceyourself for tight control (for example, aligning with your own TTS events).
utterance_silence_ms in start to tune inferred mode: 0 turns inference off (you must then use explicit control), 100-5000 sets the silence gap in milliseconds.
Heartbeats and timeouts
- After
started, sendpingperiodically (for example every 15 s) and expectpong. One ping in flight at a time is enough. - The server closes idle sockets: prolonged silence with no frames ends the connection (close code
1000, reasonInactivity timeout). - The REST-level
POST /api/sessions/{id}/heartbeatis separate bookkeeping - use both.
Errors and close semantics
error frames carry {code, message, fatal}:
Fatal errors are followed by a server-initiated close. Close codes used after upgrade (for handshake-time rejections see Pre-upgrade failures above - note
4000/4001/4004 are reused with different meanings):
ended acknowledges end_session at the protocol level only - it does not prove session cleanup finished. To confirm the session is fully torn down, poll GET /api/sessions/{id} until session.status is ENDED, or call POST /api/sessions/{id}/end.
Reconnecting
When the socket dies mid-session:- Call
POST /api/sessions/{id}/reconnectfor a freshingestion.url+wsToken(old tokens cannot be reused). - Open the new WebSocket with the
facemode.<wsToken>subprotocol. - Re-send
start- the new connection performs a fresh handshake. If audio was already flowing,audio_readyfollowsstartedagain. - Resume your
seqcounter or restart at0- sequence state is per connection.