Idempotency-Key per shot: rerun one failed AI video shot in Python
One key per shot, derived from project, shot number and revision: a retry returns the same Sume job, a changed prompt gets a new key. Python key helper inside.

Give every shot in a multi-shot video its own Idempotency-Key, built from the project, the shot number and a revision counter. A network retry of the same shot sends the same key and Sume returns the original job instead of creating and reserving a second one. When you edit the prompt, bump the revision so the key changes. Reusing a key with a different payload returns 409 idempotency_conflict.
This is the pattern to use when Kling 4.0 style plans become many separate jobs. The fal explainer (read 2026-10-05) lists up to 10 keyframes and 30 second clips in one model; on Sume today a long video is a list of shots, and each shot needs to be safe to retry on its own.
The rule in the docs
The video docs list the difference from OpenRouter in one line: send Idempotency-Key to make retries safe, and a replay returns the original job. The generation admission docs give the matching client behaviour for errors: retry queue_full, rate_limited and provider_capacity_exceeded with the same key, and use a key again only for an exact retry.
That rule defines the key design. If the key were random per attempt, a retry after a timeout could create a second paid job. If it were fixed per shot slot and never changed, an edited prompt would hit 409 idempotency_conflict. A key that includes a hash of the request body, or an explicit revision, handles both.
When to keep or change the key
| Situation | Key | Result |
|---|---|---|
| Timeout or dropped response, same body | Same | Replay returns the original job |
| 429 queue_full or rate_limited | Same | Retry after backoff; no second reserve |
| Edited prompt or new frame image | New | A new job is created |
| Same key, different body | Same | 409 idempotency_conflict |
| Failed job you want to rerun unchanged | Decide per policy | See the note below |
Rerunning one failed shot
The last row is a policy choice. The docs only say that a replay of the same key returns the original job, so rerunning a job that ended failed is safest with a new revision number rather than relying on the old key. Record the revision in your own store, next to the job id, and the next attempt is a one-line change.
A failed shot should not block the rest. Poll each job independently, collect the ones that are completed, and resubmit only the shot that failed. Because every shot is separate, you pay once for each finished shot and only reserve again for the retry.
A key helper
This helper builds the key and the payload for each shot, and shows that the key changes only when the body changes. It runs offline.
import hashlib, json
def shot_key(project, shot_no, payload, revision=1):
body = json.dumps(payload, sort_keys=True).encode()
digest = hashlib.sha256(body).hexdigest()[:10]
return f"{project}-s{shot_no:02d}-r{revision}-{digest}"
shots = [
{"model": "seedance-2.5", "prompt": "Wide street at dusk", "duration": 6,
"resolution": "720p", "aspect_ratio": "21:9"},
{"model": "seedance-2.5", "prompt": "Close on the neon sign", "duration": 4,
"resolution": "720p", "aspect_ratio": "21:9"},
]
keys = [shot_key("promo", i + 1, p) for i, p in enumerate(shots)]
print(keys)
print(keys[0] == shot_key("promo", 1, shots[0]))
edited = dict(shots[0], prompt="Wide street at dawn")
print(keys[0] == shot_key("promo", 1, edited, revision=2))Webhooks and restarts
You can skip polling for most shots by passing callback_url, an HTTPS address that receives a webhook when the job completes. Sume's standard job webhook envelope carries an x-sume-webhook-signature header, which your receiver should verify with the webhook secret before it trusts the body. Keep polling as a fallback for shots whose webhook you did not see.
Either way, store the shot key beside the job id. When your worker restarts, it can rebuild the key from the project, shot number, payload and revision, resubmit, and get the original job back. That makes the whole storyboard loop safe to run twice: a second run costs nothing for shots already submitted, and only creates jobs for shots that were never sent. It also keeps the audit trail simple, because one shot maps to one key and one job.
Sending it
Send the key as a header on POST /v1/videos along with your bearer token, then poll GET /v1/videos/{id} as in the video generation docs. The same job is also visible on GET /v1/jobs/{id}/status and /result, which the jobs docs describe. Keep the key, the job id and the usage.cost together in your shot table, and you can audit every shot of a video later.
Sources
Related posts
More in Developers
- Chain Ideogram 4.5 edits with webhooks: job.completed starts pass 2
Run a multi-turn Ideogram 4.5 edit chain on Sume without polling: submit with mode webhook, verify the signature, and start the next pass from job.completed.
- Ideogram 4.5 seed on Sume returns 400: how to repeat an edit
Ideogram's own API takes a seed for 4.5 edits, but Sume returns 400 unsupported_parameter for seed on every image model. Keep the output URL, not the seed.
- Ideogram 4.5 edit in TypeScript: handle 200, 202 and 502 on Sume
One fetch helper for POST /v1/images with Ideogram 4.5: return the URL on 200, poll status_url and result_url on 202, and throw the error body on 502.
- Ideogram 4.5 on Sume does not accept output_format
Ideogram 4.5's output format is chosen by the provider on Sume. Which other image ids take png, jpeg or webp, and how to convert after the fact in Python.
Written by Sume