Bulk run 409 idempotency_conflict: find the first queue by queue_id

A Sume bulk create that reuses a key with a different payload returns 409 with details.queue_id. How to read the original queue and decide what to resend.

5 min readSume
All posts

What does a 409 from a Sume bulk create mean, and where is the queue I already started? If you send POST /v1/formats/{handle}/{slug}/bulk-runs with an Idempotency-Key you used before and a different { concurrency, items }, the API answers 409 idempotency_conflict, and details.queue_id names the original queue. Per the Bulk runs docs, the same key with the same payload is not an error: it returns 202 and the existing queue.

That asymmetry is the whole feature. It lets a crashed uploader retry safely, and it stops a changed list from silently attaching to an old queue.

The three outcomes of a key

Bulk create replay outcomes, Sume Bulk runs docs read 2026-10-04
What you sendWhat you getWhat it means
New key202 and a new queueItems start, up to concurrency at once
Same key, same concurrency and items202 and the existing queueA true replay; nothing new starts
Same key, different payload409 idempotency_conflict with details.queue_idYour key was already spent on other content

What to do on the 409

First read the queue named in details.queue_id with GET /v1/format-run-queues/{id}. You are looking for one of two situations. If that queue holds the rows you meant to send and you only rebuilt the request differently, for example the items were re-serialized with different key order or an extra field, then accept the original queue and carry on polling. If the list really changed, the old queue is a different job: leave it alone and mint a new key for the new list.

Never reuse the same key after changing the list in the hope the server will merge them. The control plane stores the key against one payload, and the docs say a replay of a spent key returns the old queue, not a new one. Derive keys from the business intent plus a revision suffix, so a deliberate change gets a new key by construction.

Naming keys so a conflict means something

A key that can collide by accident produces the wrong 409. A key made from a date, bulk-2026-10-04, collides with the second batch you send that day and with a different list. A key built from the campaign name, the revision and a hash of the list collides only when you really are resending the same job.

Keys are scoped to one Format and can be up to 255 characters, so there is room to be descriptive. Put the Format slug, the campaign and a short hash of the serialized items in the key.

When the 409 does arrive for a genuinely changed list, log both the new key and details.queue_id, so a human can see which earlier queue the key was spent on.

A safe create wrapper

This function shows the decision logic with the HTTP call stubbed out, so it runs anywhere. Swap post_bulk for a real request to api.sume.com with your key.

def post_bulk(key, payload, _seen={}):
    if key in _seen and _seen[key] != payload:
        return 409, {"error": {"code": "idempotency_conflict"},
                     "details": {"queue_id": _seen[key + "_id"]}}
    _seen[key] = payload
    _seen[key + "_id"] = "frq_demo"
    return 202, {"data": {"id": "frq_demo"}}


def create(campaign, revision, payload):
    key = f"{campaign}-r{revision}"
    status, body = post_bulk(key, payload)
    if status == 409:
        return "conflict", body["details"]["queue_id"]
    return "queued", body["data"]["id"]


items = {"concurrency": 2, "items": [{"instruction": "a"}]}
print(create("holiday", 1, items))
print(create("holiday", 1, items))
print(create("holiday", 1, {"concurrency": 2, "items": []}))
print(create("holiday", 2, {"concurrency": 2, "items": [{"instruction": "b"}]}))

Sources

Related posts

More in Developers

All Developers posts

Written by Sume