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.
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
| In a live prototype | With Sume Avatar 1.0 |
|---|---|
| Open a session or socket | POST /v1/avatar-1.0/talking-video returns 202 with a job id |
| Stream audio in, video out | Send a script or video_inputs; Sume makes the speech and the video |
| React to partial events | Poll /v1/jobs/:id/status until terminal is true, or use a webhook |
| Show the stream | Read /v1/jobs/:id/result and play the media.sume.com URL |
| Reconnect on failure | Retry a submit with the same Idempotency-Key; never resubmit a paid job |
| Per-minute session cost | Per-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
- Model an AI generation job as a state machine in your database
A schema and update rule for tracking Sume jobs: five statuses, sticky terminal states, a separate webhook delivery column, and the idempotency key on the row.
- Pin the model id in an ad test: sume/auto follows the catalog
sume/auto is a pure function of the request plus the catalog version, so two ad arms made weeks apart can land on different models. Pin an explicit id in tests.
- Poll hundreds of AI jobs without a thundering herd: jitter and budgets
Poll many Sume jobs without synchronized bursts: jitter, next_poll_after_seconds, per-plan read budgets, and the math on how much polling a plan can absorb.
- Portuguese speech to text API: Sume STT language_code pt or pt-BR
Transcribe Portuguese audio with Sume STT using language_code pt or pt-BR, then check the reported language and word times. $0.01 per audio minute.
Written by Sume