Avatar submit response: what next_action tells your client to do

next_action has three values on avatar submits: poll_status while queued or processing, fetch_result once completed, inspect_events for failed or canceled jobs.

4 min readSume
All posts

On an avatar submit or status response, next_action has exactly three values: poll_status while the job is queued or processing, fetch_result once it has completed, and inspect_events when it failed or was canceled. Drive your client from that field and from terminal and result_ready, and you do not need your own table of job states.

The fields to read

The submit response for POST /v1/avatar-1.0/talking-video carries avatar_video_id, status_url, result_url, events_url, cancel_url, cancelable, next_action, next_poll_after_seconds, recommended_poll_interval_seconds, result_ready, terminal, and idempotency_hit. Use the URLs as given and do not rebuild them from ids.

Avatar job states and the client action (Sume OpenAPI and jobs docs, read 2026-10-05)
Job statusnext_actionterminalClient action
queuedpoll_statusfalseWait next_poll_after_seconds, GET status_url
processingpoll_statusfalseKeep polling
completedfetch_resulttrueGET result_url when result_ready is true
failedinspect_eventstrueGET events_url, fix the input, submit once
canceledinspect_eventstrueStop, or submit a new request deliberately

A minimal loop

Obey next_poll_after_seconds when it is present and use a backoff when it is not. Cap the loop with a deadline in your own client; a client timeout does not cancel the job, so a deadline only stops the waiting.

import json, os, time, urllib.request

def get(url):
    req = urllib.request.Request(url, headers={
        "Authorization": "Bearer " + os.environ["SUME_API_KEY"],
        "User-Agent": "loop/1.0"})
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)["data"]

def follow(status_url, deadline=1200):
    end, wait = time.time() + deadline, 5
    while time.time() < end:
        d = get(status_url)
        if d["next_action"] == "fetch_result":
            return get(d["result_url"])
        if d["next_action"] == "inspect_events":
            raise RuntimeError("see " + d["events_url"])
        wait = d.get("next_poll_after_seconds") or min(wait * 2, 30)
        time.sleep(wait)
    raise TimeoutError("still running; job continues")

Why a field and not a status table

Job statuses (queued, processing, completed, failed, canceled) are useful for display, but they are a poor thing to branch on, since a new state would break a client that did not know it. next_action is the contract: the OpenAPI states it is poll_status for queued and processing jobs, fetch_result once completed, and inspect_events for failed or canceled terminal jobs. A client that handles those three values keeps working if the status list grows.

Keep terminal as the loop exit and result_ready as the gate for reading output. A job can be terminal without a result, for example when it failed, and that is exactly when inspect_events applies. For the resource itself, GET /v1/avatar-videos/{id} returns data.avatar_video and data.job together, which is handy when you want the video fields and the job state in one read.

Common mistakes

  • Treating result_ready: false as a failure. The job is simply not done.
  • Resubmitting after a client timeout. Re-read the job from status_url or retry the submit with the same Idempotency-Key.
  • Reading the video URL before the job completes. Use fetch_result as the gate.
  • Ignoring inspect_events on a failed job and sending the same input again. Read the reason first.

Limits

This describes the avatar-video job envelope. Format and agent runs use a different action vocabulary, with none for terminal runs, so do not share one handler across both without branching. The sketch above omits error handling for HTTP errors on purpose; add retries for 5xx in production, and use a webhook if you prefer to be told.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume