ScreenshotNeo

BlogHow-to

How to Create Bulk Screenshots of a URL List with the Browshot API

Use Browshot’s repeated URL parameters for up to 100 screenshots per call, or upload a URL file for larger batches. Learn how to submit, track, and retrieve results.

By the ScreenshotNeo team4 October 202612 min read

For a short list, call Browshot’s /api/v1/screenshot/multiple endpoint and send one repeated url parameter per page. It accepts up to 10 URLs and 10 instance IDs, for up to 100 URL-and-instance screenshots in one call. For hundreds or thousands of URLs, use /api/v1/batch/create with a multipart upload containing a text file with one URL per line. Both workflows are asynchronous: save screenshot IDs, poll their status, then retrieve finished images.

This guide uses Browshot’s official API documentation as its reference. The examples use placeholder API keys; store your real key in an environment variable.

1. Choose the right endpoint

Input size Endpoint Request shape Useful limit or behavior
Up to 10 URLs and up to 10 browser instances /api/v1/screenshot/multiple GET with repeated url and optional repeated instance_id parameters Up to 100 URL-instance combinations in one API call
Hundreds or thousands of URLs /api/v1/batch/create POST multipart form data with an uploaded text file One URL per line; failed screenshots are retried up to three times

The multiple endpoint is not a JSON-array endpoint: repeat the query parameter. The batch endpoint is intended for larger files and returns work to track. If you need to render every URL in several browser instances, remember that 10 URLs across 10 instances means 100 captures.

2. Prepare and validate the URL list

Normalize the input before making requests. Keep one absolute URL per line, including the scheme (https:// or http://). Remove blank lines and comments, and decide whether duplicate URLs should be captured once or retained. If query strings contain secrets, do not put them in shared files, terminal history, or logs.

https://example.com/
https://example.org/pricing
https://docs.example.net/guide?version=2

For a small request, split the input into chunks of at most 10 URLs. If you also provide instance IDs, keep that list to at most 10. For larger work, upload the complete text file through the batch endpoint.

3. Submit up to 100 captures with screenshot/multiple

The response uses the same format as Browshot’s screenshot-list endpoint. Save each returned screenshot ID alongside its requested URL and instance so that you can later associate completion or failure with the original input.

cURL: two URLs and one instance

export BROWSHOT_KEY='YOUR_API_KEY'
curl --fail-with-body --get 'https://api.browshot.com/api/v1/screenshot/multiple' \
  --data-urlencode 'url=https://example.com/' \
  --data-urlencode 'url=https://example.org/' \
  --data-urlencode 'instance_id=12' \
  --data-urlencode "key=$BROWSHOT_KEY"

Use --data-urlencode for each value rather than concatenating a query string yourself. This correctly encodes ampersands, spaces, and other reserved URL characters in a target URL.

Python: submit a chunk and inspect returned IDs

import os
import requests

API_KEY = os.environ["BROWSHOT_KEY"]
ENDPOINT = "https://api.browshot.com/api/v1/screenshot/multiple"
urls = ["https://example.com/", "https://example.org/"]
instances = [12]

# A list of pairs preserves repeated query parameters.
params = [("url", url) for url in urls]
params += [("instance_id", str(instance)) for instance in instances]
params.append(("key", API_KEY))

response = requests.get(ENDPOINT, params=params, timeout=90)
response.raise_for_status()
data = response.json()
print(data)

# The multiple response matches screenshot/list. Inspect its returned structure
# and persist each screenshot ID with its input URL and instance.

Install the dependency with python -m pip install requests. The final comment is intentional: the documented response format is the screenshot-list format, so adapt ID extraction to the returned object rather than assuming the response is a flat array.

Node.js: submit repeated parameters

const endpoint = 'https://api.browshot.com/api/v1/screenshot/multiple';
const urls = ['https://example.com/', 'https://example.org/'];
const instances = [12];
const params = new URLSearchParams();
for (const url of urls) params.append('url', url);
for (const instance of instances) params.append('instance_id', String(instance));
params.set('key', process.env.BROWSHOT_KEY);

const response = await fetch(`${endpoint}?${params.toString()}`);
if (!response.ok) {
  throw new Error(`Browshot returned HTTP ${response.status}: ${await response.text()}`);
}
const result = await response.json();
console.log(result);
// Persist each returned screenshot ID with its source URL and instance.

Run this as an ES module or in a Node.js environment that supports global fetch, with BROWSHOT_KEY set in the process environment.

4. Track status and retrieve each finished screenshot

A screenshot can be in_process, finished, or error. Query /api/v1/screenshot/info with each ID until it reaches a terminal status. Do not poll in a tight loop: wait between checks, use a reasonable overall deadline, and keep the ID-to-input mapping durable so a process restart does not lose track of work.

The info response includes a screenshot URL when a capture is finished. The following examples show the status check and the finished-image retrieval. Handle error status per item so one unreachable page does not stop the rest of the batch.

cURL: check an ID

curl --fail-with-body --get 'https://api.browshot.com/api/v1/screenshot/info' \
  --data-urlencode 'id=SCREENSHOT_ID' \
  --data-urlencode "key=$BROWSHOT_KEY"

Python: poll a saved screenshot ID

import os
import time
import requests

API_KEY = os.environ["BROWSHOT_KEY"]
INFO = "https://api.browshot.com/api/v1/screenshot/info"
screenshot_id = 123456  # Replace with an ID returned by Browshot.
deadline = time.monotonic() + 600

while True:
    response = requests.get(
        INFO,
        params={"id": screenshot_id, "key": API_KEY},
        timeout=30,
    )
    response.raise_for_status()
    info = response.json()
    status = info.get("status")

    if status == "finished":
        screenshot_url = info.get("screenshot_url")
        if not screenshot_url:
            raise RuntimeError(f"Finished response has no screenshot_url: {info}")
        image = requests.get(screenshot_url, timeout=90)
        image.raise_for_status()
        with open(f"{screenshot_id}.png", "wb") as output:
            output.write(image.content)
        break
    if status == "error":
        raise RuntimeError(f"Screenshot {screenshot_id} failed: {info.get('error', info)}")
    if time.monotonic() >= deadline:
        raise TimeoutError(f"Screenshot {screenshot_id} did not finish before deadline")
    time.sleep(10)

Node.js: poll and save a finished image

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

const key = process.env.BROWSHOT_KEY;
const screenshotId = '123456'; // Replace with an ID returned by Browshot.
const deadline = Date.now() + 10 * 60 * 1000;

while (true) {
  const params = new URLSearchParams({ id: screenshotId, key });
  const response = await fetch(`https://api.browshot.com/api/v1/screenshot/info?${params}`);
  if (!response.ok) throw new Error(`Info request failed: HTTP ${response.status}`);
  const info = await response.json();

  if (info.status === 'finished') {
    if (!info.screenshot_url) throw new Error(`No screenshot_url in response: ${JSON.stringify(info)}`);
    const image = await fetch(info.screenshot_url);
    if (!image.ok) throw new Error(`Image download failed: HTTP ${image.status}`);
    await writeFile(`${screenshotId}.png`, Buffer.from(await image.arrayBuffer()));
    break;
  }
  if (info.status === 'error') throw new Error(`Capture failed: ${info.error ?? JSON.stringify(info)}`);
  if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${screenshotId}`);
  await new Promise(resolve => setTimeout(resolve, 10_000));
}

For a production worker, poll multiple outstanding IDs in controlled groups instead of serially waiting on every page. Apply an overall deadline, record terminal errors, and retry only when appropriate for the failure and account behavior. A returned screenshot_url is the documented retrieval path for a finished response.

5. Submit hundreds or thousands with batch/create

Create a plain-text file with one URL per line, then upload it as multipart form data. Browshot documents the uploaded part as file and requires a POST request with Content-Type: multipart/form-data; let your HTTP client set the multipart boundary rather than setting that header manually. The API retries failed screenshots up to three times before giving up.

cURL batch upload

export BROWSHOT_KEY='YOUR_API_KEY'
curl --fail-with-body 'https://api.browshot.com/api/v1/batch/create' \
  -F "key=$BROWSHOT_KEY" \
  -F 'instance_id=12' \
  -F 'file=@urls.txt;type=text/plain'

Keep the returned batch identifier and use Browshot’s /api/v1/batch/info endpoint to check batch progress and retrieve batch details. The documentation describes optional settings including split, screenshot size, batch name, thumbnail width/height, and output format (png or jpeg). Consult the live endpoint reference for exact accepted values and the response fields for your account.

Python batch upload

import os
import requests

with open("urls.txt", "rb") as url_file:
    response = requests.post(
        "https://api.browshot.com/api/v1/batch/create",
        data={"key": os.environ["BROWSHOT_KEY"], "instance_id": "12"},
        files={"file": ("urls.txt", url_file, "text/plain")},
        timeout=90,
    )
response.raise_for_status()
print(response.json())  # Save the returned batch identifier for /batch/info.

Node.js batch upload

import { readFile } from 'node:fs/promises';

const form = new FormData();
form.set('key', process.env.BROWSHOT_KEY);
form.set('instance_id', '12');
form.set('file', new Blob([await readFile('urls.txt')], { type: 'text/plain' }), 'urls.txt');

const response = await fetch('https://api.browshot.com/api/v1/batch/create', {
  method: 'POST',
  body: form,
});
if (!response.ok) throw new Error(`Batch create failed: HTTP ${response.status} ${await response.text()}`);
console.log(await response.json()); // Save the returned batch identifier.

6. Choose capture settings deliberately

screenshot/multiple accepts the parameters supported by screenshot/create. Relevant documented settings include:

Parameter What it changes Practical guidance
instance_id Browser instance used for rendering; default is 12 Use the same instance for comparable output. Check current account terms and balance for private or shared instances.
size screen captures the screen; page captures the full page Choose based on whether you need an above-the-fold view or a longer page image.
cache Reuse a prior screenshot of the same URL and instance within the requested cache age The documented default is 24 hours. Set cache=0 when a fresh capture is required.
delay Wait after page load before capture; documented range 0–20 seconds, default 5 Increase it for pages that render content after load; a longer delay increases completion time.
max_wait Maximum wait before triggering the PageLoad event; documented range 1–60, default 0 (disabled) Use only when its page-load behavior is needed; check the current reference for interactions with other timing settings.
script_inline JavaScript content executed after the page load event Use only trusted, controlled scripts and verify the live documentation for supported encoding and behavior.

The endpoint supports additional screenshot/create options. Use the Browshot reference for the full current parameter list and valid ranges rather than assuming an option from another screenshot service will work.

7. Reliable bulk processing

  1. Chunk small requests. Send no more than 10 URL values and 10 instance values per multiple call.
  2. Persist submission state. Save input URL, instance, returned screenshot ID, submission time, and eventual terminal status.
  3. Poll conservatively. Use a delay between checks and bounded concurrency; avoid making a status request continuously.
  4. Separate capture failures from transport failures. A successful API submission can still produce a screenshot with status error. Record the API error for that specific URL.
  5. Make downstream writes repeat-safe. Use the screenshot ID as a stable reference in your own result store and avoid overwriting a good image with an error placeholder.
  6. Plan for partial completion. A list can contain both finished and failed pages. Process each result independently.
  7. Protect the API key. Use environment variables or a secret manager; avoid committing it, exposing it in browser-side code, or logging full request URLs.

8. Performance, reliability, and cost

The multiple endpoint reduces submission overhead but does not make each browser render synchronous. The work still completes asynchronously, and pages with slow assets or delayed rendering can take longer. File batches are the documented route for hundreds or thousands of pages; failed captures are retried up to three times. Keep polling and download concurrency bounded so your own worker does not become the bottleneck.

Reuse cache when repeat captures of the same URL and instance may be acceptable; choose cache=0 when freshness matters. A full-page capture and a longer delay can increase work and elapsed time. Browshot documents instance 12 as the free default and a 100 free screenshots per month allowance in its API documentation; that quota and account terms can change, so verify them in your dashboard before budgeting a large run. Private and shared instances require a positive balance. Estimate cost from the number of URL-instance combinations, selected instance, cache behavior, and current account terms, not just the number of API calls.

9. Troubleshooting

Symptom Likely cause Fix
HTTP 400 or an API error such as “Invalid key” Missing/invalid key, malformed URL, or invalid parameter Check the key and use absolute URLs. Read the error response and verify parameter names and values against the current docs.
“Not enough credits” or instance request rejected Selected private/shared instance requires a positive balance or current account quota is insufficient Check account balance and instance access before submitting a large job.
Only one URL appears to be processed Repeated query parameters were collapsed by a manually built URL or client code Append one url parameter per URL using URLSearchParams.append, Python list-of-pairs, or repeated cURL --data-urlencode.
Multiple request exceeds documented limit More than 10 URLs or 10 instance IDs were sent Split into several multiple calls or use the file batch endpoint.
Batch upload rejected or file is empty Multipart field name/content type is wrong, or a manually supplied multipart header omitted its boundary Use the documented file part and let cURL, Requests, or FormData generate the multipart header.
Screenshot stays in progress Page is slow, capture is still running, or the worker stopped polling too early Continue bounded polling until finished or error; set an application deadline and investigate pages that exceed it.
Screenshot reaches error or shows an unexpected page Page unavailable, URL redirects or requires login, or render timing is insufficient Inspect the error details, verify the URL in a browser, and adjust supported delay or automation settings as appropriate.
Wrong screenshot dimensions or incomplete long page size defaults to screen, or page content loads below the fold after the capture point Request size=page for a full-page image; tune delay for content rendered asynchronously.
Stale image returned Cache reused a recent capture for the same URL and instance Set cache=0 to request a fresh capture.

10. When pages need interaction

For a page that requires navigation or interaction, Browshot’s automation guide describes steps such as click, type, javascript, sleep, navigate, and screenshot, passed as JSON steps to screenshot/create. Validate the supported options and whether they apply to the multiple or batch workflow before building a bulk job. Use placeholders for credentials and never put real passwords into article examples, logs, or source control. See the Browshot login and automation guide.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its single GET endpoint returns a screenshot or PDF; for large lists, call it once per URL from your own loop or job queue. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. AI agents can take screenshots through its MCP server. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000.

See the ScreenshotNeo API documentation for the available options.

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}`);

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

FAQ

Can I send the URL list as JSON?

For the multiple endpoint, the documented request uses repeated query parameters. For a large list, upload the documented text file with one URL per line.

Does one multiple call guarantee every screenshot is finished?

No. Treat submission and capture completion as separate steps; check each screenshot ID until it is finished or in error.

Can I capture the same URLs on multiple browser instances?

Yes. The endpoint accepts up to 10 instance IDs as well as up to 10 URLs, producing up to 100 combinations per call.

Should I use the multiple or batch endpoint for 20 URLs?

Use multiple in chunks of up to 10 URLs when you want direct control over small requests; use a batch file when the input is part of a larger file-based workflow.

Sources