Idempotency-Key from a body hash plus a take number (Node, Sume)
Build an Idempotency-Key for Sume /v1/videos from a SHA-256 of the body plus a take counter, so a retry replays and a re-roll pays. Node code.

A good Idempotency-Key for a video submit answers one question: is this the same purchase, or a new one? Hash the request body, keep the first 24 hex characters, and add a take number you increment on purpose. A network retry reuses the same key and gets the original job back; a deliberate re-roll bumps the take, gets a new key, and creates a new paid job. Sume documents both halves: a replay returns the original job, and the same key on a different payload returns 409 idempotency_conflict.
Why the body hash is not enough
If the key were only the body hash, a second submit of the same prompt would always replay the first job, so you could never ask for another take of the same prompt. If the key were a random UUID created on each call, a retry after a timeout would look like a new purchase and could bill twice. The take number separates the two cases: retries share it, re-rolls change it.
The sample uses seedance-2.5 at 480p for 4 seconds in 9:16. seedance-2.5 bills a minimum of 4 seconds, and 480p is $0.268677 per second, so one take costs 4 x 0.268677 = $1.074708, about $1.07. Three takes cost $3.22, which is the number to compare against your budget before a loop calls submit.
| Call | Key | Outcome |
|---|---|---|
| Submit, then retry after a timeout | draft-<hash>-take1 both times | Replay: the original job is returned, one charge |
| Same prompt, new take | draft-<hash>-take2 | New key, new job, billed again ($1.074708) |
| Prompt edited, take number unchanged | new hash, so new key | New job; the edit changes the hash |
| Different body sent under an old key | old key | 409 idempotency_conflict, nothing runs |
The Node code
The script hashes JSON.stringify(body). Build the object the same way every time, because key order changes the string. Run it as node submit.mjs 1 and then node submit.mjs 1 again; the second call returns the same job id. node submit.mjs 2 starts a second job.
import { createHash } from "node:crypto";
const body = {
model: "seedance-2.5",
prompt: "Espresso pour, macro shot, steam rising",
duration: 4,
resolution: "480p",
aspect_ratio: "9:16",
};
const digest = createHash("sha256").update(JSON.stringify(body)).digest("hex");
const keyFor = (take) => `draft-${digest.slice(0, 24)}-take${take}`;
async function submit(take) {
const res = await fetch("https://api.sume.com/v1/videos", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": keyFor(take),
},
body: JSON.stringify(body),
});
return { status: res.status, key: keyFor(take), job: await res.json() };
}
const take = Number(process.argv[2] ?? 1);
console.log(await submit(take));Rules to keep
Use a key again only for an exact retry; that is the rule in the Sume admission docs. Do not put a timestamp in the key, because a retry then generates a new key. Do not put the API key or customer data into it, because keys are logged in your own systems.
If a submit times out and you do not know whether Sume received it, send the same call again with the same key. If it did, you get the existing job; if it did not, you get a new one. Either way you pay once.
The sample prints the HTTP status and the parsed body together. Read the status first: a 202 means a job exists (new or replayed), while a 4xx means nothing was created and the key is free to use again after you fix the request.
- Store the key next to the job id in your database.
- Increment the take only when a person asks for another variation.
- Treat a 409 idempotency_conflict as a bug in your key derivation, not as something to retry.
Sources
Related posts
More in Developers
- Sume STT returns 400 for diarize: a two-speaker fix at $0.60 an hour
Sume STT 1.0 fixes diarize and tag_audio_events server-side and rejects them with 400. For two speakers, transcribe each track and merge by word times.
- Do Sume result URLs expire? media.sume.com vs /videos content
Sume Format artifact URLs on media.sume.com do not expire and are public. The /v1/videos content route needs your key. What to store, plus a Python download.
- Edit an existing clip with Gemini Omni Flash 1.1: video_url on Sume
Sume exposes Omni video edit mode through the Video Router video_url field: the prompt is the instruction, resolution defaults to 720p, no duration.
- Enterprise API key rate limit before a contract: the Scale row
Until Sume provisions a contracted number, an Enterprise key uses the Scale row: 1,200 writes and 48,000 reads per minute. The full plan table and the math.
Written by Sume