// Gmail · Apps Script

Fix "Specified permissions are not sufficient to call" in Apps Script.

When you declare oauthScopes in appsscript.json, Apps Script stops guessing which scopes your code needs and trusts only what you list. One missing scope entry produces the "specified permissions are not sufficient" error at runtime. This page shows how to find the exact scope URL and add it to the manifest.

I set an explicit oauthScopes list in my Apps Script manifest and now my script throws "specified permissions are not sufficient to call" even though it worked before.

The script

copy · paste · trigger
appsscript.json
Apps Script
// appsscript.json — explicit scope list
// Add every scope your script touches; Apps Script will NOT auto-detect
// once oauthScopes is present in the manifest.
{
  "timeZone": "America/New_York",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.googleapis.com/auth/gmail.send",
    "https://www.googleapis.com/auth/script.send_mail",
    "https://www.googleapis.com/auth/drive.readonly"
  ]
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

Why the error appears the moment you add oauthScopes

Apps Script has two modes for scope management. When appsscript.json contains no oauthScopes key, the runtime scans your source files at save time, infers every API surface you touch, and requests those scopes automatically at the OAuth consent screen. The moment you add an oauthScopes array, that scan is permanently disabled for the project. From then on, the runtime treats your list as the complete and authoritative declaration of what the script is allowed to do.

The trade-off is intentional: explicit scopes are required for Workspace Marketplace submissions, for scripts shared across an organization via the Admin console, and for any situation where you want to narrow the OAuth footprint. The cost is that you now own the list entirely. Call a service not on the list and you get the runtime error immediately, even if the service worked fine the day before you edited the manifest.

The first time I hit this, I had a working script that sent a daily digest via GmailApp.sendEmail and read rows from Sheets. I added oauthScopes to tighten the consent screen for a Marketplace review. The script broke on the next run with a wall of stack trace pointing at GmailApp, not at the manifest. The error message itself, read carefully, gives you the exact scope URL it wanted — that is the most useful part of the error.

Reading the error message to find the missing scope

The full error text looks like: Exception: Specified permissions are not sufficient to call Drive.Files.list. Required: https://www.googleapis.com/auth/drive. That URL at the end is the exact string to add to your oauthScopes array. Copy it verbatim.

Gmail-related calls produce two distinct scope URLs that trip people up separately. https://www.googleapis.com/auth/gmail.send covers GmailApp.sendEmail (direct Gmail API access). https://www.googleapis.com/auth/script.send_mail covers the Apps Script mail service, which is the layer most scripts actually call. If your script uses GmailApp, add both; if it only calls MailApp.sendEmail, the script.send_mail scope is sufficient on its own.

After editing appsscript.json, save the file and run any function in the script editor manually. The editor will detect that the authorized scopes no longer match the manifest and prompt a re-authorization dialog. You must complete that dialog — not just save and deploy — before the new scope list takes effect for the script's active tokens.

Forcing re-authorization after updating the manifest

Updating appsscript.json does not revoke the existing OAuth token automatically. The script continues running under the old token until something triggers a fresh consent flow. Running a function manually from the editor is the fastest way to force that dialog. Alternatively, go to Project Settings, revoke access under the account that owns the script, and run again.

For scripts deployed as add-ons or with a service account, token revocation works differently. Add-ons re-prompt the next time a user opens the host document after the manifest version bumps. Service accounts using domain-wide delegation need the new scope added to the delegation config in the Google Workspace Admin console before the service account token will carry it, regardless of what the manifest says.

One scope that is frequently omitted for no good reason is https://www.googleapis.com/auth/script.external_request. Any call to UrlFetchApp requires it, and the error it throws is the same pattern. If your script fetches external URLs as part of sending notifications or calling webhooks, add it to the list alongside the mail scopes.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Does removing oauthScopes from appsscript.json fix the error?
Yes, deleting the oauthScopes key re-enables automatic scope detection and the error goes away. The downside is that Apps Script will request broad inferred scopes at the consent screen again, which may be a problem if you need a narrow scope footprint for Marketplace review or admin deployment.
Why does https://www.googleapis.com/auth/gmail.send still fail after I add it?
GmailApp and MailApp use different underlying scope URLs. GmailApp.sendEmail needs gmail.send; MailApp.sendEmail needs script.send_mail. Adding only one of them leaves the other service unauthorized. Add both if you use both services, or check which class your code actually calls.
I updated the manifest but the error persists on the next run. Why?
The existing OAuth token is still active and does not carry the new scope. You need to force re-authorization: open the script editor, run any function manually, and complete the consent dialog that appears. Until that happens, the runtime enforces the old token's scope list.
Can I find all required scopes before running the script?
Partially. In the Apps Script editor, go to Project Settings and look at the current scopes detected from source. That list reflects what auto-detection would request. Cross-reference it against your oauthScopes array to find gaps. It does not cover runtime-conditional paths that auto-detection missed, so a manual test run after updating the manifest is still the definitive check.