UrlFetchApp
UrlFetchApp.fetch(url, options) makes a synchronous HTTP request and returns a response: response.getContentText() for the body and getResponseCode() for the status. Pass {method: "post", contentType: "application/json", payload: JSON.stringify(data), headers: {Authorization: "Bearer ..."}, muteHttpExceptions: true} for a JSON POST. Parse with JSON.parse.
Syntax
UrlFetchApp.fetch(url, {method, headers, contentType, payload, muteHttpExceptions})| Part | What it is |
|---|---|
method | get (default), post, put, patch, delete |
headers | Object of header names to values, for example Authorization |
payload | Request body; a string (JSON) or an object (form-encoded) |
muteHttpExceptions | true to get the response for 4xx and 5xx instead of a thrown error |
UrlFetchApp.fetchAll(requests) | Run many requests in parallel; array of {url, ...options} |
Examples
const res = UrlFetchApp.fetch("https://api.exchangerate.host/latest?base=USD");
const data = JSON.parse(res.getContentText());
Logger.log(data.rates.EUR);Result: A number such as 0.92 (the EUR rate in the response)
const options = {
method: "post",
contentType: "application/json",
headers: {Authorization: "Bearer " + PropertiesService.getScriptProperties().getProperty("API_KEY")},
payload: JSON.stringify({name: "Ana", email: "ana@example.com"}),
muteHttpExceptions: true
};
const res = UrlFetchApp.fetch("https://api.example.com/contacts", options);
Logger.log(res.getResponseCode() + " " + res.getContentText());Result: 201 {"id":123,"name":"Ana"} (or the error body with its code instead of an exception)
const items = JSON.parse(UrlFetchApp.fetch(url).getContentText());
const rows = items.map(it => [it.id, it.name, it.price]);
sheet.getRange(2, 1, rows.length, 3).setValues(rows);Result: API records written as rows with id, name and price
const reqs = ids.map(id => ({url: "https://api.example.com/items/" + id, muteHttpExceptions: true}));
const responses = UrlFetchApp.fetchAll(reqs);
Logger.log(responses.map(r => r.getResponseCode()));Result: [200, 200, 404] (one code per request, fetched in parallel)
Common mistakes
- Exception: Request failed for ... returned code 401: the API key or token is missing or wrong; put it in Script Properties and send it in the header the API expects.
- The API receives an empty body: you passed an object as payload without contentType application/json, so it was form-encoded; use JSON.stringify and set contentType.
- Unexpected token < in JSON: the response is HTML (an error page or login page); log getContentText and getResponseCode before parsing.
- Address unavailable or blocked: UrlFetchApp cannot reach localhost or private networks; the API must be public on the internet.
Practice UrlFetchApp
These drills check your answer on live data, free, no account needed.
- Retry a failing callCall `fn()` until it succeeds, at most `attempts` times, and resolve with its result. `fn` may return a Promise or a plain value, and it may throw or reject. If every attempt fails, reject with the error from the last attempt.
- Promise.all with a concurrency limitLike `Promise.all(items.map(fn))`, but with at most `limit` calls of the async function `fn(item)` running at the same time. Resolve with the results in the order of `items`. Start the next item as soon as any running one finishes. If a call rejects, reject. Tests run on a fake clock: `setTimeout`, `clearTimeout` and `Date.now` are simulated, so waits are instant and exact.
- Timeout wrapperReturn a Promise that settles like `promise` if it settles within `ms` milliseconds. Otherwise reject with `new Error("Timed out")` at `ms`. Clear your timer once the result is known, so nothing is left pending. Tests run on a fake clock: `setTimeout`, `clearTimeout` and `Date.now` are simulated, so waits are instant and exact.
Questions
Is UrlFetchApp asynchronous?
No, fetch blocks until the response arrives. For many requests, use fetchAll, which runs them in parallel and returns all responses at once.
How do I send form data instead of JSON?
Pass payload as a plain object ({a: 1, b: 2}) without contentType; Apps Script encodes it as application/x-www-form-urlencoded.
What is the quota?
20,000 UrlFetch calls per day on free accounts and 100,000 on Workspace, with a 50 MB response limit per call.
Related
- JSON.parse and JSON.stringifyUse JSON.parse and JSON.stringify in Google Apps Script: turn an API response into objects, write nested fields to a sheet, store objects as properties.
- PropertiesServiceStore settings and secrets in Google Apps Script with PropertiesService: script, user and document properties, setProperty and getProperty, JSON values, keys.
- try, catch and finallyHandle errors in Google Apps Script with try catch finally: catch exceptions from sheets, email and APIs, read err.message and err.stack, log and rethrow.
- async, await and Promises in Apps ScriptAsync and Promises in Google Apps Script: the V8 runtime accepts async await syntax but every Google service is synchronous, so use UrlFetchApp.fetchAll.
Previous: Dialogs, prompts and sidebarsNext: Send email from a sheet