Seedance 2.5 in 16:9, 9:16 and 1:1: one idempotency key per ratio
Fan one prompt out to three aspect ratios on /v1/videos, derive a separate Idempotency-Key for each ratio, and rerun the script without paying twice.

To render one Seedance 2.5 prompt in several aspect ratios, send one POST /v1/videos per ratio and build each Idempotency-Key from a base id plus the ratio. A rerun of the same script then returns the original jobs, because a replay with the same key and body returns the first job instead of starting a second one.
Check the ratios against the model's catalog entry first, since each model reports its own supported_aspect_ratios.
Why one key per ratio?
One key means one operation. If you reuse a single key across three different bodies, the second call fails with 409 idempotency_conflict, and if you mint a random key per attempt, a crash-and-rerun bills the clip again. A derived key such as launch-42-9x16 fixes both.
| Situation | Result |
|---|---|
| Same key, same body | The original job comes back |
| Same key, different body | 409 idempotency_conflict |
| New key, same body | A new paid job |
| No key | Not replay-safe |
What does the script look like?
The call uses the default async path, so the script only prints the polling URLs.
import os, requests
BASE = "https://api.sume.com/v1"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
want = ["16:9", "9:16", "1:1"]
models = requests.get(f"{BASE}/videos/models", headers=H, timeout=30).json()["data"]
m = next(x for x in models if x["id"] == "seedance-2.5")
ratios = [x for x in want if x in m["supported_aspect_ratios"]]
for ratio in ratios:
key = f"{os.environ['CAMPAIGN']}-{ratio.replace(':', 'x')}"
r = requests.post(f"{BASE}/videos", timeout=30,
headers={**H, "Idempotency-Key": key},
json={"model": "seedance-2.5", "duration": 8,
"resolution": "720p", "aspect_ratio": ratio,
"prompt": "A ceramic mug on a desk, slow push-in"})
print(ratio, r.status_code, r.json().get("polling_url"))How do I confirm the three jobs afterwards?
Print the ratio next to the polling URL, as the script does, and keep that output. Rerun the script: the same URLs coming back for the same ratios proves the replay worked, and a new URL means a new paid job. Map ratio to job by that printed label, not by list position.
What changes the key's meaning?
Any edit to the body, even the prompt text, makes the old key a conflict. Bake a version into the key (for example launch-42-v2-9x16) when you change the prompt on purpose, and keep the old key for the old spend.
Will three jobs run at once?
Only up to your plan's concurrency. The rest wait as queued, which is normal, and a full queue returns 429 queue_full. Check the headroom before a larger fan-out.
Sources
Related posts
More in Developers
- Send a signed webhook.test with POST /v1/webhooks/test-deliveries
Point POST /v1/webhooks/test-deliveries at a new HTTPS endpoint to get a signed webhook.test before any video job runs, then check the sume-v1 signature.
- Seven pre-flight checks before an Omni, H3 or Recast job
Duration, resolution, ratio, edit conflicts, reference counts, refused fields and URLs: seven per-model checks that catch a refused Sume video request.
- Size a Sume submit wave: limit 100, 30 processing, 10 queued gives 60
How many new jobs can a Sume workspace take? max(0, limit - active - queued), capped by queue_capacity_remaining. Worked numbers and a TypeScript helper.
- Make an SRT file from Sume STT word timings in Python (7 words a cue)
Sume STT always returns words[] with word, start and end in seconds. This Python function turns the list into an SRT file, seven words a cue.
Written by Sume