xAI video API polling: pending, done, failed vs Sume job states
xAI's video API returns a request_id and three states. Here is how they map onto Sume's five /v1/videos statuses, with a runnable Python polling loop.

xAI's video endpoint is a poll-only, two-call flow: POST /v1/videos/generations returns a request_id, and GET /v1/videos/{request_id} answers pending, done, or failed. Sume's POST /v1/videos has the same shape with five statuses (pending, in_progress, completed, failed, cancelled), so an xAI polling loop moves over by renaming three strings and handling one extra state.
The xAI side comes from Video Generation: Grok API Capabilities (read 2026-10-11); the Sume side from Video Generation and Jobs and results. The xAI page describes polling only and does not mention a callback or an idempotency header, so this post does not assume either exists there. Whether a given xAI model is on Sume is a catalog question: ask GET /v1/videos/models instead of guessing.
How do xAI's three states map onto Sume's five?
Collapse the two in-flight Sume states into one and add a branch for cancelled, which has no xAI counterpart on the page I read.
On the failed branch, do not stop at the word. Sume's errors page says a failed job carries public error metadata such as category, stage, retryability, retry-after seconds, public reason and next action. Read those fields before deciding whether to resubmit, because a validation failure needs a corrected request while a queue failure is worth retrying later with the same idempotency key.
| xAI status | Sume /v1/videos status | What your loop does |
|---|---|---|
pending | pending or in_progress | Sleep and poll again. |
done | completed | Download from unsigned_urls, or from GET /v1/videos/{id}/content?index=0. |
failed | failed | Read the error field of the poll response. |
| (none) | cancelled | Stop. The job was canceled and never completed; this is not a retry signal. |
What else differs besides the status names?
Four things matter when you port the loop, and only one of them is a rename.
- Duration: xAI documents 1 to 15 seconds with a default of 8. Sume does not use one global range; each model lists
supported_durationsinGET /v1/videos/models, and the limits differ by model (Seedance 2.5 takes 4 to 30 seconds, Gemini Omni Flash 1.1 takes 3 to 10). - Polling cadence: xAI says its SDKs poll every 1 second (Python SDK) or 5 seconds (AI SDK) by default. Sume's video docs suggest a moderate interval such as 30 seconds, and
GET /v1/jobs/{id}/statuscan returnnext_poll_after_seconds, which wins when present. - Retries: Sume accepts an
Idempotency-Keyon/v1/videos, and a replay returns the original job, so a lost response does not buy a second clip. - Output links: xAI says videos come back as temporary URLs. Sume's docs show
unsigned_urlspointing at the/contentroute of the job, and the documented curl download sends the API key header.
What does the ported loop look like?
This is stdlib Python. It keeps xAI-style states (pending, done, failed) at the edge of your own code so the rest of an existing pipeline does not change. The mapping table is your adapter, not a Sume feature. It sets an explicit User-Agent and passes the idempotency key on the submit call.
import json, os, time, urllib.request
KEY = os.environ["SUME_API_KEY"]
BASE = os.environ.get("SUME_BASE", "https://api.sume.com")
STATE = {"pending": "pending", "in_progress": "pending",
"completed": "done", "failed": "failed", "cancelled": "failed"}
def call(method, path, body=None, key=None):
headers = {"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
"User-Agent": "xai-port/1.0"}
if key:
headers["Idempotency-Key"] = key
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(BASE + path, data, headers, method=method)
with urllib.request.urlopen(req, timeout=60) as resp:
return json.load(resp)
def start(body, idempotency_key):
return call("POST", "/v1/videos", body, idempotency_key)["id"]
def poll(request_id, every=30, deadline=1200):
end = time.time() + deadline
while time.time() < end:
job = call("GET", f"/v1/videos/{request_id}")
if STATE[job["status"]] != "pending":
return STATE[job["status"]], job
time.sleep(every)
raise TimeoutError(request_id) # the job keeps running and billingShould the loop poll `/v1/videos` or `/v1/jobs`?
Both read the same job. The /v1/videos/{id} route keeps the OpenRouter-compatible fields (polling_url, unsigned_urls, usage), while GET /v1/jobs/{id}/status returns the terminal and result_ready booleans that the jobs docs tell you to poll on. If your code already speaks xAI's three states, stay on /v1/videos; if you are building something new, the booleans are harder to misread.
Two rules carry over from the jobs docs either way. A client-side timeout does not cancel the job, which keeps running and billing, so store the id and read it again later. And do not submit the original paid request again just because a local process timed out; retry the submit only with the same Idempotency-Key.
If you would rather not poll at all, send callback_url on the submit. Sume then POSTs its standard job envelope (job.completed, job.failed or job.canceled), signed with x-sume-webhook-signature. Keep a status poll as the backup for deliveries that never arrive.
Sources
Related posts
More in Developers
- Which MCP server lets Claude Code or Cursor generate video and images?
MCP servers that let Claude Code and Cursor make video and images: Sume, fal, Replicate, Runway, Higgsfield. Endpoints, sign-in, billing, setup.
- Idempotency keys for AI video APIs: retry without paying twice
An idempotency key makes a retried create return the original run or job instead of a second paid one. How Sume's Idempotency-Key works on each API.
- Signed webhooks for Sume video runs: events, retries, verification
Sume sends one HMAC-SHA256 signed POST when a Format, Action, or Agent Completion run completes or fails. Verify the raw body and dedupe on request_id.
- Spend caps for unattended AI agents: how Sume bounds each run
An unattended agent has no one to approve spend, so Sume caps generation per run: required on Agent Completions, and up to $500 on Format runs.
Written by Sume