Idempotency-Key from an order id and version, never a fresh uuid
A fresh uuid per request makes Idempotency-Key do nothing. Derive it from the order id plus a version you bump only to re-run. Scope: one Format, 255 chars.

Build the Idempotency-Key from the thing the run makes: your order id, plus a version number that you raise only when you want a re-run. Do not derive it from the time of the request. The docs state that if you use a new uuidgen per request, the header has no effect, because a retry arrives with a different key and starts a second run, which Sume bills.
What a replay does
Send the header on every create. The result depends on what you send twice.
| Replay | Result |
|---|---|
| Same key, same body | 200 with the original receipt and idempotency_hit: true. No second run, no second charge |
| Same key, different body | 409 idempotency_conflict. Nothing runs |
| Same key at the same moment | One request wins. The other gets 409 idempotency_key_in_use, which is retryable |
Same key after a failed create (402, 503) | Sume released the key. Fix the cause and retry with the same key |
A different instruction or a different attachment list counts as a different body. That is the usual cause of an unexpected idempotency_conflict: the key is stable, but something in the body is not, such as a timestamp embedded in the instruction text.
Scope and size
The scope of a key is one Format. If you send the same key to two Formats, you start two runs, so the same order id sent to two Formats gives two runs. A key is up to 255 characters. The helper below hashes anything longer, so an unusually long id does not turn into a rejected request. It prints the stable key, a bumped key for a deliberate re-run, and the length of a hashed key.
import { createHash } from "node:crypto";
// Derive the key from the item the run makes. Never from the clock or a fresh uuid.
export function runKey(orderId, version = 1) {
const key = `order-${orderId}-v${version}`;
if (key.length <= 255) return key;
return `order-${createHash("sha256").update(key).digest("hex")}`; // keys are up to 255 chars
}
console.log(runKey("8823")); // same order, same key: a replay returns idempotency_hit
console.log(runKey("8823", 2)); // bump the version only to force a re-run
console.log(runKey("x".repeat(300)).length); // 70Choosing the version
- Keep the version at 1 for retries after a crash, a deploy or a client timeout. The replay returns the original receipt and
idempotency_hit: true. - Raise it when the business wants a new result: the customer edited the brief, or the first output was rejected. That starts a new run with a new bill.
- Store the key with the run id in your own table the moment the create returns. A
200replay then lets you confirm that the id you hold is the one Sume returned. - Do not put secrets or personal data in the key. It is stored on the receipt as
trigger.idempotency_key. - Do not reuse one key for a changed
input. Change the version instead, so the new request has its own key.
Why the client matters too
The SDK's createSumeClient retries 408, 429, 5xx and transport failures, but it retries a POST only when the request carries an Idempotency-Key. Without one, a replay starts and bills a second run. subscribeFormatRun generates a key for you unless you pass your own, which is convenient for scripts. For anything that can be retried by a queue or a cron job, pass a key derived from your own record. See Calling a Format for the replay rules, and the nearby post on `idempotency_conflict` and `idempotency_key_in_use` for the two 409 codes.
Sources
Related posts
More in Developers
- How do I narrate a DIY tutorial step by step with a TTS API?
Narrate an 8-step DIY tutorial with one TTS job per step: 1,570 characters, $0.10 on Sume. Why per-step jobs make a fixed step a 1-cent redo.
- Do I pay for a failed AI avatar video job? Refunds on Sume
Sume reserves the avatar video price at submit, captures it on completion, and releases or refunds it where a job fails. What it means for retries.
- Does PNG, JPEG or WebP change the price of an AI image on Sume?
No. On Sume's Image API, output_format picks the file type, not the price: per-image cards and GPT Image 2.5 token math ignore it. Which models list which.
- duration vs duration_seconds on each Sume video route
/v1/videos takes duration; motion control and lip-sync take duration_seconds; recast and edit read the source clip. One table of what each does.
Written by Sume