How to Call a Screenshot API from an Apps Script Project in India
Call a screenshot API from Google Apps Script with UrlFetchApp, save the image to Drive, handle errors, and render pages as visitors in India see them.
Short answer: use UrlFetchApp.fetch() to make an HTTPS request to a screenshot API, check the HTTP status before treating the response as an image, and save the returned blob where your script needs it. Apps Script sends outbound requests from Google’s network infrastructure; being in India does not require a different Apps Script request pattern. If the target website varies by visitor location, use the provider’s supported regional-rendering option. Google’s UrlFetchApp reference documents external HTTP and HTTPS requests and the required scope for explicitly scoped projects.
This walkthrough uses ScreenshotOne as a documented example provider for the do-it-yourself integration. Its request contract, options, and errors are provider-specific. The India setting below controls the location used to render the target page; it does not establish where an Apps Script execution originates or whether a provider supports India-based signup or payment.
1. Create the Apps Script project and authorize external requests
- Open Google Apps Script and create a project.
- If the project uses an explicit OAuth scope list in
appsscript.json, includehttps://www.googleapis.com/auth/script.external_request. Apps Script usually detects required scopes automatically, but an explicit list must include this scope. - Store the provider API key in Script Properties instead of committing it in source code. In the editor, open Project Settings, enable script properties if needed, and add
SCREENSHOT_API_KEYwith your key as its value. The example reads it withPropertiesService. - Run the function once from the editor and grant the requested authorization.
For a project with a manually maintained manifest, the relevant portion can look like this (merge with any existing manifest fields and scopes):
{
"timeZone": "Asia/Kolkata",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/script.external_request",
"https://www.googleapis.com/auth/drive"
]
}
The Drive scope is needed only by the Drive-saving example below. If your script has no explicit oauthScopes list, Apps Script generally determines scopes from the code.
2. Make a screenshot request and save its binary response
ScreenshotOne documents a POST JSON request to https://api.screenshotone.com/take and supports the API key in an X-Access-Key header. A normal screenshot response is binary image data; error responses are JSON. This runnable example checks the status before saving the body to Drive.
function capturePageToDrive() {
const apiKey = PropertiesService.getScriptProperties()
.getProperty('SCREENSHOT_API_KEY');
if (!apiKey) {
throw new Error('Set SCREENSHOT_API_KEY in Script Properties first.');
}
const requestBody = {
url: 'https://example.com',
format: 'png',
viewport_width: 1365,
viewport_height: 900
// To render the target as seen from India, add: ip_country_code: 'in'
};
const response = UrlFetchApp.fetch('https://api.screenshotone.com/take', {
method: 'post',
contentType: 'application/json',
headers: { 'X-Access-Key': apiKey },
payload: JSON.stringify(requestBody),
muteHttpExceptions: true
});
const status = response.getResponseCode();
if (status < 200 || status >= 300) {
// The provider returns JSON errors. Include the status for diagnosis.
throw new Error('Screenshot API returned HTTP ' + status + ': ' +
response.getContentText());
}
const imageBlob = response.getBlob().setName('example-screenshot.png');
const file = DriveApp.createFile(imageBlob);
Logger.log('Saved screenshot: ' + file.getUrl());
return file.getUrl();
}
The request shape and endpoint follow ScreenshotOne’s getting-started documentation. This example is documentation-based; it has not been executed as part of this article. Change the target URL and filename to suit your task. The provider returns an image blob for a successful image-format request, so do not use getContentText() on success; binary image bytes are not text.
3. Return the image instead of saving it to Drive
If another Apps Script function needs the image, return the blob directly. For example, a time-driven job can save it to another destination, while a spreadsheet workflow can attach it to an email. Do not attempt to return a binary blob from a web app as if it were an HTML page; web app output has its own content-serving constraints.
function capturePageBlob() {
const apiKey = PropertiesService.getScriptProperties()
.getProperty('SCREENSHOT_API_KEY');
if (!apiKey) throw new Error('Missing SCREENSHOT_API_KEY.');
const response = UrlFetchApp.fetch('https://api.screenshotone.com/take', {
method: 'post',
contentType: 'application/json',
headers: { 'X-Access-Key': apiKey },
payload: JSON.stringify({
url: 'https://example.com',
format: 'webp',
ip_country_code: 'in'
}),
muteHttpExceptions: true
});
const status = response.getResponseCode();
if (status < 200 || status >= 300) {
throw new Error('HTTP ' + status + ': ' + response.getContentText());
}
return response.getBlob().setName('example.webp');
}
4. Use India as the page’s rendering location
There are two different locations to keep straight:
- Apps Script request origin:
UrlFetchAppuses Google’s network infrastructure and a pool of Google IP ranges. A script run by someone in India is not thereby guaranteed to make its outbound API call from an Indian IP. - Target-page rendering location: a screenshot provider may offer an option to render the target through an IP associated with a country. ScreenshotOne documents
ip_country_code: 'in'for India. That can affect pages which choose content based on IP location.
For consistent India-localized results, combine the country setting with any other relevant dimensions: the page’s India-specific URL or market selector, an Accept-Language header, a suitable browser time zone, and browser geolocation if the page reads the Geolocation API. These are distinct signals: IP country, language, time zone, and browser coordinates do not substitute for one another. ScreenshotOne’s localization guide explains its regional IP option and notes that proxy-routed requests can take longer.
5. Choose GET or POST and protect the credential
ScreenshotOne accepts request options with GET query parameters or a POST JSON body, and accepts the key as a query parameter, JSON property, or X-Access-Key header. For Apps Script, POST with JSON and a header is a practical default: it keeps the key out of the URL and avoids putting long option sets in a query string. The provider’s API key documentation says to keep keys private and distinguishes the API key from its separate signing key.
A GET request can be appropriate for short requests or when integrating with a service that specifically expects GET. Encode query parameter values correctly; URLs inside URLs contain reserved characters such as &, ?, and #. Never publish an unsigned URL containing a secret key in a public page or log. If a request URL must be public, use the provider’s supported signed-link approach rather than exposing a key.
6. cURL, Python, and Node.js equivalents
These examples use the same documented ScreenshotOne endpoint and header authentication as the Apps Script version. They are useful for isolating provider/API issues from Apps Script authorization or quota issues.
cURL
curl -X POST "https://api.screenshotone.com/take" \
-H "Content-Type: application/json" \
-H "X-Access-Key: $SCREENSHOT_API_KEY" \
-d '{"url":"https://example.com","format":"png","ip_country_code":"in"}' \
-o screenshot.png
Python
import os
import requests
api_key = os.environ["SCREENSHOT_API_KEY"]
response = requests.post(
"https://api.screenshotone.com/take",
headers={"X-Access-Key": api_key},
json={
"url": "https://example.com",
"format": "png",
"ip_country_code": "in",
},
timeout=90,
)
if not response.ok:
raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
with open("screenshot.png", "wb") as image_file:
image_file.write(response.content)
Node.js
const apiKey = process.env.SCREENSHOT_API_KEY;
if (!apiKey) throw new Error('Set SCREENSHOT_API_KEY');
const response = await fetch('https://api.screenshotone.com/take', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Access-Key': apiKey,
},
body: JSON.stringify({
url: 'https://example.com',
format: 'png',
ip_country_code: 'in',
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const bytes = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('screenshot.png', bytes));
7. Select output and capture options
Only send options relevant to your result. Names and supported values vary by provider; check the chosen provider’s current documentation before relying on an option. ScreenshotOne documents the following examples in its options reference.
| Need | Example option | Notes |
|---|---|---|
| Image format | format: 'png', 'jpg', or 'webp' |
Choose a format supported by the provider and match the filename extension and MIME type. |
| Viewport | viewport_width, viewport_height |
Controls the visible browser area. A device preset may be available; explicit dimensions can override a preset for some providers. |
| Full page | full_page: true |
Captures beyond the initial viewport. Very long pages can take longer and produce large files; lazy-loaded content may require provider-specific handling. |
| Element-only capture | selector: '.report' |
Captures a matching element where supported. Missing, hidden, or late-rendering selectors can cause errors or incomplete output. |
format: 'pdf' |
Save with a .pdf name and handle it as a binary blob. Providers may expose paper, margin, orientation, and page-range options. |
|
| India IP location | ip_country_code: 'in' |
Changes target rendering geography where supported; it does not change the Apps Script caller’s location. |
| Wait behavior | Provider-specific wait or delay options | Wait only for the condition needed. Excessive fixed delays increase runtime without guaranteeing readiness. |
ScreenshotOne’s options documentation lists multiple formats, including PNG, JPEG, WebP, and PDF; the exact options and behavior are provider-specific. Avoid sending undocumented option names: some APIs reject unknown options with a client error.
8. Handle errors and retries without corrupting files
Set muteHttpExceptions: true when you want to inspect non-2xx responses yourself. Without it, Apps Script may throw on an HTTP error before your code can read the response body. Check the status first, preserve the error text for diagnosis, and only save a successful response as an image.
Retry only failures that may be temporary, such as a transient server error or rate limit, and use a small bounded retry count with backoff. Do not repeatedly retry invalid credentials, unsupported options, malformed target URLs, or pages that consistently fail. A screenshot operation can be expensive in elapsed time, and Apps Script executions have runtime and URL Fetch quotas. Avoid retry loops that can exceed execution limits.
9. Apps Script quotas, performance, and cost
- Google quota: the Apps Script quotas page currently displays URL Fetch limits of 20,000 calls per day for consumer accounts and 100,000 for Workspace accounts. Google says quotas can change, so confirm the current quota table for your account. Each API request consumes a URL Fetch call; provider usage is separate.
- Provider limits and price: your screenshot provider can impose its own monthly allowance, concurrency, payload, or rate limits. Those do not follow from Google’s quotas; check the provider’s plan and usage page. The research available for this article does not establish ScreenshotOne’s India-based account or payment availability.
- Execution time: page rendering can take much longer than a normal HTTP request, especially for large pages, slow sites, regional proxy routing, or long wait conditions. Use the provider’s documented timeout controls where available and keep the Apps Script execution limit in mind.
- Image size: full-page, high-resolution, or PNG captures may return large files. Prefer a smaller viewport, JPEG/WebP where acceptable, or element capture if you need only part of the page. Consider Drive storage volume and downstream transfer size.
- Batching: for multiple independent captures, Apps Script offers
UrlFetchApp.fetchAll(). Check its request semantics and the provider’s concurrency/rate limits before batching. Batching does not remove per-request provider charges or Google URL Fetch accounting. - Repeat work: cache a screenshot you can reuse instead of capturing the same unchanged page repeatedly. Cache duration and billing behavior are provider-specific.
10. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Authorization error mentioning external requests | The script lacks the external request OAuth scope or has not been reauthorized. | Add https://www.googleapis.com/auth/script.external_request to an explicit manifest scope list, save, then run and authorize again. |
| HTTP 401 or 403 from the screenshot API | Missing, invalid, expired, or wrong-organization API key; account or permission restriction. | Check the provider’s key settings, pass the key using the documented method, and avoid confusing an API key with a signing secret. |
| JSON error saved as a PNG | The code saved an error response without checking its status. | Set muteHttpExceptions: true, check getResponseCode(), and log getContentText() only on failure. |
| Invalid request or unsupported option | Wrong parameter spelling/value, unsupported format, or provider contract mismatch. | Compare the payload with the provider’s current options reference. Remove optional fields until the minimal request succeeds. |
| Timeout or script execution limit | The target loads slowly, waits are excessive, a proxy is in use, or the page is very large. | Reduce unnecessary waits and full-page scope, test a simpler target, and use an asynchronous job API if the provider offers one and the workflow needs longer processing. |
| Screenshot shows the wrong country or language | Apps Script’s caller location was mistaken for target render location, or only one localization signal was changed. | Set the provider’s IP-country option and separately set the target URL, language, time zone, or geolocation as required by that page. |
| 403, CAPTCHA, or bot-check page in the image | The destination blocks automated browsing or requires a human session. | Use only an authorized target. For a site you control, configure an appropriate permitted access path; a screenshot API cannot guarantee access to third-party protected pages. |
| Drive file has the wrong extension or cannot be opened | The requested format and filename do not match, or the response was not an image. | Confirm successful status and response content type, then use the extension matching the requested format. |
| URL Fetch quota exceeded | The Apps Script account reached its daily or service quota. | Reduce request volume, reuse cached captures, spread nonurgent work across time, or move high-volume work to an architecture with suitable quotas. |
11. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. A single GET call returns an image or PDF. See the ScreenshotNeo API documentation for request details. For an Apps Script call, build a URL with encoded parameters and fetch it:
function captureWithScreenshotNeo() {
const apiKey = PropertiesService.getScriptProperties()
.getProperty('SCREENSHOTNEO_API_KEY');
if (!apiKey) throw new Error('Set SCREENSHOTNEO_API_KEY in Script Properties.');
const endpoint = 'https://api.screenshotneo.com/v1/shot';
const query = [
'access_key=' + encodeURIComponent(apiKey),
'url=' + encodeURIComponent('https://example.com')
].join('&');
const response = UrlFetchApp.fetch(endpoint + '?' + query, {
muteHttpExceptions: true
});
const status = response.getResponseCode();
if (status < 200 || status >= 300) {
throw new Error('ScreenshotNeo returned HTTP ' + status + ': ' +
response.getContentText());
}
return DriveApp.createFile(
response.getBlob().setName('example.webp')
).getUrl();
}
For comparison, the documented basic request forms are:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie/consent banners, newsletter popups, and chat widgets before the shot; those cleanup steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
12. Frequently asked questions
Does an Apps Script project run from India need a proxy?
Not for the API call merely because the developer is in India. Apps Script makes requests through Google’s network. A rendering-location option is needed only when the target page must see a particular country’s IP.
Can I display the screenshot inside a Google Sheet?
Yes. Save the image in Drive and use a shareable image URL in a sheet formula if the file permissions and URL format allow access to the intended viewers. Avoid exposing an API key in a cell formula or public spreadsheet.
Can this capture a page that needs my login?
Only if the screenshot provider supports the target site’s authentication method and you are authorized to access the page. Provider options for headers or cookies are specific to that API; protect credentials and do not send them to a site you do not control or trust.
Does setting the script time zone to Asia/Kolkata localize the target website?
No. The manifest time zone affects Apps Script date handling. The browser rendering time zone is a separate provider option, and IP-based location and language are separate again.


