Interactive Avatar: LiveKit Plugin reference

Complete reference for livekit-plugins-synthesia — every parameter, method, event, and exception. For the mental model behind these objects, see Concepts; for runnable code, see the Quickstarts.

All symbols live in livekit.plugins.synthesia.

Requirements

  • Python 3.10 or later. Tested on 3.10, 3.11 and 3.12.
  • livekit-agents >= 1.8.1 — the plugin's only dependency, installed automatically.

Install with pip:

pip install livekit-plugins-synthesia

Environment variables

Read when the corresponding argument isn't passed explicitly.

VariableUsed for
SYNTHESIA_API_KEYYour Synthesia workspace API key
SYNTHESIA_API_URLAPI base URL. Defaults to https://developers.synthesia.io
LIVEKIT_URLYour LiveKit project URL
LIVEKIT_API_KEYLiveKit API key
LIVEKIT_API_SECRETLiveKit API secret

All five are secrets and belong in your agent's environment or a secret manager. SYNTHESIA_API_KEY is workspace-bound and must never reach frontend code.

LIVEKIT_URL accepts https:// as well as wss:// — the scheme is normalised before it reaches the avatar worker, so the https:// value LiveKit Cloud injects into deployed agents works unchanged.

Minimal usage

from livekit.plugins import synthesia

avatar = synthesia.AvatarSession(
    synthesia.AvatarConfig(avatar_ids=["<avatar-id>"]),
)
await avatar.start(session, room=ctx.room)   # before session.start()

The plugin replaces session.output.audio, so the avatar lip-syncs whatever speech your agent produces. You can swap STT, LLM or TTS providers freely and these two lines never change.

synthesia.AvatarSession

AvatarSession(
    avatar_config,
    *,
    api_key=None,
    api_url=None,
    join_timeout=30.0,
    avatar_participant_identity=None,
    avatar_participant_name=None,
)
ParameterTypeDefaultDescription
avatar_configAvatarConfigrequiredThe avatars to render. See below.
api_keystr | NoneNoneSynthesia workspace API key. Falls back to SYNTHESIA_API_KEY.
api_urlstr | NoneNoneAPI base URL. Falls back to SYNTHESIA_API_URL, then https://developers.synthesia.io.
join_timeoutfloat30.0Seconds to wait for the avatar to join and publish before raising SynthesiaTimeoutError. Raise it if the worker cold-starts slowly.
avatar_participant_identitystr | None"synthesia-avatar-agent"The LiveKit identity the avatar joins under. Must be unique per concurrent avatar in a room — LiveKit evicts an existing participant when a second joins with the same identity.
avatar_participant_namestr | None"Synthesia avatar"The LiveKit display name the avatar joins under.

Passing an empty or whitespace-only string for either participant field raises SynthesiaError. Omit them to take the defaults.

Properties

PropertyDescription
avatar_identityThe LiveKit identity the avatar joins under.
providerAlways "synthesia".

synthesia.AvatarConfig

AvatarConfig(avatar_ids)
FieldTypeDescription
avatar_idsSequence[str]One to five ids of avatars available to your workspace, each prefixed av_…. The first is the active avatar; the rest are precomputed so swap_avatar() can switch to them mid-session.

avatar_ids is validated when you construct the config, not when the session starts:

  • A bare string instead of a list raises SynthesiaError.
  • Fewer than one or more than five ids raises SynthesiaError.

An id your workspace can't access raises UnknownAvatarError later, at start().

Pass every avatar you might swap to up front. Adding one afterwards requires a new session.

Methods

await avatar.start(agent_session, room, *, livekit_url=None, livekit_api_key=None, livekit_api_secret=None)

Mounts the avatar into room, launches the worker, and wires session.output.audio. Returns once the avatar has joined and published its video track.

Call this before AgentSession.start(). LiveKit credentials fall back to LIVEKIT_URL, LIVEKIT_API_KEY and LIVEKIT_API_SECRET.

The room must already be connected — the plugin mints the avatar's token on behalf of your agent's identity, so it needs one. Connect the room, then attach the avatar.

Calling start() on an already-started session returns immediately and does nothing. Calling it while another start() is still running raises SynthesiaError.

await avatar.swap_avatar(avatar_id, *, timeout=15.0)

Switches the rendered avatar mid-session to another id from avatar_ids. Pass "default" to return to the first id. Returns the now-active avatar id once the swap has taken effect.

await avatar.swap_avatar("<another-id-from-avatar-ids>")
await avatar.swap_avatar("default")

Raises SynthesiaError if the session hasn't started or is shutting down, UnknownAvatarError if the target wasn't in avatar_ids, and SynthesiaConnectionError if the worker can't be reached.

await avatar.aclose()

Cooperative shutdown; restores normal audio routing. Called automatically when the room disconnects, or when the avatar drops unexpectedly.

Events

EventFires when
session_endedThe room disconnected. A clean end.
errorThe avatar stopped publishing video while the room was still connected — a worker failure. Carries a SynthesiaConnectionError.
avatar.on("session_ended", lambda: ...)
avatar.on("error", lambda exc: ...)

The distinction is about who ended the session, not how bad it was: session_ended means you or your user ended it, error means the avatar went away on its own. Either one triggers shutdown automatically — you don't need to call aclose() in the handler.

Exceptions

Every exception subclasses SynthesiaError, which subclasses LiveKit's own livekit.agents.APIError. So you can catch Synthesia failures alongside every other plugin's, and every exception carries:

AttributeDescription
retryableWhether retrying the same call could plausibly succeed. Each class sets a sensible default you can override per call.
statusThe HTTP status the API answered, or None if no answer arrived.
request_idThe API's requestId, when the response carried one. Quote it when contacting support.
bodyThe raw response body, when there was one.
ExceptionMeaningRetryable
SynthesiaErrorBase class. Also raised for missing credentials and invalid configuration.No
SynthesiaAuthErrorThe API key is invalid, expired, or lacks the scope this endpoint requires.No
FeatureNotInPlanErrorYour workspace's plan doesn't include interactive avatars.No
InvalidRoomTokenErrorThe room token can't produce a joined session — malformed, or missing the attribute naming the agent the avatar publishes for. Distinct from SynthesiaAuthError: your Synthesia key is fine.No
LiveKitCredentialsRejectedErrorThe token is well formed but the LiveKit project it was signed for refused it. Check the LiveKit key and secret, not the Synthesia key.No
InvalidSessionRequestErrorThe backend rejected the session request payload.No
UnknownAvatarErrorAn avatar isn't in the gallery, or isn't accessible to your workspace. Also raised by swap_avatar() for a target that wasn't in avatar_ids.No
QuotaExceededErrorYour workspace's session quota is exhausted.No
RateLimitedErrorThrottled. Carries retry_after in seconds when the backend supplied one.Yes
ConcurrencyLimitErrorEvery concurrent-session slot for your plan is in use. A subclass of RateLimitedError, so except RateLimitedError catches both. The API sends no Retry-After for this today, so retry_after is normally None.Yes
SynthesiaTimeoutErrorThe avatar didn't join within join_timeout.Yes
SynthesiaConnectionErrorNo usable response — the request either failed to connect or returned a 5xx. Also raised when the avatar drops mid-session.Yes

Retry guidance

Retry only the four retryable classes. Honour retry_after on RateLimitedError. For SynthesiaTimeoutError, raise join_timeout before retrying — cold starts are the usual cause.

Starting a session does not retry internally. A transient server error surfaces immediately as SynthesiaConnectionError, so retrying is yours to do.

Never wrap auth, plan, quota, unknown-avatar, token or validation errors in a retry loop. They won't resolve on their own; surface them with the fix.

For the HTTP-level error codes behind these exceptions, see Errors.

Troubleshooting

📘

Prefer automated help?

The Claude Code / Cursor skill can scan your codebase and suggest fixes directly.

SymptomLikely causeFix
SynthesiaError: a Synthesia API key is requiredNo SYNTHESIA_API_KEY and no api_key=.Set the env var or pass api_key=.
SynthesiaError: LiveKit url, API key, and API secret are requiredOne of the three is missing or blank at start().Set the env vars or pass them to start(). A blank secret is caught here deliberately: it still mints a token Synthesia accepts, and LiveKit would only reject it much later.
SynthesiaError: avatar_ids must be a list of ids, not a single stringAvatarConfig(avatar_ids="<id>").Wrap it in a list: avatar_ids=["<id>"].
SynthesiaError: avatar_ids must contain between 1 and 5 idsAn empty list, or more than five.Pass one to five ids.
SynthesiaError: livekit_url … is not a ws:// or wss:// URLA malformed LIVEKIT_URL.Use your project's wss:// URL. https:// is normalised automatically.
SynthesiaError: the room's local participant has no identitystart() ran before the room finished connecting.Connect the room first, then attach the avatar.
SynthesiaError: start() is already in progressTwo concurrent start() calls.Await the first one.
SynthesiaAuthErrorKey invalid or expired, or missing the scope this endpoint requires.Check the key and its scopes.
FeatureNotInPlanErrorYour plan doesn't include interactive avatars.Retrying won't help — contact Synthesia.
UnknownAvatarError at start()An avatar id isn't accessible to your workspace.Use an id your workspace has access to.
UnknownAvatarError on swap_avatar()The target id wasn't passed to AvatarConfig.Include every swappable id — up to five — in avatar_ids up front.
SynthesiaError: swap_avatar() requires a started avatar sessionCalled before start(), or during shutdown.Only swap while the session is live.
ConcurrencyLimitErrorAll concurrent session slots are in use.End an active session, or wait for one to end.
SynthesiaTimeoutErrorThe avatar didn't join in time — cold start or network.Raise join_timeout and retry.
Avatar never appears, no errorTerminal console run mode uses a mock room, or start() ran after session.start().Run with dev or connect against a real room, and attach the avatar first. See Testing your integration.
Avatar joins but doesn't lip-syncThe agent isn't producing audio, or session.output.audio was reassigned after start().Confirm the agent speaks without the avatar attached; never reassign output.audio after attaching.
Avatar video looks low-res or blurrySubscriber-side adaptive streaming downscaled the track.Create the room with adaptiveStream: false, or render the avatar large and call setVideoQuality(VideoQuality.HIGH). Resolution is set by the hosted worker — there's no plugin-side control.
Works locally, fails when deployedSecrets missing from the deploy environment.Confirm all five environment variables exist in the runtime.
SynthesiaTimeoutError on most sessions, not just cold startsTypical join time has been running ~45 seconds against a 30-second default join_timeout.Raise join_timeout well above the default until this improves.
Session drops and the agent doesn't recoverThe plugin has no built-in reconnect.Handle the error event yourself and decide whether to retry — don't assume automatic recovery.
A room refuses a new session, or the avatar won't (re)join a room it was just inA known issue can leave a closed session active in LiveKit, blocking a new one in the same room.Check the LiveKit dashboard; close the stale room manually, then retry.

Choosing an avatar

Synthetic and personal avatars only. Stock actor-based avatars (for example Ryan or Ada) can't be used as interactive avatars — this is a category restriction, not a plan or access issue.

For an eligible avatar your plan gives you access to:

  1. Copy its ID from the Avatars page: open the ••• menu and select Copy ID.
  2. Convert it with POST /api/interactive-avatars/avatars. Poll until status is completed, then pass the returned id in avatar_ids.

A repeat request for the same source returns the existing interactive avatar rather than starting a new conversion. Converting a stock avatar, or one outside your plan, returns an error rather than an interactive avatar.