Apps Script UrlFetch 20,000/day and 6 min: size a Sume sheet run
Apps Script allows 20,000 UrlFetch calls a day and 6 minutes per run on a consumer account. How to size a Sume sheet batch inside both limits.

A consumer Google account can make 20,000 UrlFetch calls a day from Apps Script, and one execution stops at 6 minutes. A Sume sheet run fits easily if each row costs one submit call with mode: "async" and you read the result later, and it breaks if every row waits for its video inside the same execution.
This post sizes a batch against the limits on Google's own quota page, then shows the submit loop that stays inside them. For the full sheet-to-video build, start with Google Sheets to video automation with Apps Script and Sume.
What are the Apps Script limits that matter for an API loop?
Google lists the limits per account type, and says quotas reset 24 hours after the first request and can change without notice. Check the page before you size a production batch.
| Quota | Consumer account | Google Workspace account |
|---|---|---|
| URL Fetch calls per day | 20,000 | 100,000 |
| Script runtime per execution | 6 min | 6 min |
| Triggers total runtime per day | 90 min | 6 hr |
| Simultaneous executions per user | 30 | 30 |
| URL Fetch response size | 50 MB per call | 50 MB per call |
Why does a sync wait on every row break the 6-minute limit?
Sume's sync mode holds the HTTP request for up to wait_timeout_seconds, which is capped at 30 seconds. Ten rows that each wait the full 30 seconds already use 5 of your 6 minutes, and video jobs usually outlast that wait anyway. The jobs and results docs say a timed-out wait still returns a job id and that you must poll rather than resubmit.
So the sheet should never wait. Submit with mode: "async", write the job id into a column, and let a second, cheap trigger read status. The job keeps running whether or not your script is alive.
How do I budget the 20,000 daily calls?
Count calls per row: one submit, plus the status reads you make until the job is terminal. The next_poll_after_seconds field on the envelope tells you when to look again, so you do not need a tight loop.
As an illustration only, 100 rows with one submit and 15 status reads each is 1,600 calls, about 8 percent of the consumer day. The real count depends on your models and how often you poll, so measure one row first.
If the 90-minute trigger budget is the tighter limit, move the polling off Apps Script: a Sume webhook delivers the terminal event to a URL you control, and 10 attempts at a 30-second default spacing cover short outages.
What does the submit loop look like?
This reads rows with no job id, submits each with an Idempotency-Key built from the row number, and stores the returned job id. A re-run after a timeout returns the original job rather than billing a second one, because Sume reuses the job for the same key. It assumes the key is in Script Properties as SUME_API_KEY and that column A holds the prompt and column B the job id.
function submitRows() {
const key = PropertiesService.getScriptProperties().getProperty('SUME_API_KEY');
const sheet = SpreadsheetApp.getActiveSheet();
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
if (!rows[i][0] || rows[i][1]) continue;
const res = UrlFetchApp.fetch('https://api.sume.com/v1/image-1.0/generate', {
method: 'post',
contentType: 'application/json',
muteHttpExceptions: true,
headers: { Authorization: 'Bearer ' + key, 'Idempotency-Key': 'sheet-row-' + (i + 1) },
payload: JSON.stringify({ prompt: rows[i][0], mode: 'async' }),
});
if (res.getResponseCode() === 429) break; // back off, retry on the next trigger
const body = JSON.parse(res.getContentText());
if (body.request_id) sheet.getRange(i + 1, 2).setValue(body.request_id);
}
}What happens when a run hits the 6-minute wall?
Apps Script stops the execution, and nothing about that stops the jobs you already submitted. That is the useful property of async submits: a job is durable the moment Sume returns its id, so a half-finished loop loses only the rows it never reached. The Idempotency-Key per row means the next trigger can start from the top and the rows already submitted return their original jobs instead of new paid ones.
Two habits keep this clean. First, write the job id to the sheet immediately after each submit, not at the end of the loop. Second, stop the loop yourself at about five minutes using Date.now() so the script ends on your terms rather than by a timeout. Sume's docs are clear that a client-side timeout does not cancel the job; it only stops you watching it, and the job keeps billing until it finishes or you cancel it with POST /v1/jobs/{id}/cancel before generation starts.
When should I use a Format bulk queue instead?
If every row runs the same saved recipe, POST /v1/formats/{handle}/{slug}/bulk-runs takes 1 to 100 items with a concurrency window of 1 to 16 and returns one queue id. You then poll GET /v1/format-run-queues/{id} once instead of once per row, which saves UrlFetch calls. The details are in bulk runs and Format bulk runs for 100 renders.
Sume does not push progress into the sheet for you. The queue has no webhook of its own, so something on your side has to poll or receive per-item webhooks.
Sources
Related posts
More in Integrations
- Make an avatar video from an MCP agent: tools, dry run, spend cap
The hosted MCP server of Sume exposes avatars_list, avatar-videos_create and jobs_wait. How dry_run and max_spend_usd gate a paid call, and the scope you need.
- Poll a Sume job from a CI step with curl and jq: exit codes
A 23-line bash script that polls /v1/jobs/{id}/status and exits 0, 1, 2 or 3, so a CI step can tell done, failed, still running and a bad request apart.
- Bun.serve webhook receiver for Sume: raw body, verify, dedupe
A 24-line Bun.serve receiver for Sume job webhooks: read the raw body first, verify sume-v1, dedupe on job_id, answer 204 before the work. Tested on Bun 1.4.
- Claude Code MCP whitespace warning: a pasted Sume key with a newline
Claude Code warns when an MCP header or url has leading or trailing whitespace, often a pasted token with a newline. It does not trim it. Fix a Sume entry.
Written by Sume