Year-in-review videos for 250 customers: bulk queues of 100, 100, 50
A Sume bulk run takes at most 100 items, so 250 year-in-review runs need three queues. Set concurrency 1 to 16, a spend cap per item and a new key per queue.

Two hundred and fifty customers is three bulk queues on Sume: 100 + 100 + 50, because POST /v1/formats/{handle}/{slug}/bulk-runs accepts 1 to 100 items per request. Each queue takes a concurrency of 1 to 16, which is how many child runs stay in flight at once. Each item is an ordinary Format run, so each carries its own input, spend cap and webhook.
A personalised year-in-review is the case bulk runs were built for: the recipe is fixed and the data changes row by row. Keep the data in input as plain JSON rather than rewriting a prompt per customer.
The limits that shape the plan
These come from the Sume Formats bulk-runs and call docs.
| Limit | Value | What it means for 250 customers |
|---|---|---|
| Items per queue | 1 to 100 | Three queues |
| Concurrency | 1 to 16 per queue | Pick 4 to 8 to stay gentle on workspace concurrency |
input size | 64 top-level keys, 2 MiB | Per-customer stats fit easily |
| Queue webhook | None; webhook is per item | Set communication.webhook_url on each item, or poll the queue |
| Idempotency | A spent key returns the old queue | Use one key per queue, never reuse one for changed data |
Chunk and submit
The script splits the list and gives each queue its own key. Replaying a run of the same script with the same keys returns the same queues instead of making new ones, which is what you want after a crash and what you do not want after a data change, so include a data version in the key.
import asyncio, os
import httpx
def chunks(rows, n=100):
for i in range(0, len(rows), n):
yield rows[i:i + n]
async def main():
rows = [{"customer_id": f"c{i}", "orders": 12, "favorite": "Oat latte"} for i in range(250)]
headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
url = "https://api.sume.com/v1/formats/acme/year-in-review/bulk-runs"
async with httpx.AsyncClient(headers=headers, timeout=60) as client:
for n, part in enumerate(chunks(rows), start=1):
items = [{"input": r, "generation_spend_cap_credits": 400} for r in part]
resp = await client.post(url, json={"concurrency": 6, "items": items},
headers={"Idempotency-Key": f"yir-2026-v1-q{n}"})
resp.raise_for_status()
print(n, resp.json()["data"]["id"])
asyncio.run(main())Poll the queues and read the result
Poll GET /v1/format-run-queues/{id}. A queue reports completed when every item is terminal, not when every item succeeded, so read counts.failed and re-queue the failed rows under a fresh key. A bad item fails the whole create with 400 invalid_request and an index before anything runs, so validate rows locally first.
A 250-row job is also the place to decide the cap per item before the run, not after. A cap that is too low fails the item without finishing the video; a cap that is high lets one bad row use a large part of the budget.
- Validate that each row has a name and a number before you submit.
- Store the queue id and the run ids against your customer ids.
- Send the customer link only after a run is terminal and successful.
Related posts
More in Developers
- Which MCP server lets Claude Code or Cursor generate video and images?
MCP servers that let Claude Code and Cursor make video and images: Sume, fal, Replicate, Runway, Higgsfield. Endpoints, sign-in, billing, setup.
- Idempotency keys for AI video APIs: retry without paying twice
An idempotency key makes a retried create return the original run or job instead of a second paid one. How Sume's Idempotency-Key works on each API.
- Signed webhooks for Sume video runs: events, retries, verification
Sume sends one HMAC-SHA256 signed POST when a Format, Action, or Agent Completion run completes or fails. Verify the raw body and dedupe on request_id.
- Spend caps for unattended AI agents: how Sume bounds each run
An unattended agent has no one to approve spend, so Sume caps generation per run: required on Agent Completions, and up to $500 on Format runs.
Written by Sume