Turn a 16-shot list into 16 Sume video jobs in Python
Submit one /v1/videos job per shot with a first frame, run them concurrently with asyncio, and give each shot an Idempotency-Key so a rerun never bills twice.

To turn a 16-shot list into video on Sume, submit one POST /v1/videos per shot with the shot's still as first_frame, run the submissions together with asyncio, and give each shot a stable Idempotency-Key. A rerun then returns the original job for every shot you already sent.
This is the Sume version of a 16-keyframe plan: sixteen independent clips, joined later. It is not one 16-keyframe generation like the one Luma lists for Ray 3.2, which Sume does not run.
The code
It uses httpx. The shot URLs are placeholders; use public HTTPS images. seedance-2 takes first_frame per its catalog entry, and the durations list starts at 4 seconds.
import asyncio, os
import httpx
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
SHOTS = [(f"shot-{i:02d}", f"https://example.com/s{i:02d}.png", "slow push-in, soft light") for i in range(1, 17)]
async def submit(c, key, img, prompt):
body = {"model": "seedance-2", "prompt": prompt, "duration": 5, "resolution": "720p",
"aspect_ratio": "9:16",
"frame_images": [{"type": "image_url", "image_url": {"url": img},
"frame_type": "first_frame"}]}
r = await c.post("https://api.sume.com/v1/videos", json=body,
headers={**H, "Idempotency-Key": key})
r.raise_for_status()
return key, r.json()["id"]
async def main():
async with httpx.AsyncClient(timeout=30) as c:
done = await asyncio.gather(*(submit(c, *s) for s in SHOTS))
for key, job in done:
print(key, job)
asyncio.run(main())Why the keys matter
The Video generation docs list Idempotency-Key as a Sume addition: send it to make retries safe, and a replay returns the original job. Keys like shot-07 tie a key to a shot, so a crash and rerun resubmits nothing twice. If you change the prompt of a shot, change its key too, otherwise you get the old job back.
Sume reserves the provider list times 1.25 at submit for each job, so 16 jobs reserve 16 amounts at once. Check your balance before you fire a large list, and keep an eye on workspace concurrency.
Poll or use a callback
Each submit returns a job id and a polling_url. Poll GET /v1/videos/{jobId} until the status is completed, then download from unsigned_urls[0]. The docs suggest a moderate interval of about 30 seconds. Or send an HTTPS callback_url; Sume signs the raw JSON body with x-sume-webhook-timestamp and x-sume-webhook-signature headers.
| Field | Value | Why |
|---|---|---|
| model | seedance-2 | lists first_frame and last_frame in supported_frame_images |
| frame_images[].frame_type | first_frame | starts the clip on your still |
| duration | 5 | within 4-15 s for this id |
| aspect_ratio | 9:16 | vertical delivery |
| Idempotency-Key | shot-NN | a rerun returns the original job |
Then assemble
Download each clip, import it to media.sume.com, and join the 16 in Timeline. With more than 12 slots the render chunks automatically, and no more than 8 fades may sit side by side. The next post shows the join in Python.
Practical guardrails
Sixteen submits at once is fine for a demo and may exceed your workspace's concurrency in production. Add a semaphore (asyncio.Semaphore(4)) around submit if you see queueing. A job that is pending is only waiting in the queue, so poll it rather than resubmitting.
Keep the shot manifest in a file with the key, the still URL, the prompt, and later the job id, so the next script can poll and download without guessing.
Spend arithmetic
Each shot reserves its own list x 1.25 amount, so the batch reserve is 16 times one job. If a single 5-second 720p job reserves R dollars, the batch reserves 16R. Look up R in pricing_skus for your model, multiply, and compare to your balance before you submit.
A failed job is a failed job, not a partial success: check the failed status and the error field per shot and resubmit only that shot with a new key.
Downloading the results
After each job reaches completed, fetch GET /v1/videos/{jobId}/content?index=0 with your key, or use the first entry of unsigned_urls. Save each file under its shot key, then import it to your workspace so Timeline can use it. Keep the polling interval moderate; the docs suggest 30 seconds because video jobs usually take from 30 seconds to several minutes.
Sources
Related posts
More in Developers
- 402 halfway through 50 clips of 30-second video: stop and resume
When balance runs out mid-batch Sume answers 402 insufficient_credits before provider work starts. Halt, keep the paid job ids, and resume the rest later.
- 402 on a 30-second 1080p Seedance 2.5 job: you need $42.65
The biggest Seedance 2.5 clip (30 s, 1080p) reserves about $42.65 at submit. A 21-line Python urllib handler shows what 402 and 429 mean and what to do next.
- 402 on the caption step after a paid generate and trim: what now
A 402 insufficient_credits at the caption step means admission failed before provider work. The render and trim jobs you paid for stay completed.
- 429 on a chain step: retry with the same Idempotency-Key
A 429 on a trim or captions submit is rate_limited or queue_full. Wait for retry-after and resend the same body with the same Idempotency-Key.
Written by Sume