How to use Thumbalizr to create website thumbnails in bulk
Generate Thumbalizr thumbnails for a list of URLs with a signed API request per site, then handle results, errors, and monthly limits reliably.
Thumbalizr’s documented Embed API captures one target URL per request. To create thumbnails in bulk, keep a list of URLs, generate a correctly encoded and signed API request for each one, and process the responses while checking their status headers. The documentation reviewed here describes per-request API calls, not a separate bulk-upload endpoint.
This guide uses Python to automate a URL list, explains the API settings and response handling, and shows equivalent cURL and Node.js requests. Keep your API secret on the server or in a secure environment variable; do not expose it in browser code or a public repository.
1. Get API credentials and prepare your URL list
- Create a Thumbalizr account and obtain the API key and secret in the member area. Thumbalizr says registration for a basic API key is free. See its homepage and API documentation.
- Prepare one absolute, publicly reachable URL per line. For example, save
https://example.com/andhttps://www.example.org/pricinginurls.txt. - Decide on consistent capture settings, such as thumbnail width, image format, viewport size, and whether you need the whole page or only the visible screen.
- Check the current plan’s monthly capacity and parameter limits before a large run. Thumbalizr’s feature and pricing page lists quotas by plan; these can change, so confirm in the live account or checkout flow before relying on a limit or price.
The API documentation says the target URL is the only required parameter; other values can default to the account’s profile settings. Explicit parameters make a batch more repeatable.
2. Understand the signed request
Each Embed API URL includes an embed key, a token, and query parameters. The documentation describes the token as an MD5 of the URL query string plus the secret. It warns that the query parameters, especially the target URL, must be encoded correctly. Build the query with a URL-encoding library before signing it, and follow the exact ordering and token construction shown in Thumbalizr’s current API documentation.
Do not hand-concatenate a URL containing another URL: characters such as &, ?, #, spaces, and non-ASCII characters need correct encoding. If your integration signs one representation but sends another, the token can fail validation. The Python example below delegates query encoding to the standard library and leaves the token-generation function isolated so you can match Thumbalizr’s documented signing format exactly.
3. Automate a batch with Python
Install the HTTP client with python -m pip install requests. Set credentials in environment variables, then save this as thumbalizr_batch.py. The signing function is intentionally the single place to implement the exact query-string-plus-secret recipe and parameter ordering from Thumbalizr’s API page. Do not substitute an unverified serialization convention: signing formats are sensitive to encoding and ordering.
import hashlib
import os
import time
from urllib.parse import urlencode
import requests
API_KEY = os.environ["THUMBALIZR_API_KEY"]
API_SECRET = os.environ["THUMBALIZR_API_SECRET"]
EMBED_ENDPOINT = os.environ["THUMBALIZR_EMBED_ENDPOINT"]
# Keep the documented parameters in one ordered mapping. Confirm parameter
# names, token construction, and endpoint format against Thumbalizr's API docs.
CAPTURE_OPTIONS = {
"width": "320",
"format": "png",
"size": "page",
"bwidth": "1280",
"bheight": "900",
"delay": "5",
}
def build_signed_url(target_url):
params = {
"url": target_url,
**CAPTURE_OPTIONS,
"key": API_KEY,
}
# Use the precise query serialization required by the current API docs.
# The documented token is an MD5 of the URL query string and secret.
query = urlencode(params)
token = hashlib.md5((query + API_SECRET).encode("utf-8")).hexdigest()
params["token"] = token
return EMBED_ENDPOINT + "?" + urlencode(params)
def capture(target_url, output_path, attempts=3):
request_url = build_signed_url(target_url)
for attempt in range(1, attempts + 1):
try:
response = requests.get(request_url, timeout=(10, 90), allow_redirects=True)
status = response.headers.get("X-Thumbalizr-Status", "")
if status == "OK":
with open(output_path, "wb") as output:
output.write(response.content)
return "OK"
if status == "QUEUED":
# The API reports queued work in a response header. Poll or
# retrieve the resulting capture according to current docs.
return "QUEUED"
if status == "FAILED":
reason = response.headers.get("X-Thumbalizr-Error", "unspecified error")
return f"FAILED: {reason}"
return f"UNEXPECTED: HTTP {response.status_code}, status={status!r}"
except requests.RequestException as error:
if attempt == attempts:
return f"REQUEST ERROR: {error}"
time.sleep(min(2 ** (attempt - 1), 8))
with open("urls.txt", encoding="utf-8") as source:
targets = [line.strip() for line in source if line.strip() and not line.lstrip().startswith("#")]
os.makedirs("thumbnails", exist_ok=True)
for index, target in enumerate(targets, start=1):
result = capture(target, f"thumbnails/{index:04}.png")
print(f"{target}\t{result}")
Important: the dossier does not provide the literal Embed API endpoint or its complete parameter names and canonical signing serialization. The documentation is authoritative for those details. Set THUMBALIZR_EMBED_ENDPOINT to the endpoint shown in your account/API docs, and adapt the key and option names and token construction to that same documented format before running. This avoids pretending a partial signing example is guaranteed to authenticate.
Run it
export THUMBALIZR_API_KEY='your-embed-key'
export THUMBALIZR_API_SECRET='your-secret'
export THUMBALIZR_EMBED_ENDPOINT='endpoint-from-current-thumbalizr-api-docs'
python thumbalizr_batch.py
Use a small test file first. Inspect the response headers and downloaded file before processing the full list. The script writes successful captures under thumbnails/ and logs a result for each URL.
4. Request one capture with cURL
For a single request, construct and sign the query according to the current API documentation. This shell template illustrates the request shape; replace the placeholders with the documented endpoint, key, token, and correctly encoded query. Do not put a real secret into a shared shell history.
curl --get 'EMBED_API_ENDPOINT_FROM_THUMBALIZR_DOCS' \
--data-urlencode 'url=https://example.com/' \
--data-urlencode 'width=320' \
--data-urlencode 'format=png' \
--data-urlencode 'size=page' \
--data-urlencode 'key=YOUR_EMBED_KEY' \
--data-urlencode 'token=TOKEN_COMPUTED_FROM_THE_DOCUMENTED_QUERY_AND_SECRET' \
--dump-header response-headers.txt \
--output thumbnail.png
Because the token depends on the exact serialized query, calculate it for the same parameter representation you send. The official docs provide language examples and should be treated as the source for exact request construction.
5. Node.js request pattern
Node’s URLSearchParams safely encodes query values. Generate the token using the precise canonical query format specified by Thumbalizr; the placeholder below marks that integration-specific step.
import { createHash } from 'node:crypto';
import { writeFile } from 'node:fs/promises';
const endpoint = process.env.THUMBALIZR_EMBED_ENDPOINT;
const key = process.env.THUMBALIZR_API_KEY;
const secret = process.env.THUMBALIZR_API_SECRET;
if (!endpoint || !key || !secret) throw new Error('Set Thumbalizr credentials and endpoint');
function signedUrl(target) {
const params = new URLSearchParams({
url: target,
width: '320',
format: 'png',
size: 'page',
bwidth: '1280',
bheight: '900',
delay: '5',
key,
});
// Verify canonical query ordering/encoding against current Thumbalizr docs.
const token = createHash('md5').update(params.toString() + secret).digest('hex');
params.set('token', token);
return `${endpoint}?${params.toString()}`;
}
async function capture(target, filename) {
const response = await fetch(signedUrl(target), { signal: AbortSignal.timeout(90000) });
const status = response.headers.get('x-thumbalizr-status');
if (status === 'OK') {
await writeFile(filename, Buffer.from(await response.arrayBuffer()));
return 'OK';
}
if (status === 'QUEUED') return 'QUEUED';
if (status === 'FAILED') return `FAILED: ${response.headers.get('x-thumbalizr-error') ?? 'unspecified error'}`;
return `UNEXPECTED: HTTP ${response.status}, status=${status}`;
}
const targets = ['https://example.com/', 'https://www.example.org/pricing'];
for (let i = 0; i < targets.length; i++) {
console.log(targets[i], await capture(targets[i], `thumbnail-${i + 1}.png`));
}
As with the Python version, verify the exact endpoint, parameter names, and signature canonicalization in the current official documentation before use.
6. Choose capture options for the whole batch
| Setting | What it controls | Batch guidance |
|---|---|---|
width |
Output thumbnail width. The documented range varies by plan, from 1 to 2,000 pixels depending on tier. | Choose one width that fits the destination; confirm your tier’s maximum. |
format |
JPG or PNG output. | Use the same format for predictable downstream handling. JPG quality applies to JPEG. |
| JPEG quality | Documented quality range is 10–100. | Lower quality can reduce output size; compare appearance on representative pages. |
size |
screen for the visible screen or page for full page. |
The documented free tier is screen-size; paid tiers list full-page capture. |
bwidth, bheight |
Browser viewport dimensions used to render the page. | Keep them consistent if thumbnails need comparable framing. |
delay |
Wait after loading before capture; documented range is 1–30 seconds. Five seconds is shown as the free and Silver default. | Increase for pages that populate late, but longer delays reduce throughput. |
| Refresh timestamp | A timestamp can request a refreshed capture on paid tiers. | Use only when you need to bypass reuse of an earlier result; check plan availability. |
| Watermark | Availability depends on plan. | Check the current tier matrix if the thumbnail will be published. |
| Capture country | The listed plans document US or Germany capture locations. | Choose based on the view relevant to your audience and verify availability on your tier. |
Thumbalizr’s Features & Pricing page displays 100 screenshots/month for Free, 2,000 for Silver, 3,000 for Gold, and 5,000 or more for Platinum. It lists one-month prices of €8/$9 for Silver, €12/$13 for Gold, and €18/$20 for Platinum at 5,000 screenshots, with higher Platinum volumes also shown. These are figures displayed on the page when researched, not a guarantee of today’s checkout price, currency handling, or your account’s terms. Verify before budgeting a batch.
7. Handle queued, successful, and failed captures
Read the response headers for every request. Thumbalizr documents X-Thumbalizr-Status values QUEUED, OK, and FAILED; X-Thumbalizr-Generated gives a generation date, and X-Thumbalizr-Error provides an error reason.
- OK: save the returned image bytes and associate them with the source URL.
- QUEUED: treat the capture as pending. Follow the current API documentation’s retrieval or polling procedure rather than marking it failed.
- FAILED: record the error header and URL. Retry only when the cause is transient; a persistent page or configuration error will not be fixed by repeated requests.
- Missing or unfamiliar status: log the HTTP code, headers, and a bounded portion of the response for diagnosis. Do not save an error body with a
.pngextension.
Write a manifest alongside the images containing source URL, output filename, status, generation header, attempt count, and error reason. This makes partial batches resumable without recapturing successful entries unnecessarily.
8. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Token rejected | The signed query differs from the sent query because of encoding, ordering, parameter names, or secret handling. | Rebuild signing from the exact canonical format and parameter sequence in the official docs. Test one URL with reserved characters. |
| Capture returns an error instead of an image | The response is FAILED, the target cannot be rendered, or the request is malformed. |
Check X-Thumbalizr-Error, status, and HTTP code. Validate the target URL and options. |
| Capture appears incomplete | Visible-screen mode was used, or the page needed more time to finish rendering. | Use the documented page-size setting where supported, and adjust the delay within the tier’s allowed range. |
| Images differ in framing | Viewport dimensions or per-account defaults differ. | Set browser width and height explicitly for all requests; set other options rather than relying on profile defaults. |
| Batch stops partway through | Unhandled network timeout or process interruption. | Persist a per-URL manifest and resume only unfinished rows. Use bounded retries with backoff for transient transport errors. |
| Quota or option limit reached | The plan does not include enough monthly captures or the requested width/feature. | Check the current account plan and feature matrix before rerunning. Do not assume a limit shown in an older page remains current. |
| Unexpected page version | A previous capture may be reused or the target changed after capture. | Check the generated date header and use the documented paid-tier refresh timestamp option if appropriate. |
9. Performance, reliability, and cost
Bulk volume is the number of individual captures, so estimate the run against the account’s monthly quota before starting. A sequential script is simple and gentle, but total time grows with page load and configured delay. Do not add high concurrency without checking Thumbalizr’s current service guidance and account limits; the reviewed material does not establish a safe concurrency number.
For reliability, use explicit timeouts, bounded retries for network failures, and durable per-URL results. A queued response is a distinct state, not a successful saved image. Validate file signatures or attempt to open each output before publishing it. Preserve the source URL-to-file mapping so a failed page can be retried independently.
For storage, PNG preserves detail but can be larger than JPEG; JPEG quality controls the tradeoff when using JPG. Choose output width and quality based on the actual display size. Since quota and price are plan-specific and may change, recheck Thumbalizr’s pricing page immediately before a recurring or large capture run.
10. Migration and current service notes
Thumbalizr published an April 8, 2026 announcement describing a phased move of its screenshot engine from Browshot infrastructure to ScreenshotCenter. The announcement said new accounts would use ScreenshotCenter first and existing accounts would move in batches, while existing integrations, API calls, embed codes, and settings were intended to remain unchanged through that transition. That post announced a rollout; it does not independently confirm its completion. Check the current official documentation or account notices for present backend status. The same announcement described geographic locations, ad and popup filtering, video recordings, PDF exports, and expanded device emulation as planned for later rollout; do not assume those are available without newer confirmation. Thumbalizr’s announcement.
Or skip the browser setup
If your goal is a batch of reliable site captures without maintaining a browser or signing integration, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request accepts a URL and returns PNG, JPEG, WebP, or PDF. See the 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}`);
For a multi-URL job, make one request per URL or use its bulk capture option, which accepts up to 100 URLs per call. Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 shots/month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month with no card.
FAQ
Does Thumbalizr have a bulk upload button?
The reviewed API documentation shows a request per target URL, not a separate bulk-upload control. Automate those requests from a URL list.
Can I embed the thumbnails on my site?
Thumbalizr describes its service as creating screenshots that can be embedded on a website. Follow its current Embed API instructions for the appropriate capture URL and usage.
Can I make the captures from frontend JavaScript?
A signed request uses a secret, so generating it in public browser code would expose that secret. Generate requests on a server or another private runtime.
Which format should I choose?
Thumbalizr documents JPG and PNG. Pick based on your display and file-size needs, then keep the format consistent across the batch.


