PagePeeker Screenshot API in Python: Capture a List of URLs
Capture a list of URLs with PagePeeker in Python. Learn its one-request-per-URL flow, readiness checks, quota rules, and common fixes.
To capture a list of URLs with PagePeeker in Python, send one request to its V2 thumbnail endpoint for each URL and save the returned image bytes. PagePeeker documents a single target url per thumbnail request; its cited API documentation does not describe a multi-URL batch endpoint. The example below uses Python’s requests library and keeps the account code on the server.
PagePeeker identifies V2 as its current recommended API. V1 is discontinued and redirects to V2. See the PagePeeker API documentation for the current endpoint and account details.
1. Install the Python dependency
python -m pip install requests
Use a virtual environment if this script is part of a project. The examples assume Python 3.8 or later.
2. Capture a list of URLs
This runnable script makes one thumbnail request per URL, URL-encodes the parameters, checks for HTTP errors, and writes each response to a numbered JPEG file. Set PAGEPEEKER_CODE in the server environment when using a paid or unbranded account. The free branded service ignores the code parameter.
import os
from pathlib import Path
from urllib.parse import urlencode
import requests
ENDPOINT = "http://api.pagepeeker.com/v2/thumbs.php"
API_CODE = os.environ.get("PAGEPEEKER_CODE", "")
OUTPUT_DIR = Path("pagepeeker-thumbnails")
SIZE = "m"
URLS = [
"https://example.com",
"https://example.org/path?source=python&sort=recent",
]
def capture(url: str, output_path: Path, size: str = SIZE) -> None:
params = {"size": size, "url": url}
if API_CODE:
params["code"] = API_CODE
# urlencode encodes the target URL as a query parameter value.
request_url = f"{ENDPOINT}?{urlencode(params)}"
response = requests.get(request_url, timeout=(10, 60))
response.raise_for_status()
content_type = response.headers.get("Content-Type", "")
if not content_type.lower().startswith("image/"):
raise RuntimeError(
f"Expected an image for {url}; got Content-Type {content_type!r}"
)
output_path.write_bytes(response.content)
print(f"Saved {url} -> {output_path} ({len(response.content)} bytes)")
def main() -> None:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
for index, url in enumerate(URLS, start=1):
try:
capture(url, OUTPUT_DIR / f"capture-{index}.jpg")
except requests.RequestException as exc:
print(f"Request failed for {url}: {exc}")
except (OSError, RuntimeError) as exc:
print(f"Could not save a valid thumbnail for {url}: {exc}")
if __name__ == "__main__":
main()
The example uses api.pagepeeker.com, the documented entry point for unbranded and paid accounts. For the free branded service, replace ENDPOINT with http://free.pagepeeker.com/v2/thumbs.php. The target URL is passed through urlencode, so characters such as & in its query string do not accidentally become parameters to PagePeeker.
Choose an output filename safely
Numbered filenames avoid collisions when different URLs have the same hostname or path. For repeatable runs, derive names from a normalized URL and a hash, and retain the original URL in a manifest. Avoid using a raw URL as a filename: it can contain characters that are invalid on some filesystems and can exceed path limits.
3. Select a thumbnail size
| Value | Documented dimensions |
|---|---|
t |
90 × 68 px |
s |
120 × 90 px |
m |
200 × 150 px |
l |
400 × 300 px |
x |
480 × 360 px |
These are the sizes listed in PagePeeker’s API documentation; premium accounts may have additional sizes. The regular thumbnail endpoint returns a thumbnail, not a full-page screenshot. PagePeeker lists full-page capture as a Premium offering.
4. Optional request parameters
| Parameter | Use | Notes |
|---|---|---|
size |
Select a documented thumbnail size. | The API documentation lists t, s, m, l, and x. |
url |
Target page to capture. | Required. URL-encode it as a query parameter. |
code |
Account API key. | Recommended for server-side calls. Do not put it in browser code or a public repository. The free branded service ignores it. |
refresh=1 |
Force regeneration. | For premium accounts. |
wait |
Set the maximum seconds to wait for a thumbnail before PagePeeker returns the generated image or a placeholder. | Premium only. |
Only add parameters that apply to the account you are using. In particular, a non-empty wait value is not a general substitute for the separate readiness API.
5. Check whether a thumbnail is ready
PagePeeker documents a readiness endpoint at /v2/thumbs_ready.php. It returns JSON fields named Error and IsReady. The following example starts generation, then checks readiness with a bounded retry loop before downloading the image. The three-second interval and retry limit are choices in this sample; PagePeeker’s documentation does not prescribe a Python polling interval.
import os
import time
from pathlib import Path
from urllib.parse import urlencode
import requests
API_CODE = os.environ.get("PAGEPEEKER_CODE", "")
BASE = "http://api.pagepeeker.com/v2"
URL = "https://example.com"
SIZE = "m"
OUTPUT = Path("example-thumbnail.jpg")
def params_for(url: str) -> dict[str, str]:
params = {"size": SIZE, "url": url}
if API_CODE:
params["code"] = API_CODE
return params
# Request generation. The response may be an image or a placeholder, depending
# on readiness and account settings; this call also consumes an API call.
start_url = f"{BASE}/thumbs.php?{urlencode(params_for(URL))}"
start_response = requests.get(start_url, timeout=(10, 60))
start_response.raise_for_status()
ready_url = f"{BASE}/thumbs_ready.php?{urlencode(params_for(URL))}"
for attempt in range(20):
ready_response = requests.get(ready_url, timeout=(10, 30))
ready_response.raise_for_status()
status = ready_response.json()
if status.get("Error"):
raise RuntimeError(f"PagePeeker readiness error: {status['Error']}")
if status.get("IsReady"):
image_response = requests.get(start_url, timeout=(10, 60))
image_response.raise_for_status()
image_response.raise_for_status()
if not image_response.headers.get("Content-Type", "").lower().startswith("image/"):
raise RuntimeError("PagePeeker response was not an image")
OUTPUT.write_bytes(image_response.content)
print(f"Saved {OUTPUT}")
break
if attempt < 19:
time.sleep(3)
else:
raise TimeoutError(f"Thumbnail did not become ready: {URL}")
The readiness endpoint’s exact JSON types and error details should be handled according to the response your account receives. Do not poll indefinitely: each readiness check counts as an API call, so a bounded loop protects both runtime and quota.
6. cURL, Python, and Node.js request shapes
For a single URL, these examples show how to request and save a thumbnail. Use the PagePeeker entry point and account code appropriate to your account; keep account codes on the server side.
cURL
curl -G "http://api.pagepeeker.com/v2/thumbs.php" \
--data-urlencode "size=m" \
--data-urlencode "url=https://example.com" \
--output example-thumbnail.jpg
Python
import requests
response = requests.get(
"http://api.pagepeeker.com/v2/thumbs.php",
params={"size": "m", "url": "https://example.com"},
timeout=(10, 60),
)
response.raise_for_status()
if not response.headers.get("Content-Type", "").lower().startswith("image/"):
raise RuntimeError("Response was not an image")
with open("example-thumbnail.jpg", "wb") as image_file:
image_file.write(response.content)
Node.js
const params = new URLSearchParams({
size: 'm',
url: 'https://example.com',
});
const response = await fetch(
`http://api.pagepeeker.com/v2/thumbs.php?${params}`,
{ signal: AbortSignal.timeout(60000) },
);
if (!response.ok) {
throw new Error(`PagePeeker returned HTTP ${response.status}`);
}
const contentType = response.headers.get('content-type') || '';
if (!contentType.toLowerCase().startsWith('image/')) {
throw new Error(`Expected image response; got ${contentType}`);
}
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('example-thumbnail.jpg', image)
);
7. Does PagePeeker have a batch API?
The cited PagePeeker API documentation describes a thumbnail request with one url parameter, not a multi-URL batch operation. For a Python list, loop over the URLs as in the example. Each URL requires its own thumbnail request. Avoid applying batch parameters documented by another screenshot provider to PagePeeker.
For larger lists, process URLs in small groups with a limited number of workers. This gives you concurrency without creating an uncontrolled burst of requests. Handle failures per URL so one slow or invalid page does not discard successful files.
8. Quota, caching, plan choice, and cost
PagePeeker’s FAQ says an API call is counted for displaying a cached thumbnail, creating one when it is not cached, checking readiness, and using other exposed APIs. Readiness polling can therefore add multiple calls per target, in addition to thumbnail requests. The FAQ also says unused monthly calls do not roll over and mentions pay-as-you-go above five million monthly calls for premium accounts by contacting PagePeeker.
PagePeeker’s published plan pages, accessed in 2026, list free branded use with no call limit and 20-day caching; free unbranded use with 100,000 calls per month, 20-day caching, and a required link back; Basic at $5.99 per month for 100,000 calls with seven-day caching; and Advanced at $39.99 per month for one million calls with five-day caching. Premium pricing is custom and its caching is customizable. These terms can change, so check the current PagePeeker pricing and free thumbnail details before selecting an account.
PagePeeker publishes different typical rendering-time ranges for account categories, rather than one universal latency guarantee. Its free branded page lists 30–60 seconds and free unbranded 10–20 seconds. Treat those as vendor-published plan information, not a promise for each URL. The API documentation lists response headers for paid and unbranded accounts, including X-PP-Error, X-PP-Capture-Time, X-PP-Final-URL, and X-PP-Timestamp; inspect them when diagnosing account-specific responses.
Operational checklist
- Estimate calls as target URLs plus every readiness check and any repeated request.
- Use a bounded retry policy and a finite connection/read timeout.
- Keep the account code in an environment variable or secret store.
- Check content type before saving bytes with an image extension.
- Record each input URL and its outcome so reruns can target only failures.
- Confirm branding, required link-back, image size, cache, and full-page needs against current plan terms.
9. Troubleshooting common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Saved file is JSON or an error page, not an image | The request returned an API error or non-image response. | Check Content-Type, the body, and documented response headers such as X-PP-Error where available. Do not save the response as an image until it is validated. |
| Special-character URL captures the wrong page | The target URL’s query string was not encoded as one parameter. | Pass parameters through requests’ params or Python’s urlencode. For cURL, use --data-urlencode. |
| Thumbnail is a placeholder or not ready | Rendering has not completed yet, or the chosen request behavior returned before completion. | Use the documented readiness endpoint with a bounded polling loop. Account for every poll in your quota. |
| Account code appears to have no effect | The free branded service ignores the code parameter, or the request is using the wrong account entry point. |
Use api.pagepeeker.com for unbranded and paid accounts and verify the account code. The free branded entry point is free.pagepeeker.com. |
| Quota runs out sooner than expected | Calls for cached display and readiness checks also count. | Reduce unnecessary polling, avoid duplicate URL work in your own process, and calculate calls using PagePeeker’s FAQ rules. |
| Long waits or read timeouts | Page rendering may take time; PagePeeker publishes different typical ranges by account type. | Set a read timeout that matches your job deadline, process independently, and retry only transient failures with a cap. |
| Output files overwrite one another | All items use the same filename. | Use a sequence number or stable URL hash, and keep a manifest mapping filenames to input URLs. |
| Expected full-page image but got a thumbnail | The standard thumbnail endpoint is not the full-page feature. | PagePeeker lists full-page screenshots as Premium. Confirm that plan and its adjustable width and maximum-height options meet the requirement. |
10. Reliability and performance for URL lists
PagePeeker’s documentation describes one target per request, so total elapsed time for a purely sequential script grows with the number of URLs and each request’s rendering time. For modest lists, sequential requests are simple and keep request volume controlled. For larger lists, use bounded concurrency, a per-request timeout, and per-URL result tracking. Do not launch an unbounded task for every URL: it can create traffic spikes, complicate retries, and consume quota quickly.
Make retries selective. A connection interruption or transient server error may be worth retrying after a short backoff; a malformed target URL or account error usually needs correction, not repetition. Use an idempotent output naming scheme and write to a temporary file before renaming if interrupted writes would be costly. The API documentation does not provide a Python SDK or a list-batch example; the Python loop and reliability choices here are client-side integration patterns based on its documented HTTP format.
11. Site-owner control
PagePeeker says site owners can disallow its robot in robots.txt with:
User-agent: PagePeeker
Disallow: /
This is PagePeeker’s own documented instruction for its robot. Do not assume the same user-agent rule controls other screenshot providers.
12. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call API returns a screenshot or PDF, and its documented parameters include the names used by other screenshot APIs to ease switching. 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}`);
- 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 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.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.
Other available options include full-page capture with lazy images loaded, CSS selector element capture, dark mode, device presets and custom viewport sizes, retina scale, PDF settings, HTML/CSS capture, custom CSS and JavaScript, click-before-capture, selector hiding, wait conditions, request blocking, headers, cookies, user agent, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed public image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage API, and OpenAPI specification. Sign up free for 1,000 screenshots a month with no card.
FAQ
How do I save a PagePeeker screenshot from Python?
Make a GET request to the V2 thumbnail endpoint, check the HTTP response and image content type, then write response.content to a file. The first Python example shows the complete list workflow.
How do I know when a PagePeeker thumbnail is ready?
Call the documented thumbs_ready.php endpoint and inspect its JSON IsReady field. Use a finite retry count and delay of your choosing.
Does checking thumbnail status count against my API quota?
Yes. PagePeeker’s FAQ says readiness checks count as API calls, including when checking whether a thumbnail is ready.
Can the normal thumbnail endpoint create a full-page screenshot?
PagePeeker lists full-page screenshots as a Premium offering. The standard thumbnail size options describe small preview images.


