10 hooks by 10 endings: a 100-variant grid in one Sume bulk queue
A 10 by 10 hook and ending grid is exactly 100 items, the bulk queue maximum. How to build the items array, pick concurrency up to 16, and read the result.

A grid of 10 hooks by 10 endings is exactly 100 combinations, and 100 is the maximum number of items one Sume bulk queue accepts. POST /v1/formats/{handle}/{slug}/bulk-runs takes a concurrency from 1 to 16 and an items array of 1 to 100 entries, each the same body as a single Format run. So the full grid fits in one request, and anything larger, such as 10 hooks by 11 endings, needs a second queue.
The limits that shape a grid
These numbers come straight from the bulk runs page. The concurrency window is how many child runs stay in flight at once, and the server starts the next item whenever a slot frees.
| Field | Rule |
|---|---|
| concurrency | Required integer, 1 to 16 |
| items | Required array, 1 to 100 entries, in order |
| Each item | Same body as a single run; must name instruction, input, previous_run_id or attachments |
| Bad item | 400 invalid_request with details.index, before any queue exists |
| Queue webhook | None; set communication.webhook_url on each item |
| Cancel | No cancel-queue endpoint; cancel a child at POST /v1/format-runs/{run_id}/cancel |
Build the items array from the grid
Generate the array in code, not by hand, so that the item index maps cleanly to a hook and an ending. If you place hooks in the outer loop and endings in the inner loop, index i equals hook number times 10 plus ending number, and you can recover both from the index of any queue item. The script prints a body you can pipe to curl, and it uses an Idempotency-Key that names the batch.
Replace acme/spring-ad with a Format you can call, and keep each instruction short and concrete. The instruction field accepts up to 8000 characters, but a hook and an ending are a few sentences.
const hooks = Array.from({ length: 10 }, (_, i) => `Hook ${i + 1}: opening line ${i + 1}`);
const endings = Array.from({ length: 10 }, (_, i) => `Ending ${i + 1}: CTA ${i + 1}`);
const items = [];
for (const h of hooks) {
for (const e of endings) {
items.push({ instruction: `${h}. ${e}. Vertical 9:16, 6 seconds.` });
}
}
console.log(JSON.stringify({ concurrency: 8, items }));
console.error('items:', items.length);Send it and read the queue
Create the queue with a fresh Idempotency-Key per batch. The reply is 202 with a queue receipt whose first concurrency items are already running. Poll status_url, or GET /v1/format-run-queues/{id}, and read counts, which hold total, queued, running, completed, failed and canceled. The queue status becomes completed when every item is terminal, and that does not mean every item succeeded, so check counts.failed before you move on.
Each item row carries its index, status, run_id and error. Use the run_id to read the full receipt at GET /v1/format-runs/{run_id}. For a failed item that never started, run_id is null and error holds the reason; the rest of the queue continues.
- Replaying a spent key returns 202 with the old queue, so mint a new key for each new batch.
- If a queue grows past 100 items, split the grid by hook into two queues.
- Per-item spend caps matter: with no queue-level limit, the worst case is the number of items times the per-item cap.
Choosing the concurrency
Concurrency is a window, not a speed setting. Setting it to 16 puts 16 child runs in flight, and the workspace generation concurrency still applies to them, so a window larger than your workspace allows will not run faster. If every item took the same time, 100 items at a window of 16 would take 7 rounds, since 100 divided by 16 is 6.25 and rounds up. Real items vary, which is why the window slides instead of waiting for a full round.
Start with a smaller window such as 4 or 8 for a first batch to see failure rates, then raise it.
What the grid can and cannot tell you
A full factorial grid separates hook effects from ending effects, but it is also the most expensive design. A hundred variants is rarely what an ad platform's own test can power. Use the grid when you want a library of recombinations to choose from, and use a smaller one-factor test when you want an answer.
Validate before you send
A bad item fails the whole create with 400 invalid_request and a details.index that names the position, and Sume dispatches nothing. That is a gift for a generated grid: if item 57 is malformed, you learn it before anything is billed. Run a dry pass in your own code first, checking that every item has a non-empty instruction, and then let the API be the second check.
Remember the other early failures too. A service-account key cannot create Format runs or bulk queues and gets 403 insufficient_scope, and a team Format needs a key created in that workspace. Both are fixed by minting the right key, not by retrying.
When counts show every item terminal, split your results into completed, failed and canceled. Re-queue only the failed items in a new queue with a new key, and keep the index-to-grid mapping so that a retried item lands back in its hook and ending cell. A grid with a few holes is still useful, as long as your sheet knows which cells are holes.
Sources
Related posts
More in Developers
- Test a Sume webhook receiver with signed fixtures, no paid job needed
Generate sume-v1 signatures yourself and test six cases: good, rotated, reserialized, stale, empty-secret and unknown event. Python code that runs as is.
- Thai, Vietnamese, Indonesian speech to text: Sume STT language hints
Send th, vi or id as language_code to Sume STT, or omit it to auto-detect, then check language_probability. $0.01 per audio minute; test a sample first.
- Timeline output.fps: why a 24 fps clip judders when you force 30
Leave output.fps unset and Timeline renders at the source rate. Force 30 on a 24 fps clip and frames repeat; the job reports output_fps_resamples_sources.
- Transcribe audio with curl and jq: a Sume STT shell script
A 16-line bash script that submits audio to Sume STT, polls the job with curl, and prints every word with start and end times through jq. One cent per minute.
Written by Sume