One idempotency key per order: a retried holiday batch never doubles
Sume Format runs replay safely: same key and body returns the original run with no second charge. Build the key from order id plus a version, never the clock.

Send Idempotency-Key on every Sume Format run and derive it from the thing the run makes: your order id plus a version you bump only when you want a re-run. With the same key and the same body, Sume returns 200 with the original receipt and idempotency_hit: true, starts no second run and makes no second charge. During Black Friday week, when webhooks time out and workers restart, that is the line between one video per order and two.
The docs say it plainly: do not derive the key from the time of the request, and a fresh uuidgen per request makes the header useless. The scope of a key is one Format, and a key can be up to 255 characters. See Calling a Format.
What each replay returns
The behaviour depends on what changed between your two requests. Know the four cases before you write your retry loop.
| Replay | Result |
|---|---|
| Same key, same body | 200, original receipt, idempotency_hit true, no new charge |
| Same key, different body | 409 idempotency_conflict, nothing runs |
| Same key, two requests at once | One wins; the other gets 409 idempotency_key_in_use, retryable |
| Same key after a failed create (402, 503) | Key released; fix the cause and retry |
A key scheme for an order feed
Use order-<id>-v<n>. Start at v1. If the customer changes the product and you must re-render, bump to v2; the new key produces a new run. If a worker crashes after submit and retries, it sends v1 again and gets the original run back. The key plus the body decide the result, so changing even the instruction under the same key is a conflict, and you should treat that as a bug in your retry code, not something to work around.
curl -sS -X POST "https://api.sume.com/v1/formats/chase/product-promo/runs" \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-8823-v1" \
-d '{"instruction": "gift wrap promo", "input": {"url": "https://example.com/1.jpg"}}'Handling the 409s
For 409 idempotency_key_in_use, wait about one second and send again to get the original run. For 409 idempotency_conflict, stop and log both bodies. Do not retry with a random key, because that is how a duplicate render and a second charge happen.
For bulk queues, the key lives at the batch level: a spent key replayed on a queue create returns the old queue. Keep a per-batch key such as bf26-batch-1 and keep the per-item runs identifiable through each item instruction or input.
Store the receipt data.id the first time you see it, then use the URLs on the receipt for status and result, not paths you construct yourself.
- Key = order id + version.
- Never the clock, never a fresh uuid per try.
- Bump the version only for a deliberate re-run.
Test the replay before the sale
Do this once before launch. Submit one run with a test key, copy the receipt, then submit the identical request again. You should see 200 and idempotency_hit: true with the same run id. Then change one character of the instruction and send it under the same key. You should get 409 idempotency_conflict. If both behave, your retry code is safe to turn on.
Then simulate the crash. Kill the worker after the submit, restart it and let it retry. If it produces a second run, the key is probably built from the clock or a random value. Fix that before the queue holds 100 orders.
Finally, decide who bumps versions. A human or a clearly named job should be the only thing that moves v1 to v2, because that step is the deliberate re-run, and it is the one place a second charge is intended.
Related posts
More in Formats
- Pilot 3 rows before a Sume bulk batch and estimate spend from receipts
Run three rows first, read usage.debited_usd_micros from each receipt, and scale up. Skip billable_amount_usd_micros: it leaves out the agent turn.
- Retry one scene and keep the voice track: Format previous_run_id
A new voice is not a reason to redo a whole video. Continue a finished Format run with previous_run_id and an instruction that touches one scene only.
- Send more than 100 spreadsheet rows to a Sume Format in chunks
One Sume bulk queue takes 1 to 100 items. For a 450-row sheet, split it into chunks and key each chunk so a retry never doubles spend.
- Spreadsheet with more than 64 columns: nest them in one Sume input key
A Sume run input allows 64 top-level keys and 2 MiB. Nested keys do not count toward the 64, so a 90-column sheet row fits under a single key.
Written by Sume