/v1/videos status expired: in the enum, never sent by Sume
The /v1/videos status enum includes expired for OpenRouter compatibility, but Sume never emits it. Handle it as terminal anyway; five other statuses occur.

GET /v1/videos/{id} can return pending, in_progress, completed, failed or cancelled. The enum also holds expired, which Sume never emits: the API does not expire jobs. Write your poll loop so an unknown or expired status stops the loop instead of spinning forever.
Status mapping
Sume maps its internal job statuses to the OpenRouter-shaped values like this.
| Sume job.status | /v1/videos status | Terminal? |
|---|---|---|
queued | pending | No |
processing | in_progress | No |
completed | completed | Yes, download the video |
failed | failed | Yes, read error |
canceled | cancelled | Yes (note the spelling change) |
| not emitted | expired | Treat as terminal if you ever see it |
Two spelling traps
Two details trip people up. Sume spells the internal state canceled, but /v1/videos returns cancelled. And expired exists only so that clients generated from the OpenRouter schema parse the field without error. A strict enum in your own types should include it.
A safe poll loop
This loop treats every non-running status as terminal and never loops on a value it does not know.
import asyncio, os
import httpx
RUNNING = {"pending", "in_progress"}
async def main():
headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
async with httpx.AsyncClient(base_url="https://api.sume.com", headers=headers) as c:
job = (await c.post("/v1/videos", json={
"model": "sume/auto",
"prompt": "A paper boat drifting down a rain gutter",
})).json()
while True:
await asyncio.sleep(30)
st = (await c.get(job["polling_url"])).json()
if st["status"] in RUNNING:
continue
print(st["status"], st.get("error"), st.get("unsigned_urls"))
break
asyncio.run(main())Polling versus webhooks
The Sume docs suggest a moderate poll interval of about 30 seconds. You can also pass callback_url (HTTPS) and receive a signed webhook on a terminal state.
Sources
Related posts
More in Developers
- /v1/videos model_not_found: 404 now, not 400. Fix retry logic
An unknown model id on /v1/videos returns 404 model_not_found. It was 400 through #2311 and changed in #2321. Update clients that match on 400.
- /v1/videos size 1920x1080 returns 400: send resolution instead
POST /v1/videos rejects size with 400 unsupported_parameter because every model reports supported_sizes null. Send resolution plus aspect_ratio.
- /v1/videos poll status: pending, in_progress, and the Sume job state
On /v1/videos, a Sume job reads queued as pending, processing as in_progress, canceled as cancelled. The full status mapping, and why expired never appears.
- /v1/videos provider.options returns 400: no passthrough in v1
Non-empty provider.options on /v1/videos returns 400 unsupported_parameter: every model lists allowed_passthrough_parameters as empty. seed and size fail too.
Written by Sume