Skip to main content
A realtime session adds a face to a voice agent. The conversation runs in a LiveKit room, as a voice agent’s does today; Boson adds an avatar participant to that room that renders lip-synced video, live, for the speech your agent produces. This page builds the whole thing from zero: a LiveKit room, one Python script that holds a Higgs Realtime conversation and drives the avatar, and a browser page where you talk to it and watch it answer.
Model
Sessions endpoint
This is the live counterpart of POST /v1/videos, with the same higgs-avatar model. Video jobs render a finished clip from a still image and an audio file; a realtime session renders continuously, in a room, from audio that arrives as the agent speaks. The avatar does not listen or talk by itself: your voice agent does, and the avatar lip-syncs to its output.
You need a Boson API key (BOSON_API_KEY) and a LiveKit Cloud project with its URL, API key and secret (LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET); nothing here runs without both. Boson does not provide the room, and self-hosted LiveKit is not supported. See Before you begin.

How it works

Four participants, three of them yours:
  1. The user joins the room from a browser and publishes a microphone track. They subscribe to the avatar’s video and audio like any other participant’s.
  2. Your agent (agent.py below) joins the same room as a participant. It forwards the user’s microphone audio to Higgs Realtime, and sends the model’s spoken reply to the avatar over a LiveKit data stream (the LiveKit Agents avatar audio protocol: 24 kHz 16-bit PCM, plus a “playback finished” callback the avatar sends back). The agent never publishes audio itself.
  3. Higgs Realtime holds the conversation over one WebSocket: it listens, detects turns, and answers in speech. Same API as the Higgs Realtime quickstart.
  4. The avatar is the participant Boson adds to your room, under the identity named in your request. It renders the speech it receives into lip-synced video and publishes video and audio tracks, in sync. It is in the room when the create call returns, and leaves when you release the session, when max_duration_seconds elapses, or when everyone else has left.
The avatar’s audio and video stay inside your LiveKit room; the /v1/avatar/sessions routes never carry media.
A LiveKit Agents plugin that does the avatar part of this for you (steps 1 and 2 of agent.py) is on its way. This page builds the pieces by hand so you see how they fit; everything here keeps working with the plugin.

Before you begin

Boson. A Boson API key in BOSON_API_KEY, with available credit (new accounts claim their free trial credit first). One key covers both the avatar session and the Higgs Realtime conversation. LiveKit. A LiveKit Cloud project (the free tier is enough). The avatar joins LiveKit Cloud project URLs (wss://<your-project>.livekit.cloud); a self-hosted LiveKit server is not supported, and a session pointing at one is refused with 422 invalid_livekit_url. In the Cloud dashboard, create a project, then open its Settings → Keys and add a key (Keys and tokens in LiveKit’s docs). The project URL is shown at the top of the dashboard. Those are the three values every script below reads; rooms need no setup, a room exists as soon as the first participant joins it:
Install the LiveKit CLI too; it mints the browser’s token in one command and reads the same three variables:
Python 3.10+ with the LiveKit SDKs (the livekit-agents package brings the room client, the token helper and the avatar audio protocol; this page was written against 1.8), plus a WebSocket client:
An avatar. List the avatars your key can use with GET /v1/avatars; Boson presets such as builtin_claire_v1, builtin_emma_v1 and builtin_james_v1 are available to every key:

Step 1: the agent

Save this as agent.py. It does four things, in order: puts the avatar in the room, joins the room itself, runs the conversation with Higgs Realtime while streaming the replies to the avatar, and releases the avatar when you stop it.
agent.py
Run it:
The avatar is now in the room avatar-demo, waiting for someone to talk to. A few things worth knowing about what the script does:
  • Two tokens, both minted by you. The avatar’s token (identity boson-avatar) lets the avatar join your room and publish camera and microphone tracks; it never needs to subscribe. The agent’s token lets this script hear the user. Boson never sees your LiveKit API secret, only the avatar’s token, and does not inspect it: a token with the wrong identity or grants fails when the avatar tries to join.
  • publisher_identity is the agent. The avatar only accepts audio from that participant, over a data stream. DataStreamAudioOutput from livekit-agents speaks that protocol: capture_frame sends PCM, flush marks the end of an utterance, clear_buffer tells the avatar to drop what it has not played yet (used when the user interrupts), and the avatar reports playback_finished when it has played everything. The model produces speech faster than real time, so that event, not response.done, is what says the avatar has stopped talking.
  • One user per session. The script forwards every non-avatar participant’s microphone to the same conversation. For a demo that is one person; a product gives each user their own room, agent and session.
  • The conversation is plain Higgs Realtime. The WebSocket, session.update, input_audio_buffer.append and response.output_audio.delta are exactly the quickstart; the only difference is where the audio comes from (the user’s LiveKit track) and goes to (the avatar instead of a speaker).
  • Ctrl-C releases the session. The finally block sends the DELETE. If your process dies without it, Boson still ends the session at max_duration_seconds or once the room is empty, but a forgotten session keeps its capacity slot until then. The demo starts the avatar before anyone is in the room; in production, create the session when the user joins.

Step 2: talk to it

The user needs a token for the room and a page that joins it. Mint the token with the LiveKit CLI (it reads the LIVEKIT_* variables you exported):
Save this as viewer.html. It is the whole client: a Join button and a video element for the avatar:
viewer.html
Open it in a browser with two query parameters. url is the LIVEKIT_URL you exported above, wss:// prefix included, and token is the string lk token create just printed:
Click Join. The page connects, turns on your microphone, and attaches the avatar’s video and audio as soon as they arrive. A token in the URL is fine for a demo; a product hands it to the page from your server. Say hello. Within a second or two the avatar answers, lips moving with the words, and agent.py prints the transcript of what it said. Interrupt it mid-sentence and it stops. That is the whole loop: everything else on this page is reference.
The browser only ever holds a LiveKit room token, which is what LiveKit is designed to hand to clients. Your Boson API key and LiveKit API secret stay in agent.py on your server.
The token expires after the --valid-for you gave it (an hour above). If the page stops connecting later, rerun the lk token create command and open the page again with the new token.

The session object

POST /v1/avatar/sessions returns it with 201; GET and DELETE /v1/avatar/sessions/{session_id} return the current state:
Boson ends every session by itself, even if your DELETE never arrives: at expires_at, or once every other participant has left the room. Release sessions anyway: a forgotten session keeps its slot until it expires.

Capacity and retries

Avatar capacity is limited, and each session occupies one slot for its whole duration. When every slot is in use, POST /v1/avatar/sessions returns 429 with code: "capacity_exhausted" and a Retry-After header (seconds). Retry after that delay; a slot frees as soon as another session ends. Start the avatar only when a user is actually present, keep max_duration_seconds close to your real call length, and release promptly. 503 responses with code: "service_unavailable" are transient and also carry Retry-After. A 429 with type: rate_limit_error is the ordinary per-key rate limit: back off and retry. A 429 with type: insufficient_quota is a billing refusal and does not recover by retrying; see Common error types. Creation is not idempotent. If the create call times out, check the room for the avatar participant before retrying, and release any duplicate session. Sessions run up to max_duration_seconds (3600 s by default, per account); output picks the video size (default 480x640). Every request field, limit and error code is in the Avatar Realtime API reference.

It didn’t work?

Every avatar slot is in use, whatever avatar_id you ask for. Wait Retry-After seconds and retry. A session you forgot to release still counts against capacity until max_duration_seconds elapses or the room empties; release it with DELETE.
The avatar only joins LiveKit Cloud projects. Set LIVEKIT_URL to the project URL exactly as the Cloud dashboard shows it, wss://<your-project>.livekit.cloud, with no port, path or query. A self-hosted LiveKit server is refused.
The avatar’s token was refused by LiveKit. Check that it was minted with the same LIVEKIT_API_KEY/SECRET as the project in LIVEKIT_URL, for identity participant_identity, with room_join, can_publish and can_publish_data, and a TTL longer than the session. Boson does not validate the token; GET the session a few seconds later and you will see it terminated.
The avatar only accepts audio from publisher_identity. Make sure the identity the agent connects with is the one you named in the create request, and that you send the audio with DataStreamAudioOutput (not by publishing a microphone track).
The agent hears nothing. In the browser, allow the microphone and check that setMicrophoneEnabled(true) ran after connect. In agent.py, the track_subscribed handler must see the user’s audio track: the agent’s token needs can_subscribe. With server_vad the model answers on its own when you stop speaking; a realtime error line in the agent’s output means the WebSocket side refused something (an invalid key closes with code 3000, no credit gives insufficient_quota).

Next steps

Avatar Realtime API reference

Routes, the session object, transport fields and error codes.

Higgs Realtime

Voices, turn detection, interruptions and tool calling for the conversation behind the avatar.