Python: turn a product CSV into Sume bulk Format queues

A short Python script splits a product manifest into batches of 100, builds one bulk body per batch with per-item spend caps, and keeps idempotency keys stable.

5 min readSume
All posts

Split your manifest into slices of at most 100 rows, build one body per slice with concurrency and a per-item generation_spend_cap_usd, and send each with its own stable Idempotency-Key. The script below does that with the standard library only. It prints the slice sizes so you can run it without a key, and the submit call is where the real request goes.

import json, os, urllib.request

URL = "https://api.sume.com/v1/formats/sume/sume-product-commercial/bulk-runs"

def chunks(rows, size=100):
    for i in range(0, len(rows), size):
        yield rows[i:i + size]

def body(batch, concurrency=4, cap=50):
    return {"concurrency": concurrency, "items": [
        {"instruction": r["brief"], "input": {"sku": r["sku"]},
         "generation_spend_cap_usd": cap} for r in batch]}

def submit(batch, key):
    req = urllib.request.Request(URL, json.dumps(body(batch)).encode(), {
        "Authorization": "Bearer " + os.environ["SUME_API_KEY"],
        "Content-Type": "application/json", "Idempotency-Key": key})
    with urllib.request.urlopen(req) as res:
        return json.load(res)["data"]["status_url"]

rows = [{"sku": f"A{n}", "brief": f"15s ad for item {n}"} for n in range(250)]
for i, batch in enumerate(chunks(rows)):
    print(i, len(batch))  # swap for submit(batch, f"fall-promo-{i}")

What the script does

  • chunks cuts the list at 100, the maximum number of items a bulk queue accepts.
  • body builds the envelope, which has only two keys: concurrency (1 to 16) and items. Each item is the same body as a single Format run, here an instruction, an input object you shape yourself and a cap.
  • submit posts it with Authorization: Bearer and an Idempotency-Key, then returns the status_url for polling.
  • The key is built from a label and the slice index, so rerunning the same manifest replays the same queue rather than creating a second one.

Rules the server enforces

Validate on your side first. The cheapest failure is the one that happens before a request: drop rows with an empty brief, make sure each item names at least one of the four accepted fields, and keep each input under the 2 MiB limit.

Bulk create validation (read 2026-10-07)
MistakeResponse
items empty or longer than 100400 invalid_request
concurrency outside 1 to 16400 invalid_request
An item with no instruction, input, previous_run_id or attachments400 invalid_request with details.index; no queue is created
Same key, different payload409 idempotency_conflict
Same key, same payload202 and the existing queue
Key without formats:write403 insufficient_scope

Make the input shape yours

input is a free-form object of up to 64 top-level keys and 2 MiB. The Format reads the keys it knows and the instruction wins where the two disagree. Read the Format first with GET /v1/formats/sume/{slug} to see its description and io profile, and put stable guidance in the instruction while row data goes in input.

After the submit

Store the queue id or status_url per slice, since there is no endpoint that lists queues. Poll until status is completed, then read counts.failed. For each item that has a run_id, fetch GET /v1/format-runs/{run_id} for the output. Items that failed before starting have run_id: null and an error code on the queue item itself.

Sources

Related posts

More in Formats

All Formats posts

Written by Sume