Skip to main content

AI video avatar agents

An avatar persona is a tenant-managed binding that holds two references: one of your conversational agents (the dialog brain) and one rendered talking-head provider persona reference — the avatar id you hold at your chosen provider. The persona can then either join a live video room as a digital host, or render a personalized video clip asynchronously from a script template with {{merge_tag}} tokens and a per-recipient merge-field map. Reach for a persona when you want the same conversational brain to show up visually — a concierge onboarding host, a policy-recap clip, a personalized campaign send — rather than in a voice or messaging-only channel. This guide walks the whole path from creating the persona to verifying a finished clip.

What you build

One persona registry entry gives you three things:
  • A persona binding — one registry entry (vaa_-prefixed) tying one of your conversational agents to one provider persona (for example a Tavus replica), with an optional opening greeting and locale.
  • One live-room digital host — dispatch the persona into an existing video room; the avatar-runtime joins it as a bot participant that renders the talking head driven by the bound agent’s turns.
  • One async personalized clip render — queue a script plus merge fields and get back a tracked job (vavr_-prefixed) that ends in completed with an output_url you can attach anywhere, plus a verifiable content credential.
All of it is configuration plus guarded API calls: Orbit never holds your avatar provider credentials, and nothing here originates an outbound call or message — the avatar streams into a self-hosted WebRTC room, and a completed clip attaches to your existing send pipeline.

Prerequisites

Have these in hand before you start:
  1. A conversational agent id (agt_-prefixed). The avatar is a rendered front-end for that agent’s dialog, so you need the agent first — build one in Agents → Studio in the dashboard, or follow Author an AI Agent from a Prompt.
  2. A provider persona reference — the avatar id you hold at your chosen provider — Tavus, HeyGen, or D-ID (for example a Tavus replica id). Provision the provider account with that provider; Orbit stores only the opaque reference id, never provider credentials.
  3. An API key with the video scope — reads need video:read; writes (register a persona, script a render, report its status) need video:write plus an owner, admin, or developer role.
  4. Optionally, a scheduled video room — the room the persona joins as a digital host. Any existing room name works; scheduled rooms give the dispatch a stable target.

Step 1 — Register the persona

POST the binding under /api/v1/video/avatar-agents:
On 201 the response carries the persona with a vaa_-prefixed id:
The registry is capped at 50 personas per tenant; a duplicate provider_persona_ref is rejected, so keep the full CRUD surface in reach:
A persona that must not be dispatched can stay in the registry: set "enabled": false to retain it without letting it join rooms or render clips.

Step 2 — Dispatch a join plan into an existing room

When a persona is dispatched into a video room, the avatar-runtime requests a join plan and joins the room as a bot participant that renders the talking-head video driven by the bound agent’s turns:
The response returns the dispatch descriptor the avatar-runtime consumes — the room name, the bound conversational agent id, the avatar provider and its persona reference, a deterministic bot identity, and the persona’s opening greeting:
Orbit’s worker fleet consumes a dedicated dispatch queue to carry that plan to a real join — minting through the same join-gate seam a human join uses. How that loop works, and why re-running Build join plan collapses onto one deterministic job, is the Avatar bot dispatch queue concept page. The room it joins is the standard moderated, in-browser WebRTC room Orbit’s Video API ships — the digital host lists in the participant roster, moderator controls still apply, and the session’s recording and summary land on the contact record.

Step 3 — Confirm the bot joined the roster

Do not treat a 200 on the join plan as a successful join. Read the room’s live participant roster and check the bot is on it:
The room detail returns a participants array; look for the join plan’s bot_identity with is_publishing: true, meaning the talking-head video is live in the room:
Until the bot appears, the dispatch is still in flight (or was dropped) — see the failure-handling section below.

Step 4 — Queue a render job

The async counterpart to the live room is a render job posted to /api/v1/video/avatar-video-renders:
The script template accepts up to 5000 characters and any {{merge_tag}} tokens — bare token names with no braces on the merge-field keys. A merge_fields map can supply up to 20 keys, each key up to 64 characters, each value up to 500 characters. Every token the template references must have a supplied value, or the request is rejected with a 400 naming the missing tokens. On 202 the request is persisted as a queued job with a vavr_-prefixed id, which your provider integration or render runtime consumes (Orbit never calls your provider’s render API directly — same “no provider credentials stored” posture as the join plan):

Step 5 — Track the job to completed

Report progress back via PATCH /video/avatar-video-renders/:id, and poll with GET /video/avatar-video-renders/:id:
  • rendering — mark the job in-flight.
  • completed — supply the finished clip’s output_url and either the raw clip (video_base64, hashed server-side) or a pre-computed SHA-256 digest plus byte length, so Orbit mints the content credential without storing the clip itself.
  • failed — with an error message preserved on the job.
Only queued → rendering → completed / failed transitions are allowed; a job already in completed or failed is terminal and rejects further updates with a 409. Once completed, the clip’s output_url is a plain HTTPS link and the render record carries its content credential.

Step 6 — Attach the clip to a campaign

Attach the completed clip’s output_url to a campaign send or a 1:1 MMS/WhatsApp message anywhere the existing content pipeline accepts a generic media URL. The content credential persists on the render record and travels with the clip, so the synthetic-media disclosure survives the attachment.

Verification

Three checks confirm the pipeline is healthy:
  1. Roster check for a live join — GET /video/rooms/:name must list the bot identity from the join plan in participants (Step 3).
  2. Job-state check for a render — GET /video/avatar-video-renders/:id returns the full render record. A healthy completed render has status: "completed", a non-null output_url, a non-null completed_at, and a populated content_credential (token + manifest). List jobs with GET /video/avatar-video-renders, optionally filtered by ?status= or ?persona_id=.
  3. Credential check for a finished clip — confirm a render’s content-credential token via the verify endpoint:
Passing only the token validates it. Optionally also pass the clip — either as base64 or a SHA-256 digest — and the verify response bind-checks it: a valid token re-stapled onto a different clip reports a video_mismatch, not a false pass. The verify surface is read-scoped and stateless, so a campaign composer or an external reviewer can re-check a clip without write access.

Failure handling

  • Render reaches failed — the job preserves the error string the render runtime reported (up to 500 characters). Read it from GET /video/avatar-video-renders/:id; the job is terminal, so re-queue a fresh render rather than re-using the failed id.
  • completed without a clip payload is rejected — the API requires either video_base64 or both video_sha256 + video_bytes on the completed transition, because every completed clip must carry a minted content credential. A 400 on the transition names what was missing.
  • Join plan returns 409 on a disabled persona — re-enable with PATCH /video/avatar-agents/:id and dispatch again.
  • Bot never reaches the roster — the dispatch queue retries must eventually mint a join through the same gate humans use; if the room detail never shows the bot identity, re-issue the join plan. Re-running it collapses onto one deterministic dispatch job rather than duplicate bots.
  • Pre-minted integrations — if your render runtime re-scopes credentials, re-mint only the integration’s own token; the persona’s provider reference and Orbit’s join-plan/render bookkeeping never hold provider secrets, so nothing in Orbit needs re-issuing.

Worked example — concierge welcome clip

End to end, from registry to verified clip:
When verify reports the token valid and bound, attach the output_url to your campaign or 1:1 send.

Where to go next