Avatar video mode: sync waits 30 seconds, so use async or webhook
Sume's sync and subscribe modes wait at most 30 seconds. Avatar video usually takes longer. How to read the timed-out response and what to do next.
For an avatar video, send mode: "async" (the default) or mode: "webhook", not sync. A sync call on Sume blocks for at most 30 seconds, and the docs say avatar-video and face-swap jobs "routinely" do not finish inside that budget. When the budget runs out you still get a 2xx and a job id; the response then has sync.timed_out: true, and you must poll status_url. subscribe is an alias of sync: the same 30-second wait, not a stream.
The mode only decides how you find out. It never changes cost, whether a job is created or how long the render takes.
The four modes
What each returns for a talking-video submit.
| Mode | HTTP result | Blocks the server | Next step |
|---|---|---|---|
async (default) | 202 with job envelope and polling URLs | No | Poll status_url until terminal, then read result_url |
sync | Envelope after up to 30 s (wait_timeout_seconds 0 to 30) | Yes, at most 30 s | If not terminal, poll; do not resubmit |
subscribe | Identical to sync | Same | Same |
webhook | 202; callback stored | No | Verify the signature on the callback; keep polling as backup |
A polling loop that behaves
Honor next_poll_after_seconds when the response has it, and back off otherwise. Stop on completed, failed or canceled. Do not treat a local timeout as a failed job; the render may still be running and billing.
import asyncio, os
import httpx
BASE = "https://api.sume.com"
HEAD = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
async def wait(job_id: str, limit: int = 600) -> dict:
delay, waited = 5, 0
async with httpx.AsyncClient(headers=HEAD, timeout=20) as c:
while waited < limit:
r = (await c.get(f"{BASE}/v1/jobs/{job_id}/status")).json()["data"]
if r.get("terminal"):
return r
delay = r.get("next_poll_after_seconds") or min(delay * 2, 30)
await asyncio.sleep(delay)
waited += delay
raise TimeoutError(job_id)
asyncio.run(wait(os.environ["JOB_ID"]))When sync is fine
For avatar video, the rule of thumb in the docs is to submit async with an Idempotency-Key, store the four URLs, and either poll or take a webhook.
- Short image jobs that usually finish inside 30 seconds.
- Quick tests where you will poll anyway if it times out.
- A UI where you can show a spinner and then switch to polling.
Limits
There is no SSE or WebSocket transport on the Developer API today; GET /v1/jobs/:id/events is a pull snapshot. If the API process has no spare waiter capacity it skips the blocking wait entirely and sets sync.capacity_exhausted, so your code has to handle an immediate non-terminal response even when you asked for a wait. In practice that means writing the polling path first and treating a finished sync response as a shortcut. A client built that way works the same whether the render took 20 seconds, four minutes, or hit a busy moment on the API. Log the job id on the first line of your handler, before you look at anything else in the response, so a crash after submit never leaves you with a paid job you cannot find.
Sources
Related posts
More in Developers
- Can polling Sume job status hit the rate limit? Read budget math
Reads and writes have separate Sume budgets. Worked numbers for polling every accepted job per plan, plus a delay function that reads ratelimit headers.
- Cancel queued Sume jobs on SIGTERM during a deploy
On shutdown, cancel jobs you no longer need before they start and leave started ones alone. A 21-line Node handler using POST /v1/jobs/{id}/cancel.
- Check a video request against /v1/videos/models before you submit
Duration, resolution, size and seed errors cost a round trip. A short Python validator reads the model catalog and refuses a bad request locally first.
- Check a transparent GPT Image 2.5 PNG for real alpha in Python
A transparent GPT Image 2.5 result can still look opaque. Ask for background transparent as PNG, then check the alpha channel in Python: a 20-line script.
Written by Sume