Bulk run has no empty item: skip sheet rows without a finished script
A Sume bulk item must name at least one of instruction, input, previous_run_id or attachments. Filter unfinished sheet rows on your side before you send.

There is no empty item in a Sume bulk queue. Each item needs at least one of instruction, input, previous_run_id or attachments, and an item with none of them fails the create with 400 invalid_request and the details.index of the bad row. So filter the sheet before you send, and skip rows that have no finished script.
What the validator does
The same rule applies to a single run, where {} and {"input": {}} are both 400. In a bulk body the failure is atomic: a bad item fails the create before a queue exists, and Sume dispatches nothing. Unknown top-level fields are also rejected, and { "concurrency": 3 } without items is 400, not an empty queue.
| Field | Rule |
|---|---|
| concurrency | Required integer, 1 to 16 |
| items | Required, 1 to 100 entries, in order |
| Each item | Names at least one of instruction, input, previous_run_id, attachments |
| Top level | Unknown fields rejected |
A filter that keeps your row numbers
Skipping rows shifts positions, and items[i].index is the position in what you submitted, not your sheet row. Keep a list of the sheet rows you sent, in order, so index 4 maps back to the right line.
- Drop rows where the script cell is blank; the live-commerce pattern in the docs does exactly this on the client side.
- Store
sheet_rowinside each item'sinputas well, so a child receipt can be matched even if your index list is lost. - If more than 100 rows pass the filter, split them into several queues, each with its own
Idempotency-Key.
Testing the filter
Run your filter on the real sheet and count what it drops before you queue. If 100 rows become 83 items, you want to know which 17 were skipped and why, and you want that list in your log, not only in your head.
Then send a pilot of three finished rows first. A pilot shows the receipt shape, the child spend and the wall time, and a bad row there costs a few dollars of generation instead of a full batch.
Remember that a queue completed status does not mean every item succeeded. Failures are recorded per item with run_id and error. Your row map is what lets you turn a failed index back into a sheet line and retry only that row.
Tradeoff
Rejecting the whole create is blunt, but it keeps a half-valid sheet from spending money. The cost is that you need a validation step. Running that step takes less time than a failed create at the end of a long data pull.
Sources
Related posts
More in Formats
- One dead image URL fails the whole 100-item Sume bulk create
Sume fetches every attachment at create time, so one broken image URL in a bulk body fails the create before any queue exists. Check URLs first.
- Commit several Sume Format files in one PUT: a change set
PUT a files list to a Sume Format's contents URL and it is one commit with one version bump. Files you do not name stay as they are. Existing paths need a sha.
- Create a Sume Format over the API: auto_init and the first package sha
POST /v1/formats with auto_init (default true) commits a minimal SKILL.md and returns package_sha, contents_url and vanity_invoke_url. Keep all three.
- enum and const in a Sume output schema: a status that cannot drift
Use enum and const in a Sume output_schema to pin a status field to values your publisher expects, since oneOf and allOf are rejected. Examples that pass.
Written by Sume