How to Call the URL2PNG API from Python
Build and sign URL2PNG v6 requests in Python, choose capture options, handle caching, and troubleshoot common signing errors.
To call the URL2PNG v6 API from Python, URL-encode all request parameters, compute an MD5 hex digest of that exact query string followed by your API secret, then request the PNG endpoint using your API key and digest in the path. The query string used to sign the request must match the one sent byte for byte.
You need a URL2PNG API key and secret from your account. The key identifies your account; the secret signs each request. Keep the secret in an environment variable or other server-side configuration. The example below uses only Python’s standard library to construct the signed URL, then urllib to download the image.
1. Build and run a signed request
import hashlib
import os
from urllib.parse import urlencode
from urllib.request import urlopen
API_KEY = os.environ["URL2PNG_API_KEY"]
API_SECRET = os.environ["URL2PNG_API_SECRET"]
# Include every parameter that you want URL2PNG to receive before signing.
params = {
"url": "https://example.com/",
"fullpage": "false",
"thumbnail_max_width": "1200",
"viewport": "1280x1024",
}
# urlencode returns the exact query string that will be signed and sent.
query_string = urlencode(params)
token = hashlib.md5((query_string + API_SECRET).encode("utf-8")).hexdigest()
request_url = (
f"https://api.url2png.com/v6/{API_KEY}/{token}/png/?{query_string}"
)
with urlopen(request_url, timeout=90) as response:
image_bytes = response.read()
with open("screenshot.png", "wb") as image_file:
image_file.write(image_bytes)
print("Saved screenshot.png")
Set credentials before running the script. For example, in a POSIX shell: export URL2PNG_API_KEY='your-key' and export URL2PNG_API_SECRET='your-secret'. Do not commit real credentials to source control or log the signed URL: it contains the account key and request token.
The official URL2PNG quickstart documents the v6 signing format and Python URL-generation example. This download version adds a response read and file write so the result is saved locally. See the URL2PNG service and its plans and FAQ for current account and plan information.
2. Understand the signature
The token is MD5(query_string + secret), represented as lowercase hexadecimal. It is not a hash of the target page URL alone, and it is not a hash of JSON. The key and token go in the URL path; the encoded parameters go after the question mark.
- Choose the parameters, including the target
url. - Serialize the complete parameter set once with
urllib.parse.urlencode. - Append the secret to that serialized string, encode as UTF-8, and calculate the MD5 hex digest.
- Use that same serialized query string in the request URL.
Parameter order and escaping matter because the signature covers the serialized string. Do not sign one representation and then rebuild the query with a different order, escaping policy, or omitted values. The quickstart sample filters falsey values; that can accidentally omit an explicit setting such as fullpage=false. The example above deliberately serializes the string value false.
3. Choose capture options
| Parameter | What it controls | Guidance |
|---|---|---|
url |
Page to render. | Supply the complete URL, including scheme. Encode it as a parameter with urlencode; nested query strings are then escaped correctly. |
fullpage |
Whether to attempt the entire document canvas. | The documented default is false, meaning viewport capture. Send an explicit value when behavior should not depend on defaults. |
viewport |
Browser viewport dimensions. | The advanced-options documentation gives 1480x1037 as the API default. The quickstart’s sample helper uses 1280 by 1024 as its own function default; these are different defaults. |
thumbnail_max_width |
Constrains the rendered screenshot width. | Use it when the output must fit a target width. Confirm the resulting dimensions for your use case. |
unique |
Varies request identity to request a fresh screenshot rather than reusing a cached result. | Use a timestamp or another changing value when freshness matters. A stable value can preserve cache reuse. |
custom_css_url |
Loads custom CSS from a URL. | Use a reachable stylesheet URL and ensure it is appropriate for the page being rendered. |
say_cheese |
Waits for a named element to be available before capture. | Use when a known page element indicates the relevant content is ready. |
delay |
Adds a fixed wait after document readiness and asset loading. | Use a short delay for late client-side rendering; avoid a large delay as a substitute for a readiness condition. |
accept_languages, user_agent |
Customizes language and user-agent headers. | Set these only when the rendered page should reflect a particular locale or client identity. |
ttl |
Sets screenshot time to live. | The documented default is 2,592,000 seconds (30 days). Adjust it to fit how often the page changes. |
URL2PNG’s available options and defaults can change; consult the official quickstart and options documentation when adopting parameters beyond this list.
4. Generate a URL without downloading
If another system will fetch the image, you can create and return the signed URL instead of downloading it in Python. This follows the documented quickstart pattern:
import hashlib
import os
from urllib.parse import urlencode
api_key = os.environ["URL2PNG_API_KEY"]
secret = os.environ["URL2PNG_API_SECRET"]
params = {
"url": "https://example.com/",
"fullpage": "true",
"thumbnail_max_width": "1200",
"unique": "20261004T120000Z",
"viewport": "1280x1024",
}
query_string = urlencode(params)
token = hashlib.md5((query_string + secret).encode("utf-8")).hexdigest()
signed_url = f"https://api.url2png.com/v6/{api_key}/{token}/png/?{query_string}"
print(signed_url)
Use the timestamp-like unique value only when you want a new request identity. If you want cache reuse, omit it or keep it stable according to your cache strategy.
5. cURL, Python requests, and Node.js examples
URL2PNG v6 signing is based on its exact URL-encoded query string. These examples all serialize once, sign that string, and then send it unchanged.
cURL
Generate the signed URL with Python as above, then pass it to cURL. Avoid putting credentials in shell history in shared environments.
curl --fail --location --output screenshot.png "$SIGNED_URL"
Python with requests
import hashlib
import os
from urllib.parse import urlencode
import requests
api_key = os.environ["URL2PNG_API_KEY"]
secret = os.environ["URL2PNG_API_SECRET"]
params = {"url": "https://example.com/", "fullpage": "false", "viewport": "1280x1024"}
query_string = urlencode(params)
token = hashlib.md5((query_string + secret).encode("utf-8")).hexdigest()
request_url = f"https://api.url2png.com/v6/{api_key}/{token}/png/?{query_string}"
response = requests.get(request_url, timeout=90)
response.raise_for_status()
with open("screenshot.png", "wb") as output:
output.write(response.content)
Node.js
import { createHash } from "node:crypto";
const apiKey = process.env.URL2PNG_API_KEY;
const secret = process.env.URL2PNG_API_SECRET;
if (!apiKey || !secret) throw new Error("Set URL2PNG_API_KEY and URL2PNG_API_SECRET");
const params = new URLSearchParams({
url: "https://example.com/",
fullpage: "false",
viewport: "1280x1024",
});
const queryString = params.toString();
const token = createHash("md5").update(queryString + secret, "utf8").digest("hex");
const requestUrl = `https://api.url2png.com/v6/${apiKey}/${token}/png/?${queryString}`;
const response = await fetch(requestUrl, { signal: AbortSignal.timeout(90000) });
if (!response.ok) throw new Error(`URL2PNG returned HTTP ${response.status}`);
const bytes = Buffer.from(await response.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) => writeFile("screenshot.png", bytes));
In all languages, verify that the library’s query serializer produces exactly the string you hash and send. If you change serializers, parameter ordering, or encoding, regenerate the signature from the final serialized form.
6. Cache, usage, and cost
URL2PNG’s plans FAQ says a freshly generated screenshot counts as one render, while loading a cached screenshot does not count as a new render. Screenshots are cached for 30 days by default, and the TTL can be adjusted. Use a stable request for cache reuse; use unique or a suitable TTL policy when freshness is more important.
| Plan listed by URL2PNG | Monthly fresh screenshots | Listed monthly price | Listed overage |
|---|---|---|---|
| Bootstrapped | 5,000 | $29 | $0.006 per additional screenshot |
| Traction | 20,000 | $99 | $0.005 per additional screenshot |
| Killinit | 50,000 | $199 | $0.004 per additional screenshot |
| Enterprise | Listed as unlimited | Contact-based | Contact URL2PNG |
These are vendor-listed plan facts retrieved on 2026-10-03 and may change; verify the current URL2PNG plans page before budgeting. The same page lists worker capacity of 10, 15, and 35 for the first three plans respectively, email support for the first two, priority support for Killinit, and premium support for Enterprise. It also lists an SSL endpoint as a feature. For production, use HTTPS where your plan supports it and budget around fresh renders rather than every retrieval.
7. Reliability and performance practices
- Set a timeout. Rendering can take longer than a typical small API response; choose a timeout that fits your job’s latency budget and catch timeout exceptions.
- Bound concurrency. Plan worker capacity is finite on the listed plans. Queue work or limit parallel requests to avoid overwhelming your account and downstream service.
- Retry selectively. Retry transient connection failures and server errors with backoff. Do not blindly retry every client error; fix invalid parameters or signatures first.
- Use cache intentionally. Repeated identical requests may be served from cache and do not count as fresh renders per the vendor FAQ. Change
uniqueonly when you need a fresh capture. - Keep payloads manageable. Full-page captures and large widths can produce larger files and longer downloads. Choose viewport and width to match the needed output.
- Validate the result. Check HTTP status and, in production, verify that the response is an image before persisting it. Save to a temporary file and rename only after a complete successful download.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Authentication or invalid token response | The string signed differs from the query sent, the secret is wrong, or a parameter was added or reordered after signing. | Build the full parameter dictionary first, serialize exactly once, hash that exact string plus the secret, and append the same string to the request URL. |
| Request works until a parameter contains spaces or punctuation | Manual escaping or double encoding changed the bytes used for signing. | Pass raw values to urlencode; do not pre-escape the target URL. Sign and send its one output string. |
fullpage=false seems ignored |
A helper may have filtered out falsey values, leaving the API default in effect. | Represent boolean values as explicit strings such as "false" and preserve them during serialization. |
| Unexpected dimensions | The API default viewport (1480×1037) may differ from an example helper’s default (1280×1024), or width constraints affect output. | Send viewport and thumbnail_max_width explicitly and inspect the downloaded dimensions. |
| Screenshot is stale | A cached response is being reused. | Use a changing unique parameter for a fresh request, and review ttl for your freshness needs. |
| Screenshot misses dynamic content | The page content appeared after the capture readiness point. | Use say_cheese for an element-based wait or a modest delay for a known late render. |
| Script times out or downloads an incomplete file | The chosen timeout is too short, or the transfer failed midway. | Increase the timeout within your job budget, retry transient failures with backoff, and write to a temporary file before promoting it. |
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A single GET request can return a PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
For a Python call, use the documented request pattern below. See the ScreenshotNeo API documentation for the available parameters.
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)
Equivalent cURL and Node.js calls:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo includes full-page capture, element selection, device presets, custom CSS and JavaScript, waits, headers and cookies, caching with a chosen TTL, async jobs, bulk capture, and more. One thousand screenshots a month are free with no card; paid plans start at $5 for 3,000. See ScreenshotNeo, then sign up for 1,000 free screenshots a month with no card.
10. FAQ
Does the Python example require a third-party package?
The main download example uses only Python’s standard library. The alternative requests example requires the requests package.
Can I use this from a browser or mobile app?
The signing secret must stay private. Generate signed requests on a server you control rather than embedding the secret in public client code.
Why use MD5 here?
MD5 is the signing scheme specified by URL2PNG’s v6 request format. Follow that protocol for compatibility and keep the secret server-side.
How do I capture a page that requires authentication?
The options in the supplied URL2PNG research do not establish a cookie or authorization parameter for this flow. Check the current official options documentation for supported authenticated-page capture before relying on it.


