Job id or run id: which Sume endpoint and helper do you poll?
A job_ id is polled at /v1/jobs/:id with waitForJob. An arun_ id is a Format, Action or Agent run, polled with waitForRun and a family argument.

Look at the prefix. An id that starts with job_ is a generation job. You read it at GET /v1/jobs/:id/status and fetch the result at GET /v1/jobs/:id/result. In the TypeScript SDK you wait for it with waitForJob. An id that starts with arun_ is a Format, Action or Agent run. You read it at /v1/format-runs, /v1/action-runs or /v1/agent-runs, and you wait for it with waitForRun, which needs a family argument that names the one you mean.
Why the two families exist
The two ids come from different submit calls, and they are not interchangeable. The families also differ in how they end. A job ends in completed, failed or canceled, while a run can also end in skipped, for example when an active run policy declines to start another one. If you pass a run id to a job route you get a not-found error, not a status. This is the most common first-day mistake in integrations that submit both a direct generation call and a Format run, because both responses carry a request id and a status URL.
- Jobs are the paid generation calls such as an image or avatar generate request. They have the statuses
queued,processing,completed,failedandcanceled. - Runs are the higher level Format, Action and Agent runs. Their terminal statuses are
completed,failed,canceledandskipped, and they report anoutcomeofok,degradedorerror. - A Format run may start several generation jobs inside it. You still poll the run, not the inner jobs, unless the run receipt tells you to do otherwise.
The routing table
Use this table when you hold an id and have to choose the call.
| Id prefix | Poll route | SDK helper | Default client timeout |
|---|---|---|---|
| job_ | GET /v1/jobs/:id/status | waitForJob | 20 minutes |
| arun_ (Format) | GET /v1/format-runs/:id | waitForRun with family format | 10 minutes |
| arun_ (Action) | GET /v1/action-runs/:id | waitForRun with family action | 10 minutes |
| arun_ (Agent) | GET /v1/agent-runs/:id | waitForRun with family agent | 10 minutes |
What each helper does for you
waitForJob polls no faster than every 2 seconds and obeys next_poll_after_seconds when the server asks for a longer pause. waitForRun tolerates up to 6 transient failures before it throws. A Format run can also be watched live with subscribeFormatRun, which sends an automatic UUID idempotency key unless you pass null to send none.
Pick the route from the id
The function below picks the poll path from the id. It runs as is and needs no network, so you can drop it into a router or a test.
def poll_path(entity_id: str) -> str:
if entity_id.startswith("job_"):
return f"/v1/jobs/{entity_id}/status"
if entity_id.startswith("arun_"):
# the run family comes from the submit route you called
raise ValueError("arun_ ids need the family: format, action or agent")
raise ValueError("unknown id prefix: " + entity_id[:5])
def run_path(family: str, run_id: str) -> str:
routes = {"format": "format-runs", "action": "action-runs", "agent": "agent-runs"}
return f"/v1/{routes[family]}/{run_id}"
print(poll_path("job_123"))
print(run_path("format", "arun_123"))
Store the family with the id
Store the family next to every run id when you save it. The prefix alone does not say whether a run is a Format, an Action or an Agent run, and waitForRun will not guess. Also keep the polite behaviour the docs ask for. Use backoff, stop on a terminal status, and never submit the paid request again only because your own process timed out.
What changes after the first poll
Three more differences matter once the first poll works. They decide how you finish the loop, what you do with a result, and whether you can rely on a webhook at all.
- A finished job gives you artifacts shaped as
{id, url, type, content_type}. A finished run gives the same fields plussize_bytes,width,height,duration_msandchecksum_sha256, so a run result can be verified without a second download. - Job webhooks are
job.completed,job.failedandjob.canceled. Run webhooks areformat.run.terminal,action.run.terminalandagent.run.terminal. Canceled and skipped runs send no webhook, so a poll is the only way to see them. - Retrying a failed run needs a new idempotency key, while a job retry after a network error should reuse the original key. Treat the two families separately in your retry code, and log which family every id came from.
Sources
Related posts
More in Developers
- job_id, request_id, Idempotency-Key: which one goes in which column
Your Idempotency-Key is the unique key before submit, job_id or run_id is the poll key, and request_id dedupes webhooks and goes into support tickets.
- 503 status_busy on GET /v1/jobs/{id}/status: back off and jitter
status_busy means Sume's job status read gate is full. Reads of the same job are shared; the cap is 100 distinct in-flight reads. Poll slower, add jitter.
- Sume jobs_wait outcome: wait_slice_expired is not a failed job
jobs_wait returns outcome terminal, wait_slice_expired or operator_stopped. Only terminal means the jobs ended; an expired slice says nothing about the jobs.
- jq one-liners for the video model catalog: ids, durations, 1080p
Two jq filters over GET /v1/videos/models: a table of ids with min and max seconds, and a filter for models that take 20 s at 1080p. Tested on a local copy.
Written by Sume