Google Sheets onEdit trigger: call an API when a row changes

A simple onEdit trigger can't call UrlFetchApp, which needs authorization. Use an installable edit trigger to call an API from a Sheets edit.

5 min readSume
All posts

A simple onEdit(e) trigger in Google Sheets runs when a user changes a cell, but it can't use services that require authorization, and UrlFetchApp requires the script.external_request scope, so an API call from it fails. Use an installable edit trigger instead: it can call services that require authorization and runs under the account of the person who created it.

Google's rules below are quoted from its simple triggers and installable triggers guides, and the API side from Sume's Create a run docs, all read on 2026-09-28. Sume has no Google Sheets add-on; the handler makes a plain HTTPS call.

Why doesn't my onEdit trigger call the API?

Simple triggers fire without asking the user for authorization, so Google restricts what they can do. The installable version lifts the authorization limit but keeps the rule that matters most for a row-driven workflow: only a person's edit fires it.

  • e.value is set only when a single cell was edited, so a paste over several cells won't match the check in the code below.
  • Because script writes don't fire triggers, the handler can write the returned run id back into the row without firing itself again.
  • Rows written by another script or through an API request won't fire either trigger. For those, poll on a time-driven trigger as in Google Sheets to video automation with Apps Script.
  • For structure changes such as a new sheet or a removed column, Google offers an installable change trigger; its event carries a changeType such as INSERT_ROW.
From Google's Simple triggers, Installable triggers, and Quotas pages, read 2026-09-28.
RuleSimple `onEdit(e)`Installable edit trigger
Services that require authorizationCan't access themCan call them
Whose authorizationNone: it fires without asking the userThe account of the person who created it
Runtime30 seconds at mostScript runtime quota: 6 min / execution
Fired by script executions and API requestsNoNo
File opened read-only (view or comment)Doesn't runDoesn't run

How do I set up an installable edit trigger?

In the Apps Script editor, click Triggers, then Add Trigger, pick your function and the spreadsheet's on-edit event, and save. Or run ScriptApp.newTrigger("onRowEdit").forSpreadsheet(SpreadsheetApp.getActive()).onEdit().create() once, as in Google's SpreadsheetTriggerBuilder reference. Store the API key as a script property, not in a cell; script properties are shared by all users of the script, and Sume's Authentication docs say keys stay out of frontend JavaScript and screenshots.

The handler below runs when someone types go in column D of a Videos tab. Column A holds a stable row id, B the topic, C a version number you bump to make a new video on purpose.

const RUNS_URL = "https://api.sume.com/v1/formats/acme/daily-short/runs";

function onRowEdit(e) {
  const sheet = e.range.getSheet();
  if (sheet.getName() !== "Videos" || e.range.getColumn() !== 4 || e.value !== "go") return;
  const row = e.range.getRow();
  const [id, topic, version] = sheet.getRange(row, 1, 1, 3).getValues()[0];
  const res = UrlFetchApp.fetch(RUNS_URL, {
    method: "post",
    contentType: "application/json",
    headers: {
      Authorization: "Bearer " + PropertiesService.getScriptProperties().getProperty("SUME_API_KEY"),
      "Idempotency-Key": "videos-" + id + "-v" + version,
    },
    payload: JSON.stringify({ input: { topic: topic }, generation_spend_cap_usd: 5 }),
    muteHttpExceptions: true,
  });
  const body = JSON.parse(res.getContentText());
  sheet.getRange(row, 5).setValue(body.data ? body.data.id : body.error.code);
}

What should the handler send to the API?

One POST /v1/formats/{handle}/{slug}/runs, with the row's cells as data. Sume answers 202 with the run's receipt while the run is still queued, so the handler stores data.id and stops.

  • Put the cells in input, a JSON object of caller data up to 64 top-level keys. You choose the shape and the Format reads the keys it knows. acme/daily-short stands in for your own Format.
  • Build the Idempotency-Key from the row, not the moment. Sume's docs say to derive it from the thing being made plus a version you bump. Retyping go on an unchanged row sends the same key and body and gets 200 with the original run: no second run, no second charge.
  • If someone edits the topic but not the version, the same key arrives with a different body and Sume answers 409 idempotency_conflict, and nothing runs. The code writes that code into column E, the cue to bump the version.
  • Set contentType to application/json: UrlFetchApp defaults to application/x-www-form-urlencoded. With muteHttpExceptions on, a failure returns the response instead of throwing.
  • Send Authorization: Bearer or x-api-key, never both: a request with two credentials is 401 unauthorized.

How do I get the finished video back into the sheet?

Not from the edit trigger, which gets at most 6 minutes per execution; Sume's docs put long-form video at 15 to 30 minutes of work. Poll GET /v1/format-runs/{run_id} for the stored ids from a time-driven trigger and write primary_output_url when the run is completed (Runs and results). That URL is a durable media.sume.com file, public to anyone holding it, so anyone who can read the sheet can open the video. The published bulk Sheets post has a polling function you can reuse, and explains why an Apps Script web app is a poor webhook target.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume