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.

4 min readSume
All posts

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.

Idempotency behavior on the Sume video API (read 2026-10-06)
SituationResult
Same key, same bodyThe original job comes back; no new charge
Same key, different body (new model id, new prompt)409 conflict
New key, same bodyA new job and a new reserve
Key omittedEvery 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

All Developers posts

Written by Sume