ScreenshotNeo

BlogHow-to

How to Take Bulk Website Screenshots with Abstract Screenshot API

Capture a list of URLs with Abstract by queuing single-URL requests and pacing them to your plan’s limits. Learn what the docs do—and don’t—confirm about batch requests.

By the ScreenshotNeo team4 October 202611 min read

Direct answer: The available Abstract Website Screenshot API product page documents a request for one URL at a time. To capture a list, keep the URLs in a queue, make one request per URL, and pace requests to the per-second limit and request quota on your active plan. The page mentions batch-processing error handling, but the material reviewed does not document a multi-URL endpoint or its request schema. Do not assume that a single request can submit an entire list.

Abstract’s documented endpoint is https://screenshot.abstractapi.com/v1/, with api_key and url query parameters. The product page says the service can capture a URL or raw HTML and describes JPEG, PNG, and GIF output, along with viewport, dimensions, CSS injection, and timing controls. Check the official product page and documentation index for current details before relying on options beyond the basic request shown here.

1. Check whether you need bulk capture

Bulk capture means repeating the same capture operation for a collection of pages, often on a schedule. Common uses include multiple-device QA, recurring snapshots, website previews, checking backlink or ad placements, and safe previews for security workflows. For any of these, first define what counts as a successful capture: for example, one image per URL at a fixed viewport, or several images per URL for different device sizes.

Abstract’s published product page describes the capture API and use cases, but the available evidence does not specify a batch payload. The reliable approach supported by the documented single-URL request is a client-side queue of individual requests. Treat the queue and pacing code below as an implementation pattern, not an Abstract-prescribed SDK or batch recipe.

2. Plan your queue around quotas and rate limits

Before starting a job, count the captures you intend to make. If you capture each URL at multiple viewport settings, count each URL-and-viewport combination as a separate request unless your current account documentation says otherwise. Then check your account’s active request quota and per-second limit.

Plan snapshot on the product page Shown request quota Shown rate
Free 100 requests 1 request per second
Standard 60,000 requests 3 requests per second

These are a snapshot of Abstract’s product page as accessed on October 3, 2026. Its annual pricing view shows 60,000 requests per year for Standard, while its monthly view shows 60,000 per month. Confirm your own billing selection, plan dashboard, and current limits before estimating capacity or cost. The figures above are not a guarantee that every account has the same allowance.

For a simple sequential queue, the approximate lower bound on request pacing is one second between starts on the shown Free limit, or one third of a second on the shown Standard limit. Actual job time will also depend on how long each capture takes. Leave headroom for other jobs using the same account, and do not start multiple independent queues that together exceed the account limit.

3. Capture a URL list with Python

This runnable example reads one URL per line from urls.txt, sends one request at a time, and saves each response. It uses only the documented endpoint and query parameters. Install the dependency with python -m pip install requests. Set the API key in an environment variable rather than putting it in source control.

import os
import re
import time
from pathlib import Path
from urllib.parse import urlparse

import requests

API_KEY = os.environ["ABSTRACT_API_KEY"]
ENDPOINT = "https://screenshot.abstractapi.com/v1/"
URLS_FILE = Path("urls.txt")
OUTPUT_DIR = Path("screenshots")
SECONDS_BETWEEN_REQUESTS = 1.0  # Use at least your account's required interval.
TIMEOUT_SECONDS = 90

OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
urls = [line.strip() for line in URLS_FILE.read_text().splitlines()
        if line.strip() and not line.lstrip().startswith("#")]

session = requests.Session()
for index, url in enumerate(urls, start=1):
    parsed = urlparse(url)
    if parsed.scheme not in {"http", "https"} or not parsed.netloc:
        print(f"SKIP {index}: not an absolute HTTP(S) URL: {url}")
        continue

    # Use a stable, filesystem-safe name. The index prevents collisions
    # when two input URLs have the same path or hostname.
    host = re.sub(r"[^A-Za-z0-9.-]+", "_", parsed.netloc)
    path = re.sub(r"[^A-Za-z0-9._-]+", "_", parsed.path.strip("/")) or "home"
    destination = OUTPUT_DIR / f"{index:04d}-{host}-{path}.img"

    try:
        response = session.get(
            ENDPOINT,
            params={"api_key": API_KEY, "url": url},
            timeout=TIMEOUT_SECONDS,
        )
        response.raise_for_status()
        if not response.content:
            raise ValueError("empty response body")
        destination.write_bytes(response.content)
        print(f"OK {index}/{len(urls)}: {url} -> {destination}")
    except (requests.RequestException, OSError, ValueError) as exc:
        print(f"ERROR {index}/{len(urls)}: {url}: {exc}")

    if index < len(urls):
        time.sleep(SECONDS_BETWEEN_REQUESTS)

Run it after setting the key, for example with export ABSTRACT_API_KEY='your-key' on macOS or Linux, then python capture_urls.py. The output uses the .img extension because the basic request shown here does not specify a format parameter. If you configure a documented output format, use its matching extension and verify the returned content type before processing the file.

Make the queue resumable for larger jobs

For a short list, logging failures may be enough. For a long or recurring list, write a manifest with each URL’s status, output path, attempt count, and last error. On restart, process only pending or explicitly retryable entries. Save each successful response before moving to the next URL so a later failure does not erase completed work. Keep the API key out of the manifest and logs.

4. The same single-URL request in cURL

This is the basic request shape from Abstract’s product page, adapted to download one capture. Repeat it once per URL in your own queue, observing your account’s request limit.

curl --fail --silent --show-error \
  --get 'https://screenshot.abstractapi.com/v1/' \
  --data-urlencode "api_key=$ABSTRACT_API_KEY" \
  --data-urlencode 'url=https://example.com' \
  --output screenshot.img

For a list, a shell loop can call this command for each input line, but a small program is usually easier to make resumable and to associate errors with their URLs. Avoid printing a fully expanded request URL: query parameters include the API key.

5. Node.js queue example

This example uses the built-in fetch available in current Node.js releases. It sends requests sequentially and saves response bodies. Put one URL per line in urls.txt, set ABSTRACT_API_KEY, and run the script with Node.js.

import { readFile, mkdir, writeFile } from 'node:fs/promises';
import { setTimeout as delay } from 'node:timers/promises';
import { URL } from 'node:url';

const apiKey = process.env.ABSTRACT_API_KEY;
if (!apiKey) throw new Error('Set ABSTRACT_API_KEY first');

const endpoint = 'https://screenshot.abstractapi.com/v1/';
const outputDir = 'screenshots';
const secondsBetweenRequests = 1.0; // Set for your active plan's rate limit.
const urls = (await readFile('urls.txt', 'utf8'))
  .split(/\r?\n/)
  .map((line) => line.trim())
  .filter((line) => line && !line.startsWith('#'));

await mkdir(outputDir, { recursive: true });

for (let i = 0; i < urls.length; i++) {
  const target = urls[i];
  let parsed;
  try {
    parsed = new URL(target);
    if (!['http:', 'https:'].includes(parsed.protocol)) throw new Error('unsupported protocol');
  } catch (error) {
    console.error(`SKIP ${i + 1}: invalid HTTP(S) URL ${target}: ${error.message}`);
    continue;
  }

  const query = new URLSearchParams({ api_key: apiKey, url: target });
  try {
    const response = await fetch(`${endpoint}?${query}`, {
      signal: AbortSignal.timeout(90_000),
    });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const bytes = Buffer.from(await response.arrayBuffer());
    if (bytes.length === 0) throw new Error('empty response body');
    const safeHost = parsed.host.replace(/[^A-Za-z0-9.-]+/g, '_');
    const safePath = parsed.pathname.replace(/[^A-Za-z0-9._-]+/g, '_') || 'home';
    const filename = `${String(i + 1).padStart(4, '0')}-${safeHost}-${safePath}.img`;
    await writeFile(`${outputDir}/${filename}`, bytes);
    console.log(`OK ${i + 1}/${urls.length}: ${target} -> ${filename}`);
  } catch (error) {
    console.error(`ERROR ${i + 1}/${urls.length}: ${target}: ${error.message}`);
  }

  if (i < urls.length - 1) await delay(secondsBetweenRequests * 1000);
}

For reliable operation, persist completion state and errors to a manifest or database instead of relying only on terminal output. The example deliberately does not add concurrent workers or automatic retries: the reviewed product material does not define a concurrency limit or retry contract for a client queue.

6. Choosing output and capture behavior

Abstract’s product page describes the following capabilities. Parameter names and exact accepted values are not included in the research material for this article, so confirm them in the live API reference before adding them to a request.

Need What the product page describes What to verify before implementation
Image format JPEG, PNG, and GIF output The current parameter name, supported values, and response content type
Viewport or dimensions Viewport and dimension controls Exact width and height parameters, limits, and defaults
Delayed page rendering Capture delays and timing controls Parameter names, allowed delay range, and how timing interacts with page load
Controlled presentation CSS injection How CSS is passed, escaping requirements, and any size limits
HTML input The FAQ says raw HTML can be captured Exact request field, encoding rules, and supported behavior for external assets

Choose one consistent configuration for comparable snapshots. A delay can help when content appears after initial page load, but it also increases the time spent per capture. CSS injection can hide irrelevant sections or create a controlled presentation, but it changes what the resulting image represents. If you need region-specific page versions, the product page’s FAQ says location-based capture is not currently supported and is planned for a future version.

7. Does Abstract support batch processing?

The product page mentions batch-processing requests and says the service has enhanced error handling, detailed errors, standardized error codes, and automatic retries for failed tasks. The material reviewed does not show a multi-URL endpoint, a request body for submitting many URLs, a batch size, or a client-side concurrency limit. That wording alone is not enough to build against a batch schema.

If you need server-side batch submission, check the current detailed API reference or ask Abstract support to confirm the endpoint, payload, limits, result retrieval, and retry behavior for your account. Until those details are confirmed, use the documented single-URL request in a paced queue.

8. Reliability, performance, and cost

Reliability

  • Keep the source URL list and capture results separately so failures can be retried without repeating completed work.
  • Record the URL, timestamp, outcome, and error for each attempt. Do not log the API key.
  • Validate that a response is non-empty and, when you add format controls, check its content type before treating it as an image.
  • Use a finite request timeout. A timeout is an unsuccessful attempt from the client’s perspective; whether the service completed work after the connection ended is not established by the available material.
  • Do not assume a retry is safe or free of quota impact. The product page mentions automatic retries for failed tasks in its batch-processing description, but does not define the client request retry contract.

Performance

With sequential requests, total elapsed time is at least the number of requests divided by the permitted request rate, plus the time required to render and return each capture. A long page delay or slow target site extends the job. More parallel workers do not necessarily make a job faster if they exceed the account’s rate limit; use concurrency only when your current plan documentation explicitly permits it.

Cost and quota

Estimate request usage before a run: number of URLs multiplied by the number of distinct capture configurations per URL. Compare that total with the quota for the active plan and billing period. The published Free and Standard figures above can change, and the Standard quota differs between the page’s annual and monthly pricing views. Confirm current terms in your account before a production or recurring job.

9. Troubleshooting

Symptom Likely cause What to do
Authentication error The API key is missing, incorrect, or not being passed as api_key. Check the environment variable and query parameter name. Keep the key private and do not include it in shared logs.
The URL is rejected or produces an unexpected result The input may not be a complete HTTP(S) URL, or the target may behave differently when fetched by a hosted service. Validate the URL scheme and hostname, then try the same page in a browser. Check Abstract’s current API guidance for target restrictions.
Requests are limited or fail during a large run The queue may exceed the active plan’s per-second rate or quota, including requests from another job. Reduce the request rate, coordinate concurrent jobs, and check the account dashboard for current limits and remaining quota.
The saved file is empty or is not a usable image The request may have failed, returned an error response, or used an output configuration that differs from the assumed format. Check the HTTP status and response details before saving; verify the configured format and returned content type.
Important content is missing from the screenshot The page may render content after the initial load. Review the documented timing controls and verify their parameter names and limits in the current reference.
Images use the wrong page variant The site personalizes content by geography, while Abstract’s FAQ says location capture is not currently supported. Do not treat the result as a geographically localized view. Check for an updated location feature if that requirement is essential.
The job stopped partway through A process interruption or individual request failure can leave a partial output set. Use a persistent manifest, mark successful files, and resume only unfinished entries.

10. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request captures a URL as PNG, JPEG, WebP, or PDF. For bulk work, its API also supports bulk capture of up to 100 URLs per call. See the ScreenshotNeo API documentation for request options and the current API 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 would accept them; 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot. Each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed. Response headers report the page verdict and billing status.
  • An MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots, get page information, and capture PDFs.
  • 1,000 screenshots per month are free with no card. Paid plans start at $5 for 3,000 screenshots.

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

11. FAQ

Can I screenshot a list of URLs with Abstract?

Yes, the documented single-URL request can be repeated for each URL in a list. The material reviewed does not establish a multi-URL request schema, so use a paced client queue unless the current detailed reference confirms a batch endpoint.

How many screenshots per second can I take?

The product page snapshot shows 1 request per second for Free and 3 per second for Standard. Check the active plan in your account because limits and billing terms can change.

Can I send raw HTML instead of a public URL?

Abstract’s FAQ says raw HTML is accepted. Confirm the current field name and encoding requirements in the detailed API documentation.

Can I capture how a site looks in another country?

The product page says capture from different locations is not currently supported; its FAQ describes location configuration as planned.

Does Abstract retry failed tasks?

The product page mentions automatic retries for failed tasks in its description of batch-processing error handling. The available evidence does not define retry behavior for a client-side queue or a multi-URL endpoint.