How to Capture Screenshots of Multiple URLs with ScreenshotMachine in One Batch
ScreenshotMachine documents one URL per API request, not a multi-URL endpoint. Use a script to process a URL list, save each result, and log failures.
Short answer: ScreenshotMachine’s documented screenshot API takes one url per GET request. Its guide does not document a single API request that accepts a list of URLs. To capture multiple pages, make one request per URL in a script, save each response under a predictable filename, and record errors per page. This is client-side batch automation, not a documented multi-URL endpoint. Check current account limits with ScreenshotMachine before running a large job.
The examples below use Python for a reusable batch script, followed by cURL and Node.js alternatives. Each example sends requests sequentially and checks ScreenshotMachine’s documented X-Screenshotmachine-Response header before treating the returned file as an image. The API’s documented endpoint and options are in the ScreenshotMachine API guide.
1. Prepare a URL list
Put one complete page URL on each line in urls.txt. Blank lines and lines beginning with # are ignored by the Python script below.
https://example.com/
https://www.python.org/
https://developer.mozilla.org/
Use URLs you are permitted to capture. If a site requires authentication, see the cookies and request configuration notes below. Do not place an API key or secret phrase in a public repository or browser code.
2. Run the batch with Python
This script reads the list, makes one request for each URL, saves successful image bodies in an output directory, and writes failures to a CSV file. It uses Python 3 and the requests package. Install the package with python -m pip install requests, then set your API key in the environment and run python capture_batch.py.
import csv
import os
import re
import time
from pathlib import Path
from urllib.parse import urlparse
import requests
API_URL = "https://api.screenshotmachine.com"
API_KEY = os.environ.get("SCREENSHOTMACHINE_KEY")
INPUT_FILE = Path("urls.txt")
OUTPUT_DIR = Path("screenshots")
ERROR_FILE = Path("capture-errors.csv")
# Explicit capture settings. Adjust these for your pages and account.
OPTIONS = {
"dimension": "1366xfull",
"device": "desktop",
"format": "png",
"cacheLimit": "0",
"delay": "1000",
"zoom": "100",
}
REQUEST_TIMEOUT_SECONDS = 90
PAUSE_BETWEEN_REQUESTS_SECONDS = 0.25
def load_urls(path):
return [
line.strip()
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip() and not line.lstrip().startswith("#")
]
def safe_filename(index, url):
host = urlparse(url).hostname or "page"
host = re.sub(r"[^A-Za-z0-9.-]+", "_", host).strip("._") or "page"
return f"{index:04d}-{host}.png"
def main():
if not API_KEY:
raise SystemExit("Set SCREENSHOTMACHINE_KEY to your ScreenshotMachine API key.")
if not INPUT_FILE.is_file():
raise SystemExit(f"URL list not found: {INPUT_FILE}")
urls = load_urls(INPUT_FILE)
if not urls:
raise SystemExit("No URLs found in urls.txt")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
failures = []
saved = 0
with requests.Session() as session:
for index, url in enumerate(urls, start=1):
params = {"key": API_KEY, "url": url, **OPTIONS}
try:
response = session.get(
API_URL,
params=params,
timeout=REQUEST_TIMEOUT_SECONDS,
)
response.raise_for_status()
api_status = response.headers.get("X-Screenshotmachine-Response")
# The API documents this header for error responses. Avoid
# saving a returned error image as if it were a screenshot.
if api_status:
failures.append((url, f"API response: {api_status}"))
print(f"ERROR {index}/{len(urls)} {url}: {api_status}")
elif not response.content:
failures.append((url, "Empty response body"))
print(f"ERROR {index}/{len(urls)} {url}: empty response")
else:
destination = OUTPUT_DIR / safe_filename(index, url)
destination.write_bytes(response.content)
saved += 1
print(f"SAVED {index}/{len(urls)} {destination}")
except requests.RequestException as exc:
failures.append((url, str(exc)))
print(f"ERROR {index}/{len(urls)} {url}: {exc}")
if index < len(urls):
time.sleep(PAUSE_BETWEEN_REQUESTS_SECONDS)
if failures:
with ERROR_FILE.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["url", "error"])
writer.writerows(failures)
print(f"Finished: {saved} saved, {len(failures)} failed out of {len(urls)}.")
if failures:
print(f"Failure details: {ERROR_FILE}")
if __name__ == "__main__":
main()
Set the key in your shell before running the script: export SCREENSHOTMACHINE_KEY='YOUR_API_KEY' on macOS or Linux, or $env:SCREENSHOTMACHINE_KEY='YOUR_API_KEY' in PowerShell. The script deliberately sends one request at a time. Its short pause is a configurable courtesy interval, not a claim about ScreenshotMachine’s rate limit. The sources reviewed do not establish concurrency or rate limits.
3. Batch with cURL and a shell loop
For a small list on macOS or Linux, a shell loop can invoke cURL once per line. Save as capture-batch.sh, set SCREENSHOTMACHINE_KEY, and run bash capture-batch.sh urls.txt. cURL encodes query parameters with --data-urlencode.
#!/usr/bin/env bash
set -u
input_file="${1:-urls.txt}"
output_dir="screenshots"
: "${SCREENSHOTMACHINE_KEY:?Set SCREENSHOTMACHINE_KEY first}"
mkdir -p "$output_dir"
index=0
while IFS= read -r url || [[ -n "$url" ]]; do
url="${url//$'\r'/}"
[[ -z "$url" || "$url" == \#* ]] && continue
index=$((index + 1))
host=$(python3 -c 'import sys; from urllib.parse import urlparse; print(urlparse(sys.argv[1]).hostname or "page")' "$url")
file=$(printf '%s/%04d-%s.png' "$output_dir" "$index" "$host")
headers=$(mktemp)
if curl --silent --show-error --fail-with-body --get \
'https://api.screenshotmachine.com' \
--data-urlencode "key=$SCREENSHOTMACHINE_KEY" \
--data-urlencode "url=$url" \
--data-urlencode 'dimension=1366xfull' \
--data-urlencode 'device=desktop' \
--data-urlencode 'format=png' \
--data-urlencode 'cacheLimit=0' \
--data-urlencode 'delay=1000' \
--data-urlencode 'zoom=100' \
--dump-header "$headers" \
--output "$file"; then
if grep -qi '^X-Screenshotmachine-Response:' "$headers"; then
echo "API error for $url: $(grep -i '^X-Screenshotmachine-Response:' "$headers" | tr -d '\r')" >&2
rm -f "$file"
else
echo "Saved $file"
fi
else
echo "HTTP/request error for $url" >&2
rm -f "$file"
fi
rm -f "$headers"
done < "$input_file"
The API guide documents an error response header, but this loop’s header check is a basic guard. For production use, keep a structured error log and validate returned content before publishing or further processing files. If your installed cURL lacks --fail-with-body, upgrade cURL or remove that flag and inspect the HTTP status separately.
4. Batch with Node.js
This Node.js 18+ example uses built-in fetch, processes URLs sequentially, and logs errors to a CSV file. Save as capture-batch.mjs; run with SCREENSHOTMACHINE_KEY=YOUR_API_KEY node capture-batch.mjs.
import { readFile, mkdir, writeFile, appendFile } from 'node:fs/promises';
import { setTimeout as sleep } from 'node:timers/promises';
const key = process.env.SCREENSHOTMACHINE_KEY;
if (!key) throw new Error('Set SCREENSHOTMACHINE_KEY first');
const urls = (await readFile('urls.txt', 'utf8'))
.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
await mkdir('screenshots', { recursive: true });
await writeFile('capture-errors.csv', 'url,error\n');
function csv(value) {
return `"${String(value).replaceAll('"', '""')}"`;
}
function filename(index, url) {
const host = new URL(url).hostname.replace(/[^A-Za-z0-9.-]+/g, '_') || 'page';
return `screenshots/${String(index).padStart(4, '0')}-${host}.png`;
}
for (let index = 0; index < urls.length; index++) {
const url = urls[index];
const query = new URLSearchParams({
key,
url,
dimension: '1366xfull',
device: 'desktop',
format: 'png',
cacheLimit: '0',
delay: '1000',
zoom: '100',
});
try {
const response = await fetch(`https://api.screenshotmachine.com/?${query}`, {
signal: AbortSignal.timeout(90000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const apiError = response.headers.get('X-Screenshotmachine-Response');
if (apiError) throw new Error(`ScreenshotMachine response: ${apiError}`);
const bytes = new Uint8Array(await response.arrayBuffer());
if (bytes.length === 0) throw new Error('Empty response body');
const path = filename(index + 1, url);
await writeFile(path, bytes);
console.log(`Saved ${path}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Failed ${url}: ${message}`);
await appendFile('capture-errors.csv', `${csv(url)},${csv(message)}\n`);
}
if (index + 1 < urls.length) await sleep(250);
}
For Node.js versions without AbortSignal.timeout, use an AbortController and a timer. Keep the API key in a secret store or environment variable and avoid logging the complete request URL, because it contains the key.
5. Choose capture settings
ScreenshotMachine’s API uses query parameters. These are the options most relevant to a URL batch; consult the current API documentation for the complete parameter list and accepted values.
| Parameter | What it controls | Batch guidance |
|---|---|---|
key |
Your required API key. | Keep it server-side. Never commit it or expose it in public HTML. |
url |
The single page to capture in this request. | Pass once per request; URL-encode it. The API guide says the protocol prefix is optional, but using complete https:// URLs makes input clearer. |
dimension |
Viewport width and height as widthxheight; the documented width range is 100–1920 and height range 100–9999. Use full for full-page height. |
The default is 120x90; set dimensions explicitly for useful captures. Full-page images can be much larger. |
format |
Output format: JPG, PNG, or GIF. | The documented default is JPG. Choose PNG for sharp UI/text edges, JPG for smaller photographic images, or GIF if it suits your downstream workflow. |
device |
Device rendering profile. | Use a consistent value across the batch for comparable captures. Check the guide for accepted device values. |
delay |
Wait before capture, in milliseconds. Documented values include 0 and steps from 200 through 10000. | Increase only when pages need time for animation or client rendering. A larger delay increases job duration. |
zoom |
Page scale, from 10 to 400; default 100. | Keep the value fixed across a comparison set. Zoom changes effective content sizing and may alter layout. |
cacheLimit |
Maximum age in days for a cached screenshot that may be reused; 0 requests a fresh capture. |
Choose freshness based on the use case. The API guide describes fractional days for shorter intervals. |
selector |
Capture the DOM element matched by a CSS selector. | Useful for consistent component captures; a missing or invalid selector can produce an API error. |
click |
Click a CSS-selected element before capture. | Can trigger a page control, such as a consent action, when appropriate. Confirm the selector and resulting state. |
crop |
Capture a pixel rectangle of the viewport. | Use the documented coordinate format and verify the rectangle fits the viewport. |
cookies |
Pass cookie name/value pairs, separated by semicolons. | URL-encode reserved characters and never put sensitive session cookies in shared logs or source code. |
accept-language, user-agent |
Set request/rendering language preference or user-agent value. | Use only when needed to make pages render in a particular language or device context. |
The online ScreenshotMachine generator also exposes device, dimensions, full-page capture, zoom, format, delay, click, hide-elements, selector, and crop controls.
6. Handle errors and partial batches
A multi-URL run should be treated as a collection of independent jobs: one failure should not erase successful files or prevent later URLs from being attempted. ScreenshotMachine documents the X-Screenshotmachine-Response header for API errors. Its listed error codes include invalid_hash, invalid_key, invalid_url, missing_key, missing_url, no_credits, invalid_selector, invalid_crop, and system_error.
| Error | Likely cause | What to check |
|---|---|---|
missing_key / invalid_key |
The key was omitted, misspelled, or is not valid for the account. | Check the environment variable and account key. Do not paste the secret into a public issue or log. |
missing_url / invalid_url |
The URL parameter is absent or malformed. | Trim input lines, require a valid URL, and ensure query encoding is handled by the HTTP client. |
invalid_hash |
A configured secret phrase requires a matching hash, or the hash does not match the URL and phrase. | Follow ScreenshotMachine’s hash instructions for the exact URL and configured secret. Do not improvise a hash or expose the phrase. |
no_credits |
The account has no remaining credits for the request. | Check current account usage and plan before resuming the failed URLs. |
invalid_selector / invalid_crop |
The requested element selector or crop value is invalid for the page/request. | Test the option on that URL, validate selectors and crop dimensions, or remove the option for those pages. |
system_error |
The service returned a system-level error. | Record the URL and response details, then retry selectively with backoff and check the provider’s current service guidance. |
| HTTP timeout or connection error | The request took longer than the client timeout, or the network connection failed. | Retry that URL later, use a reasonable timeout, and avoid launching uncontrolled parallel requests. |
A returned file is not automatically a valid screenshot: the API can return an error image. Check the documented error header and keep a failure log. For higher assurance, validate image type and decode the image before sending it to downstream systems. Do not blindly retry permanent errors such as invalid credentials; retry transient network or system failures selectively.
7. Throughput, reliability, and cost
- Concurrency: The documentation reviewed describes a single-URL request and does not establish a concurrency limit, rate limit, or recommended worker count. The examples are sequential. Ask ScreenshotMachine to confirm current account-level limits before increasing concurrency.
- Runtime: A batch of N URLs entails N API calls. Its total time depends on page load and capture time, configured delay, network latency, and any waits between calls. Full-page captures and large outputs can take longer to transfer and store.
- Retries: Store successes as you go so a process interruption does not discard completed work. Retry only failed URLs, with a small bounded retry count and increasing wait for transient errors. Preserve the original URL and error in a log.
- Freshness and cache: Set
cacheLimitdeliberately. ScreenshotMachine’s API guide explains cache age selection; its pricing page states that cached screenshots reused within its stated 14-day period are not billed as new, fresh screenshots. Confirm current plan terms before estimating a large run. - Billing: ScreenshotMachine’s pricing page says additional screenshots are counted in groups of 1,000 rounded down, and only new, fresh screenshots are charged. These are vendor-published terms that can change; check the current pricing page before budgeting.
- Storage and naming: Include an index in filenames because a URL list may contain the same hostname more than once. For recurring jobs, include a run date or store metadata alongside the image so outputs can be traced to the input and settings.
8. Troubleshooting checklist
- All URLs fail with a key error: Confirm the environment variable is set in the same shell that launches the script and that it contains the account key.
- Files are tiny or show an error graphic: Inspect
X-Screenshotmachine-Response; do not assume every successful HTTP response is an image of the requested page. - URL parameters break on ampersands or spaces: Pass parameters through a URL-encoding HTTP library, or use cURL’s
--data-urlencode. Do not concatenate raw URLs into a query string. - Page is captured before content appears: Increase the documented delay for pages that need it, while accounting for longer run time. The source does not establish that delay waits for every application-specific readiness condition.
- Full page is clipped or unexpectedly large: Use the documented
fullheight setting, inspect the page’s behavior, and consider whether the use case needs a full-page image or a fixed viewport. - Duplicate names overwrite captures: Include an index or unique identifier in filenames; hostname alone is not unique.
- Batch stops halfway: Catch errors per URL, write each success immediately, and persist a failure list so the run can resume only failed items.
- Usage is higher than expected: Review whether
cacheLimit=0is forcing fresh captures, and compare expected new captures with the current plan’s billing terms.
Or skip the browser setup
If you want one API call per URL without managing a screenshot browser or stitching together a capture service, ScreenshotNeo is a website screenshot API and MCP server. A script can loop through your URLs and call its endpoint for each one. See the ScreenshotNeo API documentation for options and response details.
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}`);
- 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, blank pages, timeouts, failed loads, and cache hits are never billed. Response headers report the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free 1,000 screenshots a month, with no card required.
FAQ
Does ScreenshotMachine have a documented bulk endpoint?
The reviewed official API guide documents one url parameter per request. It does not document a multi-URL endpoint. A loop is the supported instructional approach based on that request shape; ask the vendor about any account-specific or newer bulk capability.
Can I capture the same URLs at desktop and mobile sizes?
Yes, structure your input as URL-and-settings jobs and make separate requests for each combination. Set the device and dimensions for each job, and include those values in output filenames to prevent overwrites.
Can this script make a PDF for every URL?
The examples here target image screenshots through the screenshot API. ScreenshotMachine documents a separate website-to-PDF API; consult its official PDF API documentation for that workflow.
Should I use parallel requests to finish faster?
Only after confirming current account-level limits with ScreenshotMachine. The documentation reviewed does not establish a supported concurrency or rate limit.
Sources
- ScreenshotMachine screenshot API guide — endpoint, parameters, response errors, and code examples.
- ScreenshotMachine screenshot generator — available capture controls.
- ScreenshotMachine pricing — vendor-stated cache and additional screenshot billing terms.


