Retry UrlFetch with Exponential Backoff

Flaky APIs and 429s are part of bootstrapper life. This helper wraps UrlFetchApp.fetch with exponential backoff so a single blip doesn't fail the whole job.

What you'll need

  • A URL (and optional options object) you'd normally pass to UrlFetchApp.fetch
  • Permission for the script to call external services
  • A sense of which failures are worth retrying (429 / 5xx / transient network errors — not "404 forever")

Why backoff instead of a tight loop

Hammering the same endpoint every 50ms makes rate limits worse. Exponential backoff waits longer after each failure (~0.5s, ~1s, ~2s, …) with a little jitter so overlapping scripts don't sync-retry as a herd.

How to use this snippet

function retryUrlFetch(url, options, maxAttempts) { maxAttempts = maxAttempts || 5; options = options || {}; var lastError; for (var attempt = 1; attempt <= maxAttempts; attempt++) { try { var response = UrlFetchApp.fetch(url, options); var code = response.getResponseCode(); // Treat rate limits and server errors as retryable when muteHttpExceptions is on if (code === 429 || code >= 500) { throw new Error('Retryable HTTP ' + code + ': ' + response.getContentText().substring(0, 200)); } return response; } catch (err) { lastError = err; if (attempt === maxAttempts) break; var waitMs = Math.min(30000, Math.pow(2, attempt - 1) * 500 + Math.floor(Math.random() * 250)); Utilities.sleep(waitMs); } } throw new Error('retryUrlFetch failed after ' + maxAttempts + ' attempts: ' + lastError); }

Tips:

  • Pass { muteHttpExceptions: true } in options if you want HTTP 429/5xx to come back as responses you can inspect (the helper still retries those codes).
  • Don't retry non-idempotent POSTs blindly unless the API is safe to replay — prefer idempotency keys when the provider supports them.
  • Cap attempts so a dead endpoint doesn't burn your whole execution budget.

Example

function fetchJsonWithRetry(url) { var response = retryUrlFetch(url, { method: 'get', muteHttpExceptions: true, headers: { Accept: 'application/json' } }, 4); return JSON.parse(response.getContentText()); }

Pair this with Script Properties for API keys (don't hardcode secrets) and you'll have a sturdier default for client integrations.

Happy Coding!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension