How to Generate Website Screenshots in Bulk with Cloudinary
Use Cloudinary’s URL2PNG add-on to capture a list of websites, sign each request, and orchestrate retries safely. There is no documented native bulk endpoint.
Short answer: Cloudinary’s documented way to capture websites is the URL2PNG Website Screenshots add-on. The available documentation does not establish a native multi-URL bulk endpoint, batch size, concurrency guarantee, or throughput limit. For a set of URLs, orchestrate one signed URL2PNG request per page in your own script or job queue, record each result, and control retries and request rate yourself.
This guide shows the documented request shape and a Python application-side loop. Confirm the current URL2PNG options and SDK behavior in your Cloudinary account before running a large job. Cloudinary requires an account and add-on registration. Signed delivery URLs or authenticated eager generation are required by default; unsigned add-on transformations can be enabled in Console security settings, but are not the default.
1. Set up Cloudinary URL2PNG
- Create or use a Cloudinary account and register for the URL2PNG Website Screenshots add-on.
- Choose how to request captures: signed dynamic delivery URLs for on-demand generation, or eager generation through the authenticated API when you want to request generation ahead of embedding.
- Check the current URL2PNG documentation and your account configuration for the exact URL format, supported options, and any applicable account limits.
The documented URL2PNG delivery type is url2png, with the public website URL supplied as the capture source. The documentation names viewport, user_agent, and delay as capture options; its example also uses fullpage=false. Consult the add-on documentation for exact syntax and accepted values. Cloudinary’s CLI documentation also demonstrates constructing and opening a signed URL2PNG URL.
2. Choose signed delivery or eager generation
| Method | How it works | Considerations |
|---|---|---|
| Signed dynamic delivery URL | A request for the signed URL triggers delivery and capture on demand. | Generate the signature using Cloudinary’s supported SDK or CLI flow. Treat signatures and URLs as credentials; avoid exposing signing secrets in client code. |
| Authenticated eager generation | Your server makes an authenticated API request to ask Cloudinary to generate the result ahead of embedding. | Keep API credentials server-side and handle each URL’s outcome in your job system. Verify the current API parameters and SDK call for URL2PNG in the official docs. |
The sources establish these two request styles but do not establish a difference in price or throughput. Choose based on when you need generation and where you can safely perform authentication.
3. Build a bulk workflow by orchestrating individual captures
The following is deliberately an orchestration outline, not a Cloudinary batch API. It reads one URL per line, calls a local function that you connect to a currently supported Cloudinary SDK/API request, and saves one outcome per input. Check the URL2PNG add-on’s current authenticated API flow before filling in that function; the researched documentation does not provide enough verified details to present an executable authenticated bulk request.
from pathlib import Path
import json
import time
URLS = [line.strip() for line in Path("urls.txt").read_text().splitlines()
if line.strip() and not line.lstrip().startswith("#")]
def request_one_capture(url):
"""Implement with the current documented Cloudinary URL2PNG flow.
Use a signed url2png delivery URL or authenticated eager generation.
Return a result record; never log API secrets or signing credentials.
"""
raise NotImplementedError("Connect the current Cloudinary URL2PNG request here")
results = []
for index, url in enumerate(URLS, start=1):
try:
result = request_one_capture(url)
results.append({"url": url, "ok": True, "result": result})
except Exception as exc:
results.append({"url": url, "ok": False,
"error": f"{type(exc).__name__}: {exc}"})
# Set a delay appropriate to your account and workload. No rate is implied.
time.sleep(1)
Path("results.json").write_text(json.dumps(results, indent=2))
Before using the loop, decide how the adapter reports completion and errors. For a delivery URL, a successful HTTP response alone may not be enough to validate that the intended capture was produced; inspect the returned content type and image bytes. For eager generation, use the SDK/API’s documented response and completion semantics. Do not treat the illustrative one-second pause as a Cloudinary limit or recommended rate.
4. Capture options and image transformations
viewport: set the capture viewport using the format documented for the add-on.user_agent: request a capture with the documented user-agent option when the page’s responsive behavior depends on it.delay: allow a page-specific delay where needed for rendering. A fixed delay can waste time on fast pages and still be too short for slow pages.fullpage: the documentation’s example includesfullpage=false. Verify the accepted setting and behavior for your use case.- Cloudinary transformations: after capture, apply supported image transformations if you need a crop or other output adjustment. Cloudinary’s transformation documentation says a derived file is generated on first access and cached on its CDN for later requests.
Do not assume options from a general browser automation tool are accepted by URL2PNG. Check the add-on reference for available parameters and encoding rules, especially when a target URL contains query parameters or special characters.
5. Make the job reliable
- Validate input: reject empty, malformed, or non-public URLs before queuing them. Keep a stable job identifier per input.
- Record outcomes per URL: save status, attempt count, timestamps, and a concise error category so one failure does not erase the rest of the batch.
- Retry selectively: retry transient network or service errors with bounded exponential backoff and jitter. Avoid retrying permanent input or authorization errors without a change.
- Control concurrency yourself: the retrieved sources do not document a URL2PNG concurrency limit or guarantee. Begin conservatively, monitor responses, and verify account-specific guidance before increasing parallel work.
- Make reruns safe: retain the source URL and output identity for each item. Reuse an existing valid output when appropriate rather than blindly repeating work.
- Protect credentials: create signed URLs on a trusted server, or make authenticated eager calls server-side. Do not commit secrets or put them in browser-delivered code.
6. Performance, reliability, and cost
Bulk completion time depends on the target sites, capture settings, request strategy, and the rate your account and implementation can sustain. The sources available for this guide do not verify a batch API, account quota, request limit, concurrency allowance, guaranteed throughput, or current add-on price, so no such figures should be assumed. Check your Cloudinary account and current add-on terms before estimating a job.
Cloudinary documents that transformed derived files are created on first access and cached on its CDN for later requests. That describes delivery of derived files; it does not prove that repeated website captures are free, that a screenshot is refreshed automatically, or that URL2PNG has any particular cache lifetime. Verify freshness and billing behavior for your configuration.
7. Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Unsigned transformation is rejected | Unsigned add-on transformations are blocked by default. | Use a signed URL or authenticated eager generation. Only enable unsigned transformations in Console security settings if that is an intentional account choice. |
| Signature or authorization error | The URL was not signed as required, credentials are incorrect, or URL construction differs from the documented SDK flow. | Generate the URL with the official SDK/CLI pattern, check account credentials and encoding, and keep signing secrets server-side. |
| Capture shows the wrong page or fails to load | The source URL may be malformed, inaccessible to the capture service, or altered by incorrect query encoding. | Test that exact source URL independently, preserve its full query string, and review the generated delivery URL and response. |
| Page looks incomplete | The page may need a different viewport, user agent, or render delay. | Adjust only the documented URL2PNG options and verify the result. A delay is not a guarantee that every asynchronous element has finished. |
| Some items fail in a long run | A per-item transient failure or invalid input can interrupt a naive script. | Catch errors per URL, save partial progress, categorize failures, and retry only transient cases with backoff. |
| Bulk job runs slower than expected | Each item is an individual capture and target pages can have different load times; no documented bulk throughput is available here. | Measure your own workload, inspect slow URLs and capture settings, and tune concurrency gradually against account guidance. |
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-call API can return an image or PDF; use the following request for one URL, then repeat it per URL in your own bulk workflow. See the API documentation for request 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}`);
- Cookie banners are accepted and removed before capture; 60+ known consent platforms, newsletter popups, and chat widgets can be removed, with each step configurable.
- Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is on every plan.
Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.
FAQ
Does Cloudinary have a documented URL2PNG bulk endpoint?
The sources used here do not document one. Treat bulk capture as application-side orchestration of individual requests unless current Cloudinary documentation for your account confirms a batch endpoint.
Can I generate captures before a page requests them?
The documented workflow includes eager generation through the authenticated API. Confirm the current URL2PNG API parameters and SDK implementation before integrating it.
Can I use an unsigned URL2PNG transformation?
Unsigned add-on transformations are blocked by default. Cloudinary documents an account security setting that can allow them; signed requests or authenticated generation are the default-safe paths.
Can I predict the processing time for a large URL list?
Not from the available documentation: it provides no verified bulk throughput or concurrency guarantee. Measure a representative workload in your account before setting completion expectations.


