Screenshot API returns 429: how to fix rate limit errors
Find out whether your screenshot API or the target site returned 429, then use the right wait, retry, and pacing strategy.
A 429 means a request limit was applied somewhere in the screenshot flow. First determine whether your screenshot provider rejected your API request or the destination website returned 429 to the provider. Then follow the provider’s documented Retry-After or reset value, reduce parallel requests, and queue the rest. Do not retry immediately in a tight loop. Check monthly quota separately: providers use different response codes, error fields, limits, and reset rules.
This guide explains how to identify the source, handle retries safely, and prevent the same failure from recurring. No particular provider, plan, or account is assumed, so there is no universal reset interval or rate limit to apply.
1. Identify which server returned 429
A screenshot request involves at least two systems: your client calls the screenshot API, and the screenshot service loads the destination page. Either side can impose a limit. A 429 in your HTTP response might be a provider-side limit, or the provider may report that the target host responded with 429. Those cases need different remedies.
| Evidence | Likely source | Next step |
|---|---|---|
| Provider error code or message names a rate, concurrency, or request limit | Screenshot provider | Stop adding work, check the provider’s limit/reset documentation and usage data, then resume at a controlled pace. |
| Error details say the host returned 429, or include the target’s returned status code | Destination website | Reduce requests to that site and wait before retrying. Check any applicable retry signal. |
| Response indicates quota or monthly renders exhausted | Account quota | Check account usage and the provider’s quota reset or plan options. A larger request-rate allowance may not solve an exhausted monthly quota. |
| Response is unclear or only contains a generic 429 | Unknown | Capture the complete status, body, headers, timestamp, and request ID, then consult the provider’s current documentation or support. |
For example, ScreenshotOne documents concurrency_limit_reached for its own concurrency limit and host_returned_error when a target returns an error; its host error can include returned_status_code=429. Its error reference lists screenshots_limit_reached separately for quota exhaustion. These are ScreenshotOne-specific names, not universal response fields. See its error reference and troubleshooting guide.
2. Inspect the response before retrying
Record the response details from one failed request before changing your retry logic. Preserve enough information to distinguish provider-side throttling from a target-site response.
- HTTP status and response body, including structured fields such as an error code or message.
- Response headers, especially
Retry-Afterand any provider-documented remaining or reset fields. - Provider request ID, if present, plus the timestamp and the requested target host.
- Which account and application made the request, and how many workers were active at the time.
Redact API keys, authorization values, cookies, and sensitive query parameters before sharing logs. Header names and formats differ between providers; do not assume a missing header means the limit has no reset.
3. Retry with backoff and honor reset signals
If the response includes a valid Retry-After, wait that long before retrying. It may be represented as a number of seconds or as a date, depending on the API. If the provider documents a different reset field or a usage endpoint, use that provider’s documented format. When no usable signal is present, use exponential backoff with jitter and a maximum attempt count rather than a rapid retry loop.
For an illustrative fallback, wait a few seconds, then increase the delay for each subsequent retry and add a small random offset. This is an application policy, not a universal screenshot API interval. Reset the attempt counter only after the request succeeds or after you decide to defer the job for later.
cURL: inspect the response and headers
curl -sS -D response-headers.txt -o response-body.txt \
-w 'HTTP %{http_code}\n' \
-G 'https://YOUR-SCREENSHOT-API-ENDPOINT' \
--data-urlencode 'url=https://example.com'
Replace the endpoint and authentication parameters with those required by your provider. The output files help you inspect headers and body independently. Avoid putting real secrets in shell history or logs.
Python: make one request and inspect the result
import requests
response = requests.get(
"https://YOUR-SCREENSHOT-API-ENDPOINT",
params={"url": "https://example.com", "access_key": "YOUR_API_KEY"},
timeout=90,
)
print("status:", response.status_code)
print("retry-after:", response.headers.get("Retry-After"))
print("request-id:", response.headers.get("X-Request-Id"))
print("body:", response.text[:2000])
if response.status_code == 429:
# Inspect the provider's documented error and reset fields before retrying.
raise RuntimeError("Rate limited; wait according to the provider response")
response.raise_for_status()
with open("shot.png", "wb") as output:
output.write(response.content)
The request ID header shown is only an example lookup; use the actual name documented by your provider. For a binary screenshot response, check the provider’s documented content type and error format so you do not mistake an error body for an image.
Node.js: inspect status and response headers
const query = new URLSearchParams({
url: 'https://example.com',
access_key: 'YOUR_API_KEY',
});
const response = await fetch(
`https://YOUR-SCREENSHOT-API-ENDPOINT?${query}`
);
console.log('status:', response.status);
console.log('retry-after:', response.headers.get('retry-after'));
console.log('request-id:', response.headers.get('x-request-id'));
if (response.status === 429) {
const body = await response.text();
console.error('rate limit response:', body.slice(0, 2000));
throw new Error('Rate limited; wait according to the provider response');
}
if (!response.ok) {
throw new Error(`Screenshot request failed: HTTP ${response.status}`);
}
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.png', image));
This example uses Node.js with a runtime that provides global fetch. Add the provider’s required authentication and use its documented error-body format.
4. Apply the fix that matches the limit
Provider request-rate or concurrency limit
- Pause or slow workers that are submitting new screenshot jobs.
- Put pending requests in a queue. Set a concurrency cap that stays within the provider’s documented allowance.
- Use the documented reset signal or usage endpoint to decide when to resume. Share rate-limit state across workers when they use the same account; otherwise each worker can independently exceed the shared limit.
- Check whether another service, scheduled task, or teammate is using the same account.
- Resume gradually and monitor the provider’s responses rather than restarting the full burst at once.
ScreenshotOne’s guide recommends checking its usage endpoint and retrying after its documented concurrency.reset. It also says a proxy does not change its own concurrency request limit. See the provider troubleshooting guidance.
Target website returned 429
- Reduce the frequency of captures to that destination host and wait before retrying.
- Honor a target or provider retry signal when the response makes one available.
- Avoid repeatedly requesting the same page from multiple workers; coordinate requests by destination host where practical.
- Confirm that automated access is permitted by the site and your use case. Do not treat a different route or proxy as a blanket way to bypass a site’s controls.
Monthly quota exhausted
Check the account dashboard or usage endpoint for the provider’s quota state and reset timing. Monthly quota and short-window request limits are separate controls. A provider may report quota with a different status from 429: for example, Screenshot API documents 429 rate_limited and a separate 402 quota_reached. It says its monthly quota resets at the beginning of each UTC calendar month. Those semantics apply to Screenshot API, not all providers; consult its current documentation.
5. Prevent rate-limit errors from returning
- Smooth bursts. Use a queue and fixed worker limit instead of launching every capture simultaneously.
- Coordinate workers. Keep shared rate-limit state for processes that use one account. Independent local counters do not protect a shared provider limit.
- Cache valid results. If the URL and capture settings have not changed and freshness requirements allow it, reuse a prior result. Respect the provider’s cache behavior and any TTL you configure.
- Separate limits in monitoring. Track provider 429s, target-host 429s, quota errors, and other failed loads as distinct outcomes.
- Use provider usage data. Compare actual usage with the provider’s current documented limits. Do not copy another service’s numbers.
- Make retries bounded. Set a maximum attempt count and move persistent failures to a reviewable dead-letter queue or report them to the caller.
CCB’s 2018 API rate-limiting guide illustrates the general use of reset and remaining headers, waiting for reset, and caching stable responses. Its numeric example is historical and specific to CCB, so it should not be used as a screenshot API limit. See the CCB guide.
6. Provider differences to verify
There is no universal screenshot API rate-limit policy. Before deploying retry logic, check these items in the provider’s current docs and your account dashboard.
| Question | Why it matters |
|---|---|
| Is the cap requests per second, concurrent renders, or both? | A concurrency cap concerns active renders; a request-rate cap concerns arrivals over time. Lowering one may not satisfy the other. |
| How is the target host’s error distinguished? | A target-generated 429 calls for host-aware pacing; provider-side 429 calls for account-level pacing. |
| What signals are returned? | Confirm the names, units, and formats for Retry-After, reset, and remaining values. |
| How is monthly quota reported? | Quota exhaustion may use a different status and remedy from a temporary rate limit. |
| Can usage be inspected? | A dashboard or usage API can reveal other callers or quota consumption. |
Examples from the reviewed provider documentation show why checking matters: Urlbox maps HTTP 429 to a rate-limit error and lists RateLimitExceededError among common errors; Screenshot API documents rate_limited and Retry-After; ScreenshotOne distinguishes concurrency, target-host error, and screenshot-quota cases. The actual limits and response formats should be checked in each provider’s current docs: Urlbox API reference, Screenshot API documentation, and ScreenshotOne error reference.
7. Troubleshooting common cases
| Symptom | Likely cause | Fix |
|---|---|---|
| 429 appears only during batch jobs | A burst exceeded a rate or concurrency cap. | Queue the batch, cap worker concurrency, and resume using the provider’s reset guidance. |
| Only one destination domain fails | The target host may be returning 429. | Inspect structured error details for a host status, then reduce requests to that host and wait. |
| Retries keep returning 429 | Retry loop is too fast, reset was ignored, or another worker keeps consuming the shared allowance. | Stop immediate retries, coordinate workers, and use the documented reset signal. |
| 429 began after an increase in traffic, but concurrency is low | A short-window request-rate limit may have been exceeded. | Check whether the provider sets requests-per-second limits separately from active-render limits; pace request starts. |
| Requests fail after many successful captures | Monthly quota may be exhausted or another caller consumed it. | Inspect usage and quota errors in the account dashboard. Check the reset cycle or plan options. |
| Response body is HTML or an image parsing fails | An error response may be handled as if it were screenshot bytes. | Check status and content type before saving or decoding the body; log a bounded, redacted error body. |
No Retry-After header is present |
The provider may expose reset information another way, or no automatic retry value. | Consult the provider’s docs and usage endpoint. Use bounded backoff only when no documented signal applies. |
| Adding a proxy does not resolve 429 | The limit may belong to the screenshot API account, not the network route. | Identify which system returned the 429. A proxy does not raise a provider’s account concurrency or quota limit. |
8. Performance, reliability, and cost
Performance: Increasing parallelism can reduce queue time only until a provider or target limit is reached. Beyond that point, retries add work and latency. A bounded queue smooths traffic and makes completion times more predictable. For mixed workloads, separate queues by target host so a heavily throttled domain does not hold up unrelated captures.
Reliability: Treat 429 as a retryable signal only when the response indicates a temporary limit. Make retries bounded and idempotent from your application’s perspective: do not create duplicate downstream records every time the same capture is attempted. Persist queued work and its attempt count if jobs must survive process restarts.
Cost: Retries can consume quota or incur charges depending on provider policy. Check how each provider bills failed captures and retries; the research reviewed here does not establish a universal billing rule. Cache unchanged screenshots when appropriate, and distinguish rate-limited attempts from successful renders in your usage tracking.
Or skip the browser setup
If the task is simply to get a screenshot, ScreenshotNeo returns a screenshot or PDF from one GET request. Its API accepts the same parameter names used by other screenshot APIs, which can make switching straightforward. 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 are accepted and removed, along with known consent platforms, newsletter popups, and chat widgets, before the shot.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers report the page verdict and billing outcome.
- 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 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
FAQ
Does every screenshot API use the same 429 reset time?
No. Limits, reset intervals, headers, and account remedies vary. Use the provider’s current response details and documentation.
Should I retry a 429 automatically?
Yes, when the error is temporary and your retry honors the supplied wait or reset signal. Keep retries bounded, add pacing when needed, and stop if the response indicates quota exhaustion or a persistent target-host restriction.
Can I tell from status 429 alone whether my provider or the target site is limiting me?
No. Inspect the response body and provider-specific error fields for a host-returned status or provider-side limit code.
Will a proxy fix a provider account rate limit?
Not if the provider is enforcing the account’s request or concurrency allowance. First identify which system applied the limit.


