Spreadsheet to Format bulk queue in Node: 100-row limit and key rules
Turn a CSV of products into one bulk-runs request in Node. Items are capped at 100, concurrency at 16, and the Idempotency-Key must be new for each batch.

To turn a spreadsheet into Format runs, read each row into one item and send the list to POST /v1/formats/{handle}/{slug}/bulk-runs. A queue takes 1 to 100 items and a concurrency of 1 to 16, so a longer sheet needs to be split into batches of 100. The Node script below does it for one batch with a header row. It uses the built-in fetch, which needs Node 18 or later.
The script
acme/sale-clip is a placeholder for a Format you own. The CSV is assumed to have two columns, sku,name, with no commas inside values; a real sheet needs a CSV parser. The key needs formats:write.
const fs = require("fs");
async function main() {
const rows = fs.readFileSync("sale.csv", "utf8").trim().split("\n").slice(1);
const items = rows.map((line) => {
const [sku, name] = line.split(",");
return { instruction: "15 second vertical sale clip", input: { sku, name },
generation_spend_cap_usd: 10 };
});
const res = await fetch("https://api.sume.com/v1/formats/acme/sale-clip/bulk-runs", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.SUME_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "sale-clips-" + new Date().toISOString().slice(0, 10) },
body: JSON.stringify({ concurrency: 4, items: items.slice(0, 100) }),
});
const body = await res.json();
if (!res.ok) throw new Error(JSON.stringify(body));
console.log(body.data.id, body.data.counts);
}
main().catch((e) => { console.error(e); process.exit(1); });
Rules that the script relies on
Each item has the same shape as a single run, so each can carry its own generation_spend_cap_usd. The table lists the queue's limits as of 2026-10-09.
| Item | Limit or rule |
|---|---|
items | 1 to 100 entries, in order |
concurrency | Required integer, 1 to 16 |
| Bad item | The whole create fails with 400 invalid_request and details.index; no queue exists |
| Webhook | None on the queue; set it per item |
| Replay of a spent key | 202 with the old queue, so use a new key per batch |
| Scope | formats:write to create, formats:read to poll |
After the request
A 202 returns data.id (frq_…) and counts. Poll GET /v1/format-run-queues/{id}. When the queue status is completed, all items are terminal, which is not the same as all succeeded, so check counts.failed and counts.canceled. To find why a row failed, read the child run at GET /v1/format-runs/{run_id}.
The date-based key in the script means a second run on the same day with the same body replays the old queue, which is what you want after a crash. For a deliberate re-run, change the key. For a sheet longer than 100 rows, add the batch number to the key.
Hardening the script
The script is short on purpose. For production, add four things. Parse the CSV with a real parser. Validate each row before you build the item, since one bad item fails the whole create with 400 invalid_request and details.index pointing at the row. Write the returned queue id and the row-to-index mapping to your database, because the queue items come back in the same order as you sent them. Split sheets over 100 rows into batches, each with its own key.
Poll status_url with a gap of a few seconds that grows toward a minute, like the single-run loop. Remember that the read budget is much larger than the write budget, but it is still per key. When the queue is completed, fetch the child receipts only for items with completed status, and handle the failed rows separately.
If the process stops after the create call, nothing is lost: the queue runs on the server. Read the frq_ id from your log, or send the same body with the same key to get the existing queue back with 202, and carry on polling from there.
Sources
Related posts
More in Formats
- Queue 100 Format runs at concurrency 16: 7 waves, one idempotency key
Sume bulk runs accept 1 to 100 items and a concurrency window of 1 to 16. The queue has no webhook, and completed does not mean all succeeded.
- Format Contents API: If-Match stops two agents overwriting each other
Per-file sha checks do not catch two agents editing different files. Send If-Match with package_sha and handle 409 format_package_sha_mismatch.
- Share a Format with another workspace: grants, accept and the 404
A Format owner can share a team Format with another workspace by grant. The other workspace accepts, calls it with its own key, and pays its own bill.
- Format input vs instruction: where scraped product copy should go
Put scraped or customer text in the input object, not in instruction. Sume writes input to a file and marks it as data. Limits: 64 keys, 2 MiB, 4000 characters.
Written by Sume