ScreenshotNeo

BlogHow-to

How to Bulk Screenshot a List of URLs and Export Results to JSON

Capture screenshots for many URLs, preserve each result and error, and export a reliable JSON file using a hosted batch API or a local CLI.

By the ScreenshotNeo team4 October 20269 min read

To bulk screenshot URLs and export results to JSON, submit the URL list to a batch screenshot API, collect each URL’s outcome, and save those outcomes as a JSON array. Preserve the original URL, status, screenshot location, and any error for every input, including failures. For a local workflow, shot-scraper multi writes screenshot files from a YAML list; add a small script if you also need a consolidated JSON index.

This guide shows both workflows, explains how to design a stable result format, and covers batch limits, retries, storage, and common errors.

1. Choose a workflow

Approach Best for Result handling
ScreenshotNeo Hosted captures with shared options, batch capture, and an API or MCP workflow Its bulk capture supports up to 100 URLs per call. The usage API and response billing and page-verdict headers help track outcomes.
Other hosted batch APIs Asynchronous jobs or provider-specific result fields Response schemas vary: some return a batch ID for polling, screenshot URLs, or per-request status and error details.
shot-scraper local CLI Running captures from a YAML list on your own machine Writes image files. Add code to create a JSON manifest; consolidated JSON export is not documented as built in.

Compare the maximum URLs per batch, whether work is synchronous or asynchronous, available capture settings, response fields, storage durability, and quotas. Provider limits and schemas can change, so check the current documentation before a large run.

2. Define a useful JSON result

Do not represent a batch only as a list of successful screenshot links. A downstream job needs to know which input each result belongs to and whether capture failed. One practical schema is:

[
  {
    "input_url": "https://example.com/",
    "status": "success",
    "screenshot": "screenshots/example-com.webp",
    "error": null,
    "captured_at": "2026-10-04T12:00:00Z",
    "settings": {"format": "webp", "full_page": true}
  },
  {
    "input_url": "https://bad.example/",
    "status": "error",
    "screenshot": null,
    "error": "DNS lookup failed",
    "captured_at": "2026-10-04T12:00:00Z",
    "settings": {"format": "webp", "full_page": true}
  }
]

The timestamp and settings are useful when comparing runs or auditing a capture. If a provider returns a durable hosted URL, put that URL in screenshot; if you download files, use a relative path or object-storage key. Avoid putting temporary URLs in a long-lived dataset unless you know their expiry behavior.

3. Hosted batch API workflow

  1. Normalize and validate inputs, but retain the original string for the result record.
  2. Split the list at the provider’s documented batch limit.
  3. Submit each batch with shared capture settings.
  4. If the response gives a job ID, persist it and poll or subscribe to progress as the provider documents.
  5. Map every provider outcome back to its input URL, including errors.
  6. Write a JSON file atomically after results are collected.

Provider response shapes are not interchangeable. Confirm whether the API returns the final results directly, requires status polling, or supplies screenshot links separately. Do not assume that submitting a batch automatically creates a JSON file on your machine.

ScreenshotNeo batch capture

ScreenshotNeo is a website screenshot API and MCP server. Its bulk capture accepts up to 100 URLs per call, and its other options include full-page capture, a chosen output format, selector capture, device and viewport settings, custom headers and cookies, waiting rules, and caching. See the ScreenshotNeo API documentation for current request details and supported parameters.

A single-URL request using the documented API call looks like this:

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

For a bulk run, use the documented bulk request format and divide inputs into groups of at most 100. Save each per-URL outcome and screenshot reference in your own JSON manifest. The one-URL examples above are runnable individual capture calls; they do not imply a particular bulk response schema. Check the docs for the bulk request and response fields before mapping results.

ScreenshotNeo responses include X-Page-Verdict and X-Billed headers. Preserve these alongside each result when billing visibility matters: the verdict indicates the page outcome, and the billing header indicates whether that response was billed. ScreenshotNeo says bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing.

4. Local workflow with shot-scraper

The documented multi-URL workflow uses a YAML file with URL and output-file pairs. Install the tool in your Python environment, then create a configuration file such as shots.yml:

- url: https://example.com/
  output: screenshots/example.png
- url: https://www.python.org/
  output: screenshots/python.png

Run the multi command:

shot-scraper multi shots.yml

This creates screenshot files. To export an index as JSON, maintain URL-to-filename records yourself, or read the YAML and write a JSON manifest after capture. The reviewed shot-scraper documentation documents the multi command and file outputs, not a built-in consolidated JSON exporter.

For a more auditable local workflow, have a wrapper script create a record before each capture, set its status to success only after the command completes, and store the exception or process error text on failure. Keep the input order or add a stable ID so duplicate URLs remain distinguishable.

5. Make the export reliable

Keep one record per input

Never silently drop failures or duplicate URLs. Preserve the original list position or assign an ID; the same URL may appear more than once with different settings or at different times.

Validate without losing provenance

Check that each input has an allowed scheme such as http or https and a hostname. Store the original input even if you create a normalized version for submission. Do not assume URL normalization is harmless: query parameters and trailing slashes can identify different pages.

Retry selectively

Retry transient network failures and rate limits using bounded exponential backoff and the provider’s retry guidance. Do not endlessly retry invalid URLs, authorization failures, or permanent render errors. Make result writes idempotent by keying records to a batch ID plus input index, or another stable identifier.

Write JSON safely

Write to a temporary file in the destination directory, flush and close it, then rename it to the final path. This avoids leaving a partially written export if the process stops. For very large jobs, write newline-delimited JSON incrementally or store results in a database, then produce a JSON array when needed.

Store image references intentionally

Decide whether JSON points to local files, durable object storage, or provider-hosted links. Keep screenshot files with the export when possible, or document the storage bucket and retention policy. Do not embed image bytes as base64 in JSON unless portability in one file is worth the larger export and memory use.

6. Limits, performance, and cost

  • Batch size: split according to the current provider cap. ScreenshotNeo supports up to 100 URLs per bulk call; ScreenshotAPI.net documents a 50-URL maximum for its bulk JSON API. These are provider-specific limits, not general standards.
  • Concurrency: more parallel requests can shorten elapsed time but may trigger rate limits or exhaust account concurrency. Begin conservatively and increase only within documented limits.
  • Asynchronous jobs: where the provider returns a batch ID, persist it before polling so an interrupted client can resume status checks.
  • Payload and storage: full-page and high-resolution images consume more transfer and storage than viewport captures. Keep the JSON as an index rather than embedding image content.
  • Quotas: check monthly credits, per-minute request buckets, and remaining concurrency. ScreenshotOne says bulk requests use the same one-minute request bucket as regular requests and points users to its usage endpoint for remaining concurrency and reset information.
  • Costs: calculate expected captures after accounting for retries and provider billing rules. ScreenshotNeo bills only clean shots; bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Its free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.

For context, the reviewed Screenshot API documentation lists 60 requests per minute and 500 screenshots per month on its free plan, without a publication year on the page. Verify current limits before depending on them. Treat all vendor limits and pricing as changeable.

7. Troubleshooting

Symptom Likely cause Fix
Some URLs are missing from the export The client only records successful responses, or an async job was not fully collected. Pre-create a result slot for every input and reconcile the final output count against the input count.
Batch submission is rejected The URL count or request body exceeds the provider’s limit or schema. Check the current maximum and required JSON shape; split the list into smaller batches.
HTTP 401 or 403 Missing, invalid, or insufficient API credentials. Check the key, account access, and required authentication field; keep secrets out of the exported JSON.
HTTP 429 or throttling Request-rate or concurrency limit reached. Reduce parallelism, honor any retry-after guidance, and retry with bounded backoff.
Capture reports an error for a valid URL The site may be unreachable from the capture environment, block automation, or fail to finish rendering. Retain the provider error, inspect the page manually, and adjust supported wait conditions or headers when appropriate.
JSON parsing fails HTML or binary image data was saved where a JSON response was expected. Use the provider’s documented response mode. Store downloaded image bytes as files and serialize only metadata and references.
Export has duplicate or mismatched records Results were paired by completion order, but asynchronous requests completed out of order. Match on provider request ID or your own stable input index, never on response order alone.
Screenshot links stop working The provider URL may be temporary or the file was not copied to durable storage. Check link expiry and retention; download or transfer captures to storage you control when needed.

8. Or skip the browser setup

ScreenshotNeo lets you request a screenshot with one GET call. For a single URL:

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

Use its bulk capture for URL lists, with up to 100 URLs per call; use the response fields documented in the API docs to build your JSON export. Cookie banners are accepted and removed before capture, along with known newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, blank pages, timeouts, and failed loads are never billed. An MCP server lets AI agents use screenshot, page-info, and PDF tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.

Sign up free for 1,000 screenshots a month, with no card required.

9. Frequently asked questions

Should each URL be a JSON object or a string?

Use an object per URL in the results file so status, screenshot location, and error can travel together.

Can I export screenshot images inside JSON?

You can encode bytes, but it makes files larger and less convenient. Usually store images separately and place a durable path or URL in each JSON record.

What if I need to rerun only failures?

Filter records whose status is an error, keep their original input identifiers, and submit those records as a new batch. Link the retry batch to the original run in your own metadata.

Does shot-scraper produce one JSON file for a multi-URL run?

The reviewed documentation describes YAML-driven multi captures that write screenshot files. Add a wrapper or separate export step for a consolidated JSON manifest.

Can I preserve provider-specific fields?

Yes. Keep a normalized set of common fields for downstream use and optionally include a nested provider response field for details your application may need later.