Google Sheets to video automation with Apps Script and Sume
Turn up to 100 sheet rows into Sume video runs with one Apps Script request, then poll the queue from a time-driven trigger and write URLs back.

To automate video from Google Sheets, have Apps Script send the rows to Sume as one bulk run: a single UrlFetchApp.fetch POST to /v1/formats/{handle}/{slug}/bulk-runs queues up to 100 rows as Format runs, and a time-driven trigger polls the queue and writes each finished video URL back to its row.
Sume has no Google Sheets add-on; this is a plain HTTPS call from your own script. The Sume facts come from the Bulk runs and Runs and results docs, and the Apps Script facts from Google's reference pages, all read on 2026-09-27. For the request contract on its own, see Sume Format bulk runs.
How do I send the sheet to Sume from Apps Script?
Keep the API key out of cells and out of the code. Add it as a script property on the project settings page. Script properties are shared among all users of the script, so share the project only with people who may hold the key. Sume's docs add that keys never go into frontend JavaScript, support tickets, or screenshots.
The function below reads rows 2 to 101 of a Videos tab (instruction in A, image URL in B) and posts them as one queue. acme/product-promo stands in for your own Format's handle and slug. Sume publishes no field list for input, so send the keys your Format reads.
Sume's cookbook says to keep your own sheet-row ↔ index map, because items[i].index is the position you submitted. The code sends every row in order, so a row is always index + 2. Keep the rows contiguous: a blank row inside the range is still sent as an item.
- Send
contentType: "application/json". UrlFetchApp defaults toapplication/x-www-form-urlencoded. muteHttpExceptions: truemakesfetchreturn the response instead of throwing when the status code signals a failure, so you can log Sume's error body.- The
Idempotency-Keycomes from aBATCHproperty you bump for each new batch, so re-runningsubmitSheeton an unchanged batch returns the existing queue with202instead of starting a second one. - The script tracks one batch at a time: one
QUEUE_ID, anddropTriggerremoves the old polling trigger before a new one is installed.
const API = "https://api.sume.com/v1";
const props = PropertiesService.getScriptProperties();
function submitSheet() {
const sheet = SpreadsheetApp.openById(props.getProperty("SHEET_ID")).getSheetByName("Videos");
const rows = sheet.getDataRange().getValues().slice(1, 101); // row 1 is the header
const items = rows.map((r) => ({ instruction: r[0], input: { url: r[1] } }));
const res = UrlFetchApp.fetch(API + "/formats/acme/product-promo/bulk-runs", {
method: "post",
contentType: "application/json",
headers: {
Authorization: "Bearer " + props.getProperty("SUME_API_KEY"),
"Idempotency-Key": "sheet-batch-" + props.getProperty("BATCH"),
},
payload: JSON.stringify({ concurrency: 4, items: items }),
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 202) throw new Error(res.getContentText());
props.setProperty("QUEUE_ID", JSON.parse(res.getContentText()).data.id);
dropTrigger(); // a rerun must not leave a second polling trigger behind
const trigger = ScriptApp.newTrigger("pollQueue").timeBased().everyMinutes(5).create();
props.setProperty("TRIGGER_ID", trigger.getUniqueId());
}How do I poll the queue without a long-running script?
Don't wait inside submitSheet. Google stops a script after 6 minutes per execution, and Sume's docs say each child is minutes of work when the Format makes video. So submitSheet installs a time-driven trigger, and pollQueue reads GET /v1/format-run-queues/{queue_id} on every tick.
everyMinutes(n)accepts only 1, 5, 10, 15, or 30 (ClockTriggerBuilder). Sume's docs note that polling a queue every second buys nothing and costs rate limit.- Queue
completedmeans every item is terminal, not that every item succeeded. A row whose run failed gets its item status instead of a URL; read why on the child receipt atGET /v1/format-runs/{run_id}. - A
429or503during a poll is transient and the queue keeps working, so the function returns and tries again on the next tick. Any other error throws, so it reaches you as a failure email. primary_output_urlis a durablemedia.sume.comURL. It does not expire and is public to anyone holding it, so anyone who can read the sheet can open the videos.
function pollQueue() {
const auth = { Authorization: "Bearer " + props.getProperty("SUME_API_KEY") };
const read = (path) => {
const res = UrlFetchApp.fetch(API + path, { headers: auth, muteHttpExceptions: true });
const code = res.getResponseCode();
if (code === 429 || code === 503) return undefined; // transient: try the next tick
if (code !== 200) throw new Error(res.getContentText()); // Apps Script emails the failure
return JSON.parse(res.getContentText()).data;
};
const queue = read("/format-run-queues/" + props.getProperty("QUEUE_ID"));
if (!queue || queue.status !== "completed") return; // a 429/503, or still running
const sheet = SpreadsheetApp.openById(props.getProperty("SHEET_ID")).getSheetByName("Videos");
for (const item of queue.items) {
const run = item.run_id ? read("/format-runs/" + item.run_id) : null;
if (run === undefined) return; // a 429/503: rewrite every row on the next tick
sheet.getRange(item.index + 2, 3).setValue((run && run.primary_output_url) || item.status);
}
dropTrigger(); // every row is written
}
function dropTrigger() {
for (const t of ScriptApp.getProjectTriggers()) {
if (t.getUniqueId() === props.getProperty("TRIGGER_ID")) ScriptApp.deleteTrigger(t);
}
}Why not have Sume call an Apps Script web app instead?
The queue itself has no webhook; only each item's communication.webhook_url does. You could point that at an Apps Script web app, but content returned by the Content service is redirected to a one-time URL at script.googleusercontent.com. Sume does not follow redirects and counts a 3xx as a failed delivery attempt.
A trigger that polls needs no public endpoint, which makes it the pattern that works with Apps Script alone. For per-run webhooks, run a receiver outside Apps Script, as in Sume Format run lifecycle.
What are the limits on each side?
Google's quotas are per user and reset 24 hours after the first request, and an installable trigger always runs under the account of the person who created it.
- When a triggered function throws, no error appears on screen. Apps Script sends a failure-summary email instead, with a link to deactivate or reconfigure the trigger.
- To queue only the rows that failed, see Retry failed items in an AI video batch.
| Limit | Value | Side |
|---|---|---|
| Items per bulk request | 1–100 | Sume |
Child runs in flight (concurrency) | 1–16 | Sume |
| Script runtime | 6 min per execution | |
| Triggers total runtime | 90 min/day (consumer), 6 hr/day (Workspace) | |
| URL Fetch calls | 20,000/day (consumer), 100,000/day (Workspace) | |
| Triggers | 20 per user per script |
Sources
- Bulk runs
- Runs and results
- Format cookbook
- Authentication
- Google Apps Script: Class UrlFetchApp (read 2026-09-27)
- Google Apps Script: Quotas for Google Services (read 2026-09-27)
- Google Apps Script: Class ClockTriggerBuilder (read 2026-09-27)
- Google Apps Script: Installable triggers (read 2026-09-27)
- Google Apps Script: Properties Service (read 2026-09-27)
- Google Apps Script: Content Service (read 2026-09-27)
- Google Apps Script: Class SpreadsheetApp (read 2026-09-27)
Related posts
More in Integrations
- Gradio video generation app: Sume API key in a Space secret
Build a Gradio video generation app on the Sume API: the key stays in a Hugging Face Space secret, a generator polls the job, and gr.Video plays it.
- Inngest wait for event: resume when a Sume video run ends
Start a Sume run in step.run, turn its webhook into an Inngest event with a transform, then step.waitForEvent on the run id with a 2h timeout.
- LangChain video generation tool that runs a Sume Format
A LangChain @tool can start a Sume Format run, cap its spend, key it for safe retries, and return a run id the agent checks until the video is ready.
- LlamaIndex image generation tool with the Sume Image API
Wrap POST /v1/images in a LlamaIndex FunctionTool: send sume/auto and a prompt, return URLs on a 200, and hand back the job id on a 202.
Written by Sume