Video fallback ladder: why reusing an Idempotency-Key returns 409
On Sume's video API a repeated Idempotency-Key with a different body is a 409. When you fall back from one model id to the next, derive a new key per id.

If you reuse one Idempotency-Key while you move down a fallback ladder, say from seedance-2.5 to wan-3.0, Sume answers 409, because the key now belongs to a different body. Build the key from the model id plus the request payload, so each rung gets its own key and a retry of the same rung replays safely.
The two behaviors are both useful. A replay of the same body returns the original job, which stops a timeout from becoming a second paid clip. A changed body is treated as a conflict, which stops a bug from hiding behind a stale key.
What each case returns
These are the documented behaviors of POST /v1/videos.
| Situation | Result |
|---|---|
| Same key, same body | The original job comes back; no new charge |
| Same key, different body (new model id, new prompt) | 409 conflict |
| New key, same body | A new job and a new reserve |
| Key omitted | Every retry is a new job |
A key per rung
Hash the model id with the rest of the body. The same input always produces the same key, so a retry after a timeout is safe, and a different model produces a different key.
import hashlib, json
def video_key(body: dict) -> str:
raw = json.dumps(body, sort_keys=True, separators=(",", ":"))
return "vid-" + hashlib.sha256(raw.encode()).hexdigest()[:32]
ladder = ["seedance-2.5", "wan-3.0", "minimax-h3-max"]
base = {"prompt": "A paper boat on a rainy street", "duration": 8}
for model in ladder:
body = {**base, "model": model}
print(model, video_key(body))When to step down a rung
Fall back on a capability error such as an unsupported duration, not on a 402 or a timeout. A 402 means your balance is below the reserve, and every id will say the same. A timeout should retry the same rung with the same key. Only a 4xx that names the model's limits justifies a new body and a new key.
What to keep per attempt
Store enough to explain what happened later.
Log the key next to the job id. When a user asks why they were charged once, or twice, the pair answers it in one lookup.
Also keep the keys short-lived in your own store. A key is a promise about one body, so once the job is finished and recorded there is no reason to reuse it for anything else. Generate fresh keys for new work, and let the hash do that for you.
- The key you sent and the model id.
- The job id from the 202 response.
- The final status and
usage.cost. - The reason you stepped down a rung, such as a named capability error.
Sources
Related posts
More in Developers
- Same Idempotency-Key, two Sume members: two jobs, not one
A Sume Idempotency-Key is unique per workspace and per key owner. Two members who send the same string get two jobs. Namespace your keys by what you generate.
- One Sume voice, a different language: the 409 and the confirm flag
Ask Sume TTS to speak Spanish with an English-tagged voice and you get a 409 before any charge. Retry with confirm_language_mismatch only if you mean it.
- Save a Sume artifact atomically: write a .part file, then rename
A half-written MP4 that looks finished is worse than none. Download a Sume artifact to a .part file, check the length, rename once. Tested in Python.
- Schedule the next Ideogram 4.5 batch wave from ratelimit-reset
Size each wave from ratelimit-remaining and wave_size_hint, and when the write bucket is empty sleep ratelimit-reset seconds. A pure function you can test.
Written by Sume