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.

5 min readSume
All posts

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.

Idempotency on bulk create (Sume docs, read 2026-10-05)
You sendResult
Same key, same concurrency and items202 and the existing queue
Same key, different payload409 idempotency_conflict, details.queue_id names the original
New key, same payloadA second queue and a second set of runs
Key in header and in bodyThe 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

All Developers posts

Written by Sume