Migrate a real-time avatar prototype to Sume async jobs: what changes

Moving from a live avatar session to Sume means replacing a stream with submit, poll and fetch. The code changes, the UX changes, and a Node example that runs.

4 min readSume
All posts

Moving a real-time avatar prototype to Sume means replacing a continuous session with three calls: submit a job, poll its status, and fetch the result. Sume avatars are async jobs: you submit a request, get a job id back, and read the finished video later. There is no live video session. The rest of your app changes less than you expect, but the experience for the viewer changes in one big way: they get a finished clip, not a conversation.

This post lists what you remove, what you add, and gives a Node program that runs the whole loop.

What you remove and what you add

Real-time prototype to Sume async jobs (Sume docs, read 2026-10-07)
In a live prototypeWith Sume Avatar 1.0
Open a session or socketPOST /v1/avatar-1.0/talking-video returns 202 with a job id
Stream audio in, video outSend a script or video_inputs; Sume makes the speech and the video
React to partial eventsPoll /v1/jobs/:id/status until terminal is true, or use a webhook
Show the streamRead /v1/jobs/:id/result and play the media.sume.com URL
Reconnect on failureRetry a submit with the same Idempotency-Key; never resubmit a paid job
Per-minute session costPer-second job cost, reserved at submit and refunded on failure

The loop in Node

The program below submits a talking video in async mode, honors next_poll_after_seconds, stops when terminal is true, and reads the result only when the job's sume_status is completed. It needs Node 18 or later for fetch, and your key in SUME_API_KEY. The Idempotency-Key means a retry after a network failure returns the original job.

const API = "https://api.sume.com";
const auth = { Authorization: `Bearer ${process.env.SUME_API_KEY}` };
const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));

async function main() {
  const body = {
    avatar_handle: "product_host",
    script: "Your order shipped today. Here is how to set it up in two minutes.",
    quality: "standard",
    mode: "async",
  };
  const res = await fetch(`${API}/v1/avatar-1.0/talking-video`, {
    method: "POST",
    headers: { ...auth, "Content-Type": "application/json", "Idempotency-Key": "ship-note-001" },
    body: JSON.stringify(body),
  });
  const job = await res.json();
  let s;
  do {
    await sleep(job.next_poll_after_seconds ?? 5);
    s = await (await fetch(`${API}/v1/jobs/${job.request_id}/status`, { headers: auth })).json();
  } while (!s.terminal);
  if (s.sume_status !== "completed") throw new Error(`job ended ${s.sume_status}`);
  const out = await fetch(`${API}/v1/jobs/${job.request_id}/result`, { headers: auth });
  console.log(JSON.stringify(await out.json(), null, 2));
}
main();

What changes in the product

Your loading state becomes a waiting state with real statuses. There is no way to speak to the avatar, so interaction moves to a form, a chat, or a human. The script becomes the review surface: because the video is built from text, you can read, approve or edit every word before you pay for the render, and you can first approve the stills with an Avatar video preview.

Cost moves from time spent in a session to seconds of finished video. A 30-second clip at plus is $7.35 no matter how many people watch it later.

What to keep from the prototype

Keep the script writing, the persona, the disclosure text and the analytics. Replace the transport. If your prototype included turn-taking, move that logic into the script, with one job per prepared reply. If the prototype's value was the conversation itself, async clips will not replace it, and you should use Sume for the prepared parts only.

Errors and retries

A network error on submit is the first thing to handle. Send an Idempotency-Key on every submit and reuse the same key and the same payload when you retry, and the retry returns the original job instead of billing a second one. A client-side timeout while polling is different: it does not cancel the job, which keeps running and billing, so your fix is to poll again with the stored job id.

A request can also be refused before a job exists. 402 insufficient_credits means Sume cannot reserve the estimated cost, so there is nothing to poll. A terminal failed job carries a public error, and the reservation is released or refunded where applicable, so read the error, fix the input, and submit a new request with a new key.

A migration order that works

  • Create the avatar once and store its handle.
  • Render one clip with the loop above and play the result in your own player.
  • Add the waiting state with real statuses.
  • Switch to webhook mode and keep polling as a backup.
  • Add captions and a synthetic-media label.
  • Only then retire the live prototype's transport code.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume