Build a Sume Idempotency-Key from an order id: changed body result
The same key and body replays the original Sume job. A different body with the same key is a 409. Pick keys that make both outcomes safe, in runnable Python.

What should an Idempotency-Key be for an AI video order?
A value that your system can regenerate after a crash and that changes only when you mean it to: the order id, plus a version number you bump when the request itself changes. With the same key and the same body, Sume replays the original job. With the same key and a different body, it answers 409 idempotency_conflict. On Format runs, a concurrent duplicate gets a retryable 409 idempotency_key_in_use; check the API reference for the route you call.
A body counts as different when the request fields differ, so a changed prompt, duration or resolution is a conflict, while a retry of the identical request is a replay. Build the body once, keep it, and send exactly that body on every retry.
Both outcomes protect your money. A key built from a random UUID per attempt protects nothing, because every retry looks like a new order.
The three outcomes
| You send | Sume answers | Your move |
|---|---|---|
| Same key, same body | Replays the original job | Treat it as the same order |
| Same key, different body | 409 idempotency_conflict | Mint a new key if the change is intended |
| Same key while the first is running | 409 idempotency_key_in_use, retryable | Wait briefly, retry |
Key builder and a local stand-in
The stand-in below is not Sume's server. It reproduces the documented rules in a dictionary so you can see how a key scheme behaves before it reaches production.
import hashlib, json
def key(order_id: str, version: int = 1) -> str:
return f"order-{order_id}-v{version}"
def body_hash(body: dict) -> str:
return hashlib.sha256(json.dumps(body, sort_keys=True).encode()).hexdigest()[:12]
seen: dict = {}
def submit(k: str, body: dict) -> str:
h = body_hash(body)
if k in seen:
return "replay " + seen[k][0] if seen[k][1] == h else "409 idempotency_conflict"
seen[k] = (f"job_{len(seen) + 1}", h)
return "created " + seen[k][0]
a = {"model": "wan-3.0", "prompt": "a mug", "duration": 30}
print(submit(key("1042"), a))
print(submit(key("1042"), a))
print(submit(key("1042"), {**a, "duration": 20}))
print(submit(key("1042", 2), {**a, "duration": 20}))When to bump the version
Bump it only when you deliberately change the request, for example a new prompt after review. That creates a new job, a new reservation and a new charge, and it should be a conscious step in your workflow. A crash, a timeout or a 5xx is never a reason to bump: retry with the same key.
A failed job releases its reservation, so the money is not lost, but check how your surface treats a key after a terminal failure before you assume the same key starts a fresh attempt.
Store the key beside the order row, not only in memory. A deterministic key means a restarted worker computes the same value, and a stored key also lets you audit later which submit belonged to which order, and which job id came back for it.
Do not put the prompt text or any customer data in the key. It travels in a header and shows up in logs. An order id with a short version suffix is enough, and it is easy to read when you are looking at a support ticket.
Sources
Related posts
More in Developers
- BullMQ delayed job that polls an AI video job and reschedules itself
A BullMQ worker reads Sume's job status once, then adds the next poll with a delay from next_poll_after_seconds, so no worker slot is held while a clip renders.
- A calendar file for AI model shutdown dates: .ics from Python
Generate an .ics file with all-day events and 14-day reminders for the gpt-image-1 and gpt-image-1.5 shutdown dates, then import it into any calendar app.
- Cancel a queued transcription job: what the 409 means
POST /v1/jobs/{id}/cancel works only before work starts. After that it returns 409 job_generation_already_started and the job finishes and bills normally.
- Celery task for an AI video API: submit, poll, retry on Wan 3.0
Two Celery tasks for Sume's /v1/videos: one submits a Wan 3.0 job with an Idempotency-Key, one polls with self.retry(countdown) and stops on a terminal status.
Written by Sume