Bulk queue replay returns 202 and the old queue: derive the key
A repeated Idempotency-Key on a Sume bulk create returns 202 and the old queue, not 200. Derive the key from the batch: safe retries, deliberate reruns.

A single Format run replays with 200 and idempotency_hit: true. A bulk queue does not. If you send the same Idempotency-Key with the same { concurrency, items }, the create answers 202 and gives you the queue that already exists, and the queue object has no idempotency_hit field. The Bulk runs page states the difference. So a retry after a network drop is safe, and you cannot tell from the status code alone whether you started a new queue or got the old one.
The replay table
The rules are small. The scope of a key is one Format, so the same key against two Formats makes two queues.
| You send | Result |
|---|---|
| Same key, same concurrency and items | 202 and the existing queue |
| Same key, different payload | 409 idempotency_conflict, details.queue_id names the original |
| New key, same payload | A second queue and a second set of runs |
| Key in header and in body | The header wins |
Derive the key from the batch
The docs say to mint a fresh key for each batch. For a batch that you may retry, derive the key from what the batch is. Hash the product list and a version you bump on purpose, and a retry gets the same key while a deliberate rerun gets a new one.
import hashlib, json
def batch_key(campaign, version, concurrency, items):
body = json.dumps({"concurrency": concurrency, "items": items}, sort_keys=True)
digest = hashlib.sha256(body.encode()).hexdigest()[:32]
return f"{campaign}-v{version}-{digest}"
items = [{"instruction": "15 s ad, hook A"}, {"instruction": "15 s ad, hook B"}]
print(batch_key("spring-serum", 1, 4, items))
print(batch_key("spring-serum", 2, 4, items))What can go wrong
Two cases catch teams. In the first, you edit one item and resend with the old key. The payload now differs, so you get 409 idempotency_conflict and details.queue_id names the first queue. That is correct, and the hash key above avoids it, because an edited batch hashes to a new key. In the second, you want a rerun of the same batch, for instance after a Format changed, and you send the old key. You get 202 and the old queue, and nothing runs. Bump the version in the key.
In both cases, store the queue id from the first response, with the key. If you lose the queue id, the API has no list-queues call, so a replay with the original key is how you recover it, and that only works while you still have the exact payload.
Keep it separate from the per-item webhooks
There is no queue-level webhook. Each item can carry its own communication.webhook_url, and each child can register its own terminal webhook. Poll status_url for queue progress. If you also want a signed callback on each ad, set the URL on every item, and have the receiver refuse a request when its signing secret is empty. The queue key and the item keys are independent: the batch key protects the create, and each child run has its own receipt.
A deliberate rerun, step by step
Say the spring batch finished and two of 40 ads failed. You want those two again, and only those two. Do not resend the first batch with a new key, because that would rerun all 40 and charge for all 40. Build a new list from the two failed items, derive a new key from the new list, and create a new queue. The new payload hashes differently, so the key is new on its own, and the version number stays a label for people.
If instead you changed the Format and want to compare the 40 ads from before and after, bump the version, keep the items, and create the queue on purpose. Note the old queue id in your records, so the two sets of receipts stay linked. The old queue is not changed by the new one, and its runs keep their own spend.
Sources
Related posts
More in Developers
- Bulk text to speech from a CSV: one job per row, safe retries
Voice 500 CSV rows with Sume TTS 1.0. One idempotency key per row id, retry-after on 429, and the queue-capacity numbers per plan decide how fast it goes.
- Bun script: submit a Wan 3.0 video job, poll it and save clip.mp4
A single Bun file that posts to /v1/videos for wan-3.0, polls until completed and writes clip.mp4 with Bun.write; the 5-second 480p test costs $0.3125.
- Burn an 'AI-generated' disclosure line into a clip with caption cues
YouTube asks creators to disclose realistic AI content. Add an authored on-screen line with Sume caption cues, no speech-to-text, that travels with the file.
- Burn captions from TTS word timings: no second transcription
Ask Sume TTS for word timings, group them into phrases and send them as caption cues. The caption job skips speech-to-text and still costs $0.20.
Written by Sume