ScreenshotNeo

BlogHow-to

How to screenshot multiple URLs with a REST API and save the results

Capture a list of web pages through a REST API, handle asynchronous batches and failures, and save each result with its source URL.

By the ScreenshotNeo team4 October 20269 min read

To screenshot multiple URLs with a REST API, send an authenticated HTTP request containing a list of URLs to a provider’s batch endpoint, then save the images or archive it returns. Some APIs finish immediately; others return a job ID that you poll or monitor before downloading results. Keep a mapping between each input URL and its output, and check the provider’s failure report because one page can fail while the rest succeed.

This guide uses Screenshot API’s documented batch endpoint for the do-it-yourself example. Its endpoint and response contract are provider-specific; verify the current documentation and account limits before adapting the code. [Screenshot API REST API documentation]

1. Send a batch request

Store the API key in an environment variable rather than in source code. The example sends two URLs with shared viewport and format options. It creates a JSON response file so you can inspect whether the service returned completed results or a batch ID.

export SCREENSHOT_API_KEY='YOUR_API_KEY'
curl -sS -X POST 'https://api.screenshot-api.org/api/v1/screenshot/batch' \
  -H "Authorization: Bearer $SCREENSHOT_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": [
      "https://example.com",
      "https://example.org"
    ],
    "options": {
      "viewport": { "width": 1280, "height": 720 },
      "format": "png"
    }
  }' \
  -o batch-response.json

Inspect batch-response.json and its HTTP status before proceeding. If the response includes a batch ID, use the documented status endpoint GET /api/v1/batch/:batchId until the batch reaches a terminal state. The API also documents an SSE stream at GET /api/v1/batch/:batchId/stream for event-based progress. Do not assume that receiving a job ID means the screenshots are ready.

2. Choose the right output handling

Batch APIs do not share a universal output format. A provider may return image bytes, links to individual images, or an archive download. Follow that provider’s response contract and check the content type before saving or extracting anything.

Response What to do What to preserve
Image bytes Write the response body to a file with the correct image extension. Input URL, output filename, format, and status.
Image URLs Download each result URL and check its HTTP status before writing it. A URL-to-file manifest and any per-image errors.
ZIP archive Download the archive, validate it, and extract it to a batch-specific directory. The archive, manifest, and failure report.
Queued job ID Poll the documented job endpoint or consume its documented event stream, then retrieve results. Job ID, final status, and result metadata.

For example, url2image documents a job status endpoint at GET /api/v1/jobs/{id} and an archive download endpoint at GET /api/v1/jobs/{id}/download. Its described ZIP contains an image per URL, manifest.json with page metadata, and not-rendered.csv for failures. Those paths and archive contents apply to url2image, not Screenshot API or other services. [url2image batch screenshots]

3. Save results with Python

The following runnable script submits a batch to Screenshot API and saves the initial response for inspection. It deliberately does not guess at a provider-specific JSON schema or job completion flow. Read the documented response, then add the matching status and download steps for the response type your account receives.

import json
import os
import sys
from pathlib import Path

import requests

api_key = os.environ.get("SCREENSHOT_API_KEY")
if not api_key:
    sys.exit("Set SCREENSHOT_API_KEY before running this script")

urls = ["https://example.com", "https://example.org"]
payload = {
    "urls": urls,
    "options": {
        "viewport": {"width": 1280, "height": 720},
        "format": "png",
    },
}

response = requests.post(
    "https://api.screenshot-api.org/api/v1/screenshot/batch",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    json=payload,
    timeout=60,
)

if not response.ok:
    print(f"Batch request failed: HTTP {response.status_code}", file=sys.stderr)
    print(response.text, file=sys.stderr)
    response.raise_for_status()

Path("screenshots").mkdir(parents=True, exist_ok=True)
content_type = response.headers.get("Content-Type", "")
if "application/json" in content_type:
    result = response.json()
    Path("screenshots/batch-response.json").write_text(
        json.dumps(result, indent=2), encoding="utf-8"
    )
    print(json.dumps(result, indent=2))
    print("Inspect the response and follow the provider's documented status/download flow.")
elif content_type.startswith("image/") or "application/zip" in content_type:
    suffix = ".zip" if "application/zip" in content_type else ".image"
    Path(f"screenshots/batch-result{suffix}").write_bytes(response.content)
else:
    sys.exit(f"Unexpected response Content-Type: {content_type}")

This handles direct binary results if the service returns them and saves JSON responses for the next step. When a provider returns result URLs or a job ID, use its documented fields and endpoints to download the results. For a ZIP, inspect the archive and preserve its manifest and failure list. Map files back to inputs by manifest or a stable naming scheme; do not infer that response order always matches request order unless the provider says so.

4. Node.js example

This Node.js example submits the same batch, checks for HTTP errors, and saves either a JSON response or direct binary output. It likewise leaves polling and extraction tied to the provider’s documented schema.

import { mkdir, writeFile } from 'node:fs/promises';

const apiKey = process.env.SCREENSHOT_API_KEY;
if (!apiKey) throw new Error('Set SCREENSHOT_API_KEY before running');

const payload = {
  urls: ['https://example.com', 'https://example.org'],
  options: { viewport: { width: 1280, height: 720 }, format: 'png' },
};

const response = await fetch(
  'https://api.screenshot-api.org/api/v1/screenshot/batch',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(60_000),
  },
);

if (!response.ok) {
  throw new Error(`Batch request failed: HTTP ${response.status}: ${await response.text()}`);
}

await mkdir('screenshots', { recursive: true });
const contentType = response.headers.get('content-type') ?? '';
if (contentType.includes('application/json')) {
  const result = await response.json();
  await writeFile('screenshots/batch-response.json', JSON.stringify(result, null, 2));
  console.log(result);
  console.log("Inspect the response and follow the provider's documented status/download flow.");
} else if (contentType.startsWith('image/') || contentType.includes('application/zip')) {
  const extension = contentType.includes('application/zip') ? '.zip' : '.image';
  await writeFile(`screenshots/batch-result${extension}`, Buffer.from(await response.arrayBuffer()));
} else {
  throw new Error(`Unexpected response Content-Type: ${contentType}`);
}

5. Configure batches and handle edge cases

Shared capture options

The example uses a viewport of 1280 by 720 pixels and PNG output. A batch API may apply shared options to every URL, but the exact supported fields and defaults depend on the provider. Confirm whether it supports full-page capture, image format, device or viewport emulation, selector targeting, and wait conditions before relying on them. The Screenshot API batch documentation describes shared viewport and format options; consult its current reference for accepted values and other controls. [Screenshot API API reference]

Input list and per-page failures

  • Validate that every URL is absolute and uses a supported scheme before submitting.
  • Deduplicate URLs if duplicate captures are not intended.
  • Expect redirects, authentication walls, bot checks, very slow pages, and pages that never finish loading to behave differently by provider.
  • Treat a batch as partially successful unless the API explicitly guarantees all-or-nothing behavior. Record failures against their original URLs.
  • If you split a large input list into smaller jobs, retain a batch ID and input index for every URL.

Batch size and completion strategy

Check documented request and job limits before selecting a batch size. Limits vary: Browshot documents up to 10 URLs and up to 10 instances (100 combinations) in its multiple endpoint, while url2image states support for up to 500 URLs per batch. These are vendor-specific published limits and can change; they are not general REST API limits. [Browshot API documentation] [url2image batch screenshots]

Polling is simple to implement: wait between status requests, stop when the documented terminal state appears, and enforce an overall deadline. Avoid tight polling loops that waste requests or hit rate limits. If the provider offers a documented stream or webhook, it may reduce polling, but validate event delivery and provide a recovery path by checking final job status.

6. Retry safely and keep results reliable

  • Retry transient failures: network timeouts, selected server errors, and rate limits may be temporary. Use bounded retries with exponential backoff and jitter, and honor any Retry-After header.
  • Do not blindly repeat a batch POST: a lost response may mean the provider accepted the job. Use an idempotency key if the API documents one; otherwise reconcile through the account’s jobs or provider support before resubmitting to avoid duplicate work.
  • Retry per URL where possible: if only some pages failed, resubmit those URLs rather than recapturing successful pages.
  • Validate downloads: check HTTP status and content type, write to a temporary file, and rename only after the transfer completes. For archives, test extraction and retain the original ZIP.
  • Make reruns traceable: save a manifest with input URL, output path, capture options, job ID, status, and error details.

7. Performance, reliability, and cost

Batching reduces request orchestration overhead, but rendering each page still takes time and provider capacity is finite. Large jobs may take longer to complete; use the documented batch limits, avoid excessive concurrent submissions, and measure completion time for your own page set before setting deadlines. No single throughput or latency figure applies across providers.

For reliability, set explicit request timeouts, cap retries, retain job identifiers, and make the workflow resumable. Separate submission, status checking, downloading, and extraction so a download failure does not force a new capture. Preserve failure reports and verify a sample of saved images when the output feeds another automated step.

Calculate cost from the service’s current pricing and billing rules, including whether failed pages, retries, queued jobs, or repeated URLs are chargeable. Check quotas and rate limits for your account. The research sources do not establish comparable pricing across providers, so verify the provider’s pricing page before running a large batch.

8. Other approaches

For a local workflow, shot-scraper can use YAML URL/output pairs with shot-scraper multi. This is useful when you can install and maintain the tool and its browser dependencies in the environment running the capture. [shot-scraper documentation]

ScreenshotCenter describes batch jobs, progress tracking, ZIP downloads, and integrations with cloud storage and automation platforms. Check its full API documentation for current limits and supported destinations before designing around them. [ScreenshotCenter batch screenshot features]

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its API accepts a URL in one GET request and returns a PNG, JPEG, WebP, or PDF. For multiple URLs, call the endpoint once per URL or use its bulk capture option, which supports up to 100 URLs per call. See the ScreenshotNeo API documentation.

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 accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. [ScreenshotNeo docs]

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

FAQ

Can I screenshot a list of URLs in one request?

Yes, if the provider has a batch endpoint. Send the list using its required request shape and respect its documented per-request or per-job limit.

Does a successful batch response mean every page rendered?

No. A response can indicate that a job was accepted, and individual pages may still fail. Check final job state and per-URL results or the provider’s failure report.

How should I name the saved files?

Use a stable index or sanitized hostname plus an index, and keep a manifest that maps each filename to its full source URL. Avoid using raw URLs as paths.

Can I use one API’s batch code with another provider?

The workflow concepts transfer, but endpoint paths, authentication, option names, response fields, limits, and download formats do not. Adapt each integration to its provider’s current documentation.