VisualScraper Screenshot API Rate Limits and Request Quotas
Learn how Screenshot API’s per-second limits and monthly quota work, what 429 and 402 errors mean, and how to monitor usage and retry safely.
Direct answer: Screenshot API has two independent limits: a per-second request limit that controls bursts, and a monthly render quota that limits total usage. A 429 rate_limited means requests are arriving too quickly; wait for the Retry-After duration and reduce request concurrency. A 402 quota_reached means the monthly allowance is spent; check usage, upgrade, or wait for the calendar-month reset in UTC. The vendor’s documentation lists the current limits and error behavior.
Important naming note: This article covers the service documented at screenshot-api.net, which its official materials call Screenshot API. The research available for this article does not confirm that Screenshot API and “VisualScraper” are the same service. Verify the API host and product name in your account before using these examples.
1. Know which limit you reached
Rate limits and monthly quota are separate. You can hit the request-per-second ceiling while plenty of monthly renders remain, or use the monthly allowance over time without sending requests too quickly. The vendor documents the following plan limits; these are vendor-published values, not independent load-test results. Plan details can change, so confirm them on the official docs and product page before purchasing.
| Plan | Renders per month | Requests per second | Snapshot sets | Published monthly price |
|---|---|---|---|---|
| Free | 100 | 1 | 1 | $0 |
| Starter | 2,000 | 5 | 5 | $9 |
| Pro | 10,000 | 10 | 20 | $29 |
| Team | 25,000 | 25 | 50 | $49 |
| Business | 100,000 | 50 | 200 | $149 |
The plan limits and snapshot-set counts are listed in the official documentation; prices are listed on the official plan table. The terms state that limits are per account, unused renders do not roll over, and no overage is charged after quota exhaustion.
2. Handle 429 responses with backoff
A 429 response with error rate_limited indicates that the account sent more requests per second than its plan allows. Respect Retry-After, which provides the wait before retrying. If that header is absent or unusable, use exponential backoff with jitter. Also lower concurrency or pace work through a queue: retrying a whole burst immediately can cause another 429.
The following example uses the documented screenshot endpoint and bearer-key authentication. Set SCREENSHOT_API_KEY in the environment before running it. It saves the image only after a successful response and reports quota headers when available.
cURL
export SCREENSHOT_API_KEY="YOUR_API_KEY"
curl --fail-with-body --retry 0 \
-H "Authorization: Bearer $SCREENSHOT_API_KEY" \
-D response-headers.txt \
-G "https://screenshot-api.net/v1/screenshot" \
--data-urlencode "url=https://example.com" \
-o screenshot.png
cURL does not automatically implement this service’s 429 policy in the example above. Inspect the response status and Retry-After in response-headers.txt; use an application-level queue for automated retries. --fail-with-body makes HTTP errors visible while retaining the response body for diagnosis.
Python: retry 429 using Retry-After
import os
import random
import time
from email.utils import parsedate_to_datetime
from datetime import datetime, timezone
import requests
API_KEY = os.environ["SCREENSHOT_API_KEY"]
ENDPOINT = "https://screenshot-api.net/v1/screenshot"
params = {"url": "https://example.com", "format": "png"}
def retry_delay(value, attempt):
if value:
try:
return max(0.0, float(value))
except ValueError:
try:
retry_at = parsedate_to_datetime(value)
if retry_at.tzinfo is None:
retry_at = retry_at.replace(tzinfo=timezone.utc)
return max(0.0, (retry_at - datetime.now(timezone.utc)).total_seconds())
except (TypeError, ValueError, OverflowError):
pass
# Fallback only when Retry-After is missing or malformed.
return min(30.0, 0.5 * (2 ** attempt)) + random.uniform(0.0, 0.25)
for attempt in range(6):
response = requests.get(
ENDPOINT,
params=params,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=40,
)
if response.status_code == 429 and attempt < 5:
delay = retry_delay(response.headers.get("Retry-After"), attempt)
time.sleep(delay)
continue
response.raise_for_status()
with open("screenshot.png", "wb") as image_file:
image_file.write(response.content)
print("remaining:", response.headers.get("X-Quota-Remaining"))
break
else:
raise RuntimeError("Rate limit persisted after retries")
A date-form Retry-After value is also handled. Network errors and timeouts are deliberately not retried in this small example: add bounded retries for those only when your operation is safe to repeat and your queue prevents a retry storm.
Node.js: retry 429 using Retry-After
const apiKey = process.env.SCREENSHOT_API_KEY;
if (!apiKey) throw new Error("Set SCREENSHOT_API_KEY first");
const endpoint = new URL("https://screenshot-api.net/v1/screenshot");
endpoint.searchParams.set("url", "https://example.com");
endpoint.searchParams.set("format", "png");
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function retryDelay(value, attempt) {
if (value) {
const seconds = Number(value);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
const dateMs = Date.parse(value);
if (Number.isFinite(dateMs)) return Math.max(0, dateMs - Date.now());
}
return Math.min(30000, 500 * (2 ** attempt)) + Math.random() * 250;
}
let response;
for (let attempt = 0; attempt < 6; attempt++) {
response = await fetch(endpoint, {
headers: { Authorization: `Bearer ${apiKey}` },
signal: AbortSignal.timeout(40000),
});
if (response.status !== 429 || attempt === 5) break;
await sleep(retryDelay(response.headers.get("retry-after"), attempt));
}
if (!response.ok) {
const details = await response.text();
throw new Error(`Screenshot API ${response.status}: ${details}`);
}
await Bun.write("screenshot.png", new Uint8Array(await response.arrayBuffer()));
console.log("remaining:", response.headers.get("x-quota-remaining"));
This file-writing line uses Bun. With Node.js alone, replace it with import { writeFile } from 'node:fs/promises'; await writeFile('screenshot.png', Buffer.from(await response.arrayBuffer()));. Keep credentials on a trusted server; do not expose them in browser code.
3. Monitor monthly usage and reset timing
Image responses include X-Quota-Limit and X-Quota-Remaining. Record these values alongside status codes so a caller can distinguish a rate spike from a shrinking monthly balance. The GET /v1/account endpoint provides plan and current usage information; consult the docs for its authentication requirements and response shape. The quota resets at the start of each calendar month in UTC, and unused renders do not roll over.
Set an internal alert threshold before exhaustion, for example when remaining quota drops below the amount needed for the next scheduled run. Use the account endpoint for periodic checks and the response header for immediate per-request accounting. Do not treat a locally cached balance as authoritative when several workers share one account.
4. Estimate snapshot and scheduled-job consumption
Estimate monthly demand before choosing a plan or setting a schedule:
monthly renders = pages per run × widths per page × runs per month
For example, 10 pages at 2 widths each, captured hourly for a 30-day month, require 10 × 2 × 24 × 30 = 14,400 renders. This is the vendor’s documented example calculation, not an independent usage study. Compare the estimate with both monthly quota and peak requests per second. If the schedule starts all captures at once, a workload that fits the monthly quota can still exceed the burst limit.
- Count every page and viewport/width captured during one run.
- Multiply by runs per day and days in the billing month.
- Spread large runs over time so the instantaneous request rate stays within plan limits.
- Leave room for manual captures, retries, and changes in monitored pages.
- Compare the number of configured snapshot sets with the plan’s snapshot-set limit.
5. Handle quota errors and other failures
| Status and error | Meaning | Response |
|---|---|---|
429 rate_limited |
Too many requests per second. | Honor Retry-After; reduce concurrency or pace requests. |
402 quota_reached |
Monthly render allowance is exhausted. | Check account usage; upgrade or wait for the UTC month reset. Immediate retries do not add quota. |
502 render_failed |
The page did not load or render. | Check the target URL and page availability. The docs say failed renders are refunded. |
503 busy |
Renderers are saturated. | Pause briefly, then retry with backoff. The docs say the reserved unit is released. |
Branch on the stable JSON error value and HTTP status, rather than parsing human-readable message text. Do not retry a 402 in a tight loop: only an upgrade or the monthly reset changes the allowance. For 502, verify the target page separately; for 503, avoid synchronizing every worker’s retry at the same instant.
6. Performance, reliability, and cost considerations
- Throughput: Use a bounded worker pool and a queue. Keep the number of concurrent requests within your plan’s burst allowance, and smooth scheduled work across the run window.
- Retries: Honor
Retry-Afterfor 429. For 503, use a short pause with exponential backoff and jitter. Cap attempts and send persistent failures to a review queue. - Accounting: A successful render consumes quota. Per the docs, failed 502/503 renders release the reserved unit; track response status and quota headers to reconcile usage.
- Timeouts: A client timeout does not prove the server did not finish. Avoid launching immediate duplicate captures after a timeout; first apply a deliberate retry policy.
- Capacity planning: Monthly render count and requests per second are distinct dimensions. A monthly plan that fits the volume may still need scheduling changes to fit peak rate.
- Cost: The cited vendor-published monthly prices range from $9 Starter to $149 Business, with a free tier. Treat these as figures found in official materials on October 3, 2026, and verify current pricing before committing.
7. Troubleshooting checklist
Every request returns 429
Confirm the account’s plan and pace below its requests-per-second limit. Add a shared queue or rate limiter across workers; independent per-process limits can collectively exceed the account ceiling. Honor the response’s Retry-After.
Requests return 402 even though the rate is low
This is monthly quota exhaustion, not a burst limit. Check X-Quota-Remaining or GET /v1/account. Reduce captures, upgrade, or wait until the start of the next calendar month in UTC.
Usage appears higher than expected
Recount pages, widths, and schedule frequency. For example, capturing two widths is two renders per page per run. Also check whether multiple systems or teammates share the same account.
A failed capture seems to have consumed quota
Check the final status and response headers, then refresh account usage. The documentation says failed 502 and 503 renders release the reserved unit. A client-side timeout alone may not reveal the final server result.
Retries make the rate limit worse
Do not retry immediately or let every worker retry at once. Honor Retry-After; add jitter to fallback delays, cap concurrency, and use one shared queue for an account.
The account endpoint disagrees with a recent response header
Requests may have completed between the two observations, especially when workers share credentials. Treat the account endpoint as a usage snapshot and correlate it with request timestamps and response headers.
8. Or skip the browser setup
For a screenshot workflow where you want cookie banners, popups, and chat widgets removed, try ScreenshotNeo. It accepts consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify outcomes with X-Page-Verdict and X-Billed. Its MCP server gives Claude, Cursor, and other MCP clients screenshot tools. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000, and every feature is on every plan.
cURL example (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
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo returned ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
9. FAQ
Does a 429 mean the monthly quota is gone?
No. The documented 429 is the per-second rate limit. Monthly exhaustion is reported as HTTP 402 with quota_reached.
When does the monthly allowance reset?
At the beginning of each calendar month in UTC. Unused renders do not carry over.
Do failed renders count?
The documentation says failed 502 and 503 renders release the reserved unit. Check the actual response and account usage when reconciling a client timeout.
Can I use the published limits as a guaranteed throughput?
No. They are plan limits published by the vendor, not an independent performance guarantee or benchmark. Keep requests paced and verify current plan details with the service.
Sources
- Screenshot API documentation: plan quotas, rate limits, response headers, endpoint behavior, and errors.
- Screenshot API terms: published plan prices, quota reset and rollover terms, and billing behavior.


