ScreenshotNeo

BlogHow-to

How to Call a Screenshot API from Google Apps Script

Use Apps Script’s UrlFetchApp to request a screenshot, handle image or JSON responses, store credentials safely, and save the result to Google Drive.

By the ScreenshotNeo team4 October 202610 min read

Call a screenshot API from Google Apps Script with UrlFetchApp.fetch(). Build the request using the screenshot provider’s documented endpoint, authentication method, parameters, and response format. The provider may return image bytes, JSON containing a result URL, or an asynchronous job identifier; the code must match that contract.

This guide uses ScreenshotNeo for a complete, runnable example that returns image bytes directly, then shows the generic JSON pattern you can adapt to another provider. Google’s URL Fetch service makes outbound HTTP(S) requests and returns an HTTPResponse. Google’s UrlFetchApp reference · Google’s external APIs guide.

1. Set up Apps Script and store the API key

  1. Open a standalone or spreadsheet-bound Apps Script project.
  2. In Project Settings, add a script property named SCREENSHOTNEO_API_KEY with your ScreenshotNeo API key. Keep credentials out of source files and logs, and restrict access to the Apps Script project.
  3. When prompted, authorize the script to make external requests. If your manifest explicitly lists OAuth scopes, include https://www.googleapis.com/auth/script.external_request. This Apps Script permission is separate from the API key ScreenshotNeo requires.
  4. Use the API’s documented request format. ScreenshotNeo accepts a GET request with access_key and url parameters and returns the screenshot as the response body.

Google documents UrlFetchApp.fetch(url, params) and options including method, headers, contentType, payload, and muteHttpExceptions. Use only options required by the chosen provider. UrlFetchApp reference.

2. Complete example: get a screenshot and save it to Drive

This function makes a ScreenshotNeo GET request, checks the HTTP status and response headers, and saves the returned bytes as a WebP file in the script owner’s Google Drive. Set the script property from step 1 before running it.

function capturePageToDrive() {
  const apiKey = PropertiesService.getScriptProperties()
    .getProperty('SCREENSHOTNEO_API_KEY');
  if (!apiKey) throw new Error('Set SCREENSHOTNEO_API_KEY in Script Properties.');

  const targetUrl = 'https://stripe.com';
  const endpoint = 'https://api.screenshotneo.com/v1/shot';
  const query = [
    'access_key=' + encodeURIComponent(apiKey),
    'url=' + encodeURIComponent(targetUrl)
  ].join('&');

  const response = UrlFetchApp.fetch(endpoint + '?' + query, {
    method: 'get',
    muteHttpExceptions: true
  });
  const status = response.getResponseCode();
  const headers = response.getAllHeaders();

  if (status < 200 || status >= 300) {
    // Do not include the request URL: it contains the API key.
    throw new Error('Screenshot request failed with HTTP ' + status +
      ': ' + response.getContentText());
  }

  const verdict = headers['X-Page-Verdict'] || headers['x-page-verdict'];
  const billed = headers['X-Billed'] || headers['x-billed'];
  const blob = response.getBlob().setName('screenshot.webp');
  const file = DriveApp.createFile(blob);

  console.log('Saved file ID: ' + file.getId());
  console.log('Page verdict: ' + verdict + '; billed: ' + billed);
  return file.getId();
}

Replace the target URL with a page you are allowed to capture. The API key is sent as a query parameter because that is ScreenshotNeo’s documented request format; avoid logging the full URL. The response’s X-Page-Verdict and X-Billed headers report the page verdict and billing status. Refer to the ScreenshotNeo API documentation for current request details and supported options.

3. Generic JSON POST pattern for other providers

If your provider documents a JSON POST, serialize the body with JSON.stringify(), set contentType, and use the provider’s exact authentication header and schema. The endpoint and header below are placeholders; they are not a universal screenshot API contract.

function requestJsonScreenshotJob() {
  const apiKey = PropertiesService.getScriptProperties()
    .getProperty('SCREENSHOT_API_KEY');
  if (!apiKey) throw new Error('Set SCREENSHOT_API_KEY in Script Properties.');

  const endpoint = 'https://YOUR_PROVIDER.example/v1/screenshot';
  const requestData = { url: 'https://example.com' };
  const response = UrlFetchApp.fetch(endpoint, {
    method: 'post',
    contentType: 'application/json',
    headers: { 'X-API-KEY': apiKey }, // Replace with documented authentication.
    payload: JSON.stringify(requestData),
    muteHttpExceptions: true
  });

  const status = response.getResponseCode();
  const body = response.getContentText();
  if (status < 200 || status >= 300) {
    throw new Error('Provider returned HTTP ' + status + ': ' + body);
  }
  // Parse only if the provider documents a JSON response.
  return JSON.parse(body);
}

Google’s external APIs guide demonstrates JSON serialization and parsing. A provider may instead return image bytes, a URL, or a job ID. Use getBlob() for binary image data and parse text only when the response is documented as JSON. Google External APIs guide · UrlFetchApp reference.

4. cURL, Python, and Node.js equivalents

These examples make the same ScreenshotNeo request outside Apps Script. Keep your API key private in each environment.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

For Apps Script, use the first-party JavaScript examples above rather than attempting to run Node.js libraries there. Apps Script uses its own services such as UrlFetchApp, PropertiesService, and DriveApp.

5. Handle response types and save results

Direct image bytes

For a synchronous image response, check getResponseCode(), then save response.getBlob() with an appropriate extension. A successful status alone does not establish that the body is an image; when the provider documents a content-type header, inspect it before saving.

const contentType = response.getHeaders()['Content-Type'];
if (!contentType || !String(contentType).startsWith('image/')) {
  throw new Error('Expected image response; got ' + contentType);
}
const file = DriveApp.createFile(response.getBlob().setName('page.webp'));

JSON with an image URL

If the documented response contains a URL, parse the JSON and make a second fetch to retrieve the image. Check both requests for errors and follow the provider’s rules for signed URL expiration and authentication.

const result = JSON.parse(response.getContentText());
if (!result.image_url) throw new Error('Response has no image_url.');
const imageResponse = UrlFetchApp.fetch(result.image_url, {
  muteHttpExceptions: true
});
if (imageResponse.getResponseCode() < 200 ||
    imageResponse.getResponseCode() >= 300) {
  throw new Error('Image download failed: ' + imageResponse.getResponseCode());
}
const file = DriveApp.createFile(imageResponse.getBlob().setName('page.png'));

Asynchronous job ID

Some APIs return a job ID before the screenshot is ready. Follow that provider’s documented polling endpoint, interval, and terminal states, or use a webhook if offered. Do not assume an immediate image body or invent a polling route. Keep in mind Apps Script executions have a finite runtime; a later time-driven execution may be more suitable than sleeping and polling in one run.

6. Request options and configuration

Apps Script’s fetch options describe the HTTP request; screenshot controls are provider-specific parameters. Use only fields in the selected provider’s current documentation.

Need Apps Script setting Notes
HTTP method method: 'get' or 'post' Choose the method specified by the API. A GET request with query parameters is not interchangeable with a JSON POST.
Authentication headers or query parameters Use the provider’s documented API key or OAuth flow. ScreenshotNeo’s example uses the access_key query parameter.
JSON request body contentType: 'application/json', payload: JSON.stringify(data) Only for providers that document JSON POST.
Error inspection muteHttpExceptions: true Lets code inspect non-2xx response codes and bodies rather than relying on an exception alone.
Image response response.getBlob() Use for binary image output; do not JSON-parse image bytes.
JSON response response.getContentText() Parse only when the provider documents JSON and after checking status.

ScreenshotNeo supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets and custom viewports, retina scale, PDF settings, HTML/CSS to image, custom CSS and JavaScript, click-before-capture, selector hiding, waits, request and resource blocking, headers, cookies, user agent, Authorization, timezone, geolocation, transparent backgrounds, resizing, caching with a chosen TTL, signed links for public image tags, asynchronous jobs with signed webhooks, bulk capture up to 100 URLs per call, a usage API, and an OpenAPI spec. The parameter names used by other screenshot APIs also work, which can make switching easier. See the docs for parameter details. Apps Script still needs to respect its own request, header, size, and runtime limits.

7. Troubleshooting

Symptom Likely cause Fix
Authorization prompt or scope error The script has not been authorized for external requests, or an explicit manifest scope is missing. Run the function interactively and approve its requested permission. If scopes are declared in appsscript.json, include https://www.googleapis.com/auth/script.external_request.
HTTP 401 or 403 Missing, invalid, or insufficient provider credential; wrong authentication placement; or provider access restriction. Check the provider’s current authentication docs and key status. Keep Apps Script authorization and provider credentials distinct. Never print the key in logs.
HTTP 400 Malformed URL, missing required option, wrong parameter name, or JSON that was not serialized. Try the smallest documented request, encode query parameter values, and serialize JSON with JSON.stringify().
HTTP 429 Provider rate limit or account quota reached. Respect the provider’s rate-limit guidance, reduce concurrency or batch size, and retry later with bounded backoff if permitted.
JSON parse error The body is an image, HTML error page, or other non-JSON content. Check status and content type first. Use getBlob() for image bytes; parse text only for documented JSON responses.
Saved file is corrupt or has the wrong extension Binary content was treated as text, or the provider returned a different format than expected. Save the response blob and use the actual output format and content type documented by the service.
Timeout or script exceeds runtime Slow page rendering, long provider processing, or too many sequential captures. Request fewer captures per execution. For a provider with documented asynchronous jobs, submit now and retrieve results in a later execution or supported webhook flow.
Works locally but provider rejects source IP The provider allowlists IP addresses and Apps Script requests originate from Google’s IP address pool. Ask whether allowlisting is supported and consult Google’s published IP range information; do not assume Apps Script has a fixed outbound IP.
Response too large Large full-page image or encoded payload exceeds a platform or provider limit. Reduce capture dimensions or output size, choose a provider-supported format, or use a documented hosted result flow.

For diagnosis, log the status code, a safely truncated error body, and non-secret response headers. Avoid logging complete request URLs when credentials are query parameters.

8. Quotas, performance, reliability, and cost

Google currently lists URL Fetch quotas of 20,000 calls per day for consumer accounts and 100,000 for Workspace accounts, with a maximum six-minute script runtime per execution for both. It lists a 50 MB URL Fetch response and POST size per call, up to 100 headers, 8 KB of total header size, and a 2 KB URL length limit. Google says these quotas can change without notice, so check the current Apps Script quotas page before deployment. Quotas are per user and reset 24 hours after the first request, according to Google’s documentation.

  • Batch carefully: one capture usually means at least one outbound fetch. Large sequential batches can exceed execution time even before daily fetch quotas are reached.
  • Bound retries: retry only transient failures, use a small maximum attempt count with increasing delays, and do not retry permanent authentication or validation errors.
  • Keep responses small: full-page and retina captures can produce large files. Stay within the Apps Script response cap and any provider-specific response limit.
  • Design around async work: use a job and later retrieval pattern only when the API documents it. Avoid long waits that consume a six-minute execution.
  • Budget both systems: Apps Script quotas and screenshot-provider billing/rate limits are independent. Check the provider’s current price and billing rules before high-volume use.

ScreenshotNeo bills only clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response includes X-Page-Verdict and X-Billed headers. Its plans are Free for 1,000 shots per month with no card, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Check the ScreenshotNeo site for the product and current plan details.

9. Or skip the browser setup

If you want Apps Script to trigger a capture without managing a browser, call ScreenshotNeo’s screenshot endpoint. The full parameter reference is in the ScreenshotNeo docs.

function saveScreenshotNeoCapture() {
  const apiKey = PropertiesService.getScriptProperties()
    .getProperty('SCREENSHOTNEO_API_KEY');
  if (!apiKey) throw new Error('Set SCREENSHOTNEO_API_KEY in Script Properties.');

  const params = {
    access_key: apiKey,
    url: 'https://stripe.com'
  };
  const query = Object.keys(params).map(key =>
    encodeURIComponent(key) + '=' + encodeURIComponent(params[key])
  ).join('&');
  const response = UrlFetchApp.fetch(
    'https://api.screenshotneo.com/v1/shot?' + query,
    { muteHttpExceptions: true }
  );
  if (response.getResponseCode() < 200 || response.getResponseCode() >= 300) {
    throw new Error('ScreenshotNeo returned HTTP ' + response.getResponseCode());
  }
  return DriveApp.createFile(response.getBlob().setName('shot.webp')).getId();
}
  • Cookie banners are accepted like a visitor, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are never billed.
  • An MCP server gives AI agents such as Claude, Cursor, or any MCP client the take_screenshot, get_page_info, and capture_pdf tools.
  • 1,000 screenshots each month are free with no card; paid plans start at $5 for 3,000.

Sign up for ScreenshotNeo’s free plan and get 1,000 screenshots a month with no card.

10. Frequently asked questions

Can a time-driven Apps Script trigger take screenshots?

Yes. A trigger can run a function on a schedule, subject to Apps Script authorization, quotas, and runtime. Keep the API key in script properties and make each run small enough to finish within the execution limit.

Can Apps Script capture a page that requires login?

Only if the screenshot API supports the required authentication or browser state and you are authorized to access the page. Apps Script’s own Google sign-in does not automatically sign the screenshot provider’s browser into the target site.

Does every screenshot API use POST and return JSON?

No. Follow the chosen API’s documented method and response schema. Some return image bytes directly; others return a URL or asynchronous job result.

Can I put the API key in a spreadsheet cell?

A cell may be visible to spreadsheet editors and copied with the document. Script Properties keep it out of ordinary source code, but anyone with sufficient project access may still be able to change script behavior; restrict project access appropriately.