Do All Screenshot API Requests Count Against Your Quota?
No. Screenshot API quota rules vary: successful renders usually count, while failures, cache hits, and retries may be treated differently.

No—quota accounting is provider-specific. A successful screenshot normally consumes one render or screenshot unit, but failed captures may be refunded or excluded, cache hits may be free, and a retry after a client timeout can create a second billable capture. Read the provider’s quota, cache, failure, and retry documentation before assuming that every HTTP request costs a credit.
Quick answer by request outcome
| Request outcome | Common quota behavior | What to verify |
|---|---|---|
| Successful image capture | Usually counts as one screenshot or render. | Whether the unit is a request, a successful render, or a returned image. |
| Renderer or target failure | Often refunded or excluded from successful-capture allowance. | Which failures qualify and whether the request still consumes rate-limit capacity. |
| HTTP 4xx or invalid parameters | Provider-specific; commonly not a successful capture. | Billing and error documentation for validation failures. |
| Cache hit | May not count when the provider documents cache exemptions. | Cache key rules, TTL, and a response header such as x-cache: HIT. |
| Client timeout followed by retry | Can count twice if the first capture completed before your timeout. | Idempotency, job status, request IDs, and retry guidance. |
| Rate-limited request | Rate limits are separate from monthly quota. | Requests-per-second windows and whether failed requests still count toward them. |
For example, Screenshot-api.net says captures made count against the plan, while its renderer or target failures return the reserved unit. ScreenshotEngine excludes failed captures from its successful-capture allowance but still subjects those requests to rate limiting. SnapURL documents that identical requests with x-cache: HIT do not count, and ScreenURL says cached responses do not count. These policies are examples, not a universal industry rule.
Four quota models used by screenshot APIs
1. Request-based accounting
Under a request-based model, each qualifying endpoint call consumes a unit even when the result is not an image. This is simple to explain but can make invalid URLs, renderer errors, or short timeouts expensive. SnapURL describes every /v1/screenshot call as one render, subject to its cache exception. Before integrating, check whether PDF, extraction, and metadata endpoints share the same pool.

2. Successful-render accounting
Some providers count only when a screenshot is successfully produced. ScreenURL describes each successful API call that returns an image as one screenshot. ScreenshotEngine similarly distinguishes successful-capture allowance from requests that failed. Ask whether a blank page, navigation timeout, bot challenge, or browser crash is classified as a failed capture.
3. Reserved-unit accounting with refunds
A provider may reserve a unit when work starts, then return it when the renderer or target fails. This protects capacity while the job runs and gives you a predictable allowance after a documented failure. A refunded unit is not the same as a free request: the call may still count against concurrency or rate limits.
4. Cache-aware accounting
With cache-aware billing, an identical request can be served without another render. “Identical” usually includes the URL and every rendering input that affects pixels, such as viewport, device scale, color scheme, custom CSS, headers, cookies, and JavaScript. A changed query parameter, timestamp, or cache-busting header can force a new render. Confirm the cache key and TTL instead of assuming that two visually similar requests are free.
Do failed screenshot requests use credits?
Sometimes. Providers use different definitions for a failure:
- Validation failure: The API rejects a missing URL or malformed option before launching a browser. This is often outside successful-render usage, but verify the provider’s terms.
- Navigation failure: DNS errors, connection refusals, TLS problems, or a target that never responds.
- Browser failure: The renderer crashes, runs out of resources, or cannot create a page.
- Target protection: A bot check or CAPTCHA prevents a usable page from loading.
- Empty result: The page technically loads but produces a blank or unusable capture.
Screenshot-api.net documents refunds for renderer and target failures. ScreenshotEngine and CaptureAPI document exclusions for failed captures or server-side failures. The exact boundary matters: a target returning an HTTP 200 page containing an application error may still be considered a successful render because an image was produced.
Do cached screenshot requests count?
Do not assume either answer. SnapURL says identical requests served with x-cache: HIT do not count against quota. ScreenURL also says cached responses do not count. Other services may bill cache hits, disable caching on some plans, or use a short default TTL.
To use caching safely:
- Keep rendering inputs stable for assets that can be reused.
- Record the provider’s cache indicator and remaining-quota header.
- Choose a TTL that matches how often the page changes.
- Do not add a random query parameter unless you need a fresh render.
- Cache your own completed image or PDF when your application can reuse it.
For visual regression tests, bypassing the cache is usually necessary. For thumbnails and social cards, a stable cache key can reduce rendering work if the provider offers a documented exemption.
Can a timeout charge you twice?
Yes. A client timeout only tells your application that it stopped waiting. It does not prove that the browser stopped. ScreenshotEngine warns that a capture may succeed just before the client times out; retrying then creates another successful request that counts.

Use a retry policy that separates safe failures from uncertain outcomes:
- Retry DNS, connection, and explicit server errors when the provider says no render started.
- After a read timeout, first query a job-status endpoint when available.
- Use an idempotency key or deterministic job identifier if the API supports one.
- Apply exponential backoff with jitter instead of immediate loops.
- Cap attempts and log the request ID, URL, parameters, and response headers.
If the API has no status or idempotency feature, treat a timeout as “outcome unknown.” Repeated retries can improve availability while increasing duplicate renders and cost.
Measure quota with response metadata
Build accounting into your client rather than estimating from request counts. When documented, inspect headers for remaining quota, reset time, cache status, billing status, and page verdict. Store them with your job record. A simple record should include:
- Request ID and timestamp
- Canonical URL and rendering options
- HTTP status
- Cache result
- Quota remaining and reset information
- Whether an image or PDF was returned
- Retry count and final outcome
Rate limits and monthly quota are different controls. Screenshot-api.net separates requests per second from monthly renders. ScreenshotEngine says failed requests remain subject to rate limiting even when they do not consume the successful-capture allowance. A burst can therefore receive 429 responses while your monthly balance remains high.
Runnable request examples
The following cURL example shows the shape of a screenshot request. Adapt the endpoint and authentication to your provider, and inspect the response headers before deciding whether a unit was consumed.
curl -i -G "https://api.example.test/v1/screenshot" \
--data-urlencode "url=https://example.com" \
-o shot.png
Python example with explicit timeout and header logging:
import requests
params = {"url": "https://example.com"}
r = requests.get("https://api.example.test/v1/screenshot", params=params, timeout=90)
print(r.status_code)
print("cache:", r.headers.get("x-cache"))
print("remaining:", r.headers.get("x-quota-remaining"))
r.raise_for_status()
open("shot.png", "wb").write(r.content)
Node.js example that avoids retrying an uncertain timeout blindly:
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 90000);
try {
const q = new URLSearchParams({ url: 'https://example.com' });
const res = await fetch(`https://api.example.test/v1/screenshot?${q}`, {
signal: controller.signal
});
console.log('status', res.status);
console.log('cache', res.headers.get('x-cache'));
console.log('remaining', res.headers.get('x-quota-remaining'));
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await Bun.write('shot.png', bytes);
} finally {
clearTimeout(timer);
}
Reliability and cost checklist
- Set a timeout longer than the provider’s normal browser wait, but not so long that workers pile up.
- Limit concurrency to the documented rate and browser capacity.
- Use a queue for bulk work and persist each job before sending it.
- Make retries conditional on the error class and outcome certainty.
- Prefer provider caching for repeated, unchanged captures when cache hits are free.
- Alert on quota remaining, reset time, and sudden increases in failed renders.
- Budget successful renders separately from rate-limit headroom.
Cost estimates should use successful renders, cache policy, PDF or extraction rules, and retry behavior. A system that sends 10,000 HTTP requests may consume fewer units when many are cache hits, or more than 10,000 successful renders if uncertain timeouts are retried after the original work completed.
Common errors and fixes
| Error | Likely cause | Fix |
|---|---|---|
| 429 Too Many Requests | Rate window exceeded, regardless of monthly balance. | Back off, reduce concurrency, and honor retry headers. |
| Quota exhausted | Successful renders consumed the allowance. | Check reset time, cache repeated work, or change plan. |
| Timeout after a long wait | Slow page, blocked resource, or client timeout shorter than browser work. | Check job status before retrying; capture a request ID. |
| Unexpected second charge | A retry followed a capture that completed after the client stopped waiting. | Use idempotency or status polling and cap retries. |
| Cache reported as MISS every time | Changing query parameters, headers, cookies, viewport, or TTL. | Canonicalize inputs and inspect the documented cache key. |
| Image returned but page is unusable | Bot check, consent overlay, blank app shell, or target-side error page. | Classify the result using provider verdict metadata and adjust capture options. |
Or skip the browser setup
ScreenshotNeo makes quota behavior explicit. Its API returns a clean PNG, JPEG, WebP, or PDF from one GET request. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Each response includes X-Page-Verdict and X-Billed headers so your application can see what happened.
Before capture, ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Each cleanup step can be turned off. It also supports full-page captures with lazy images loaded, CSS-selector element shots, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click actions, waits, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, PDF options, HTML/CSS-to-image, and a usage API.
Use the documented parameter names and see the ScreenshotNeo API documentation for the complete option list.
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)
print(r.headers.get("X-Page-Verdict"), r.headers.get("X-Billed"))
r.raise_for_status()
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}`);
console.log(res.headers.get('X-Page-Verdict'), res.headers.get('X-Billed'));
ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does a 4xx response always cost nothing?
No universal rule. Check whether the provider bills requests, successful renders, or only returned images, and whether validation failures are excluded.
Are PDFs counted separately?
Sometimes. Compare the quota rules for screenshot, PDF, and extraction endpoints before combining workloads.
Can I infer billing from HTTP status?
No. A successful HTTP response can be a cached result or a rendered error page, while a timeout can hide a completed capture. Use documented headers, job status, and usage data.
What should I record for an audit?
Store the request ID, canonical inputs, status, cache indicator, verdict, billing indicator, remaining quota, and retry history.
Should I disable retries?
No. Make them outcome-aware: retry known pre-render failures, but check status or idempotency after an uncertain timeout.