CaptureKit Screenshot API Rate Limits and Retry Handling
Handle CaptureKit rate limits and transient failures safely: inspect error bodies, retry only eligible requests, and avoid duplicate work or charges.
Direct answer: CaptureKit’s general error-handling guide says rate limits return HTTP 401, with the error text identifying the limit. It recommends waiting 2–5 seconds before retrying. The Capture endpoint reference also shows a 429 response schema, so inspect both the status and response body instead of assuming every rate limit is a 429. Retry rate limits only after a delay; retry transient 500 errors and network timeouts with bounded exponential backoff. Do not retry a 402 until you fix the billing or credit issue.
This guide covers how to classify CaptureKit failures, implement retries in cURL, Python, and Node.js, handle timeouts and batches, and avoid common retry mistakes. CaptureKit’s exact endpoint URL and capture parameters are not included in the research material below; copy those from your CaptureKit account’s current endpoint reference rather than guessing.
1. Understand CaptureKit’s limits and status codes
CaptureKit documents two kinds of limits: subscription rate limits and API-key quotas. An error can identify a daily, total, weekly, or monthly key quota. The specific requests-per-second, burst, and concurrency thresholds are not published in the reviewed documentation, so do not build assumptions around a guessed number.
| Status or failure | Meaning | Recommended action |
|---|---|---|
| 401 | Invalid, inactive, or expired API key, or a rate limit. The error text distinguishes the cases. | Fix credentials for authentication errors. If the body identifies a rate limit, wait 2–5 seconds, then retry with a cap. |
| 402 | Payment, balance, or invoice condition. The request is not executed. | Resolve credits, Auto Top-Up, plan, or invoice status before retrying. |
| 429 | The Capture endpoint reference displays a 429 schema, although the general error guide says rate limits return 401. | Inspect the response body and handle a clearly identified rate limit with the same bounded delay policy. |
| 500 | Internal server error that may be transient. | Retry with bounded exponential backoff, up to three retries as in CaptureKit’s guide. |
| Network timeout | The client did not receive a response in time; the server may or may not have completed work. | Use bounded backoff. Consider whether a retry could duplicate a capture or its downstream storage work. |
| 400 | Invalid request or parameters. | Correct the request; repeating it unchanged is not useful. |
| 403 | Permission problem. | Check access and account permissions. |
| 404 | Requested resource is absent. | Handle as absent or correct the resource identifier. |
The 401-versus-429 discrepancy is material: the endpoint reference includes a 429 response schema, while the general error guide explicitly says rate limits return 401, not 429. The reviewed docs do not explain whether 429 applies in some contexts or whether that schema is outdated. Write the handler to inspect the actual status and parse the provider’s error message.
2. Choose a retry policy
Rate limits: wait before trying again
For a response whose body identifies a rate limit, wait 2–5 seconds before retrying. Add random jitter and a maximum number of retries in production so multiple workers do not resume simultaneously. Jitter and the cap are prudent client-side safeguards; they are not stated as a provider guarantee.
500 errors and timeouts: exponential backoff
CaptureKit’s best-practices example starts at 500 ms and doubles the delay after each retry, with no more than three retries. That means an initial attempt plus up to three retry attempts. Treat this as documented guidance, not an SLA or guarantee that a later attempt will succeed.
For a real-time call, account for CaptureKit’s documented 60-second server-side timeout. A client timeout can end the request earlier. Use the provider’s asynchronous mode for work that may exceed the real-time limit.
Do not retry permanent failures unchanged
A 402 is a billing or balance condition, not a transient network failure. The request is not executed when payment is required, but it will continue to fail until you add credits, enable Auto Top-Up, upgrade, or settle an invoice as applicable. Likewise, fix invalid parameters, credentials, or permissions before trying again.
3. Send an authenticated capture request
CaptureKit authenticates capture calls with an x-api-key header. Keep the key in a server environment variable or trusted automation environment; do not put it in browser-side JavaScript or a public repository. Copy the capture URL and required request fields from the current CaptureKit endpoint reference. The examples use CAPTUREKIT_CAPTURE_URL and CAPTUREKIT_API_KEY as placeholders because the endpoint URL was not supplied in the research material.
cURL
export CAPTUREKIT_API_KEY='YOUR_API_KEY'
export CAPTUREKIT_CAPTURE_URL='YOUR_CAPTURE_ENDPOINT_FROM_CAPTUREKIT_DOCS'
curl --fail-with-body --show-error --silent \
-H "x-api-key: $CAPTUREKIT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com"}' \
"$CAPTUREKIT_CAPTURE_URL" \
-o capture-response.bin \
-w '\nHTTP %{http_code}\n'
Use the body format and output handling required by your CaptureKit endpoint. The example sends a JSON URL field only to show where the request belongs; confirm the endpoint’s current required parameters and whether it returns image bytes, JSON, or a job result before using it as-is.
Python with bounded retry handling
import os
import random
import time
import requests
endpoint = os.environ["CAPTUREKIT_CAPTURE_URL"]
api_key = os.environ["CAPTUREKIT_API_KEY"]
payload = {"url": "https://example.com"} # Confirm fields in the current endpoint docs.
session = requests.Session()
max_retries = 3
for attempt in range(max_retries + 1):
try:
response = session.post(
endpoint,
headers={"x-api-key": api_key},
json=payload,
timeout=65,
)
except requests.Timeout:
if attempt == max_retries:
raise
time.sleep((0.5 * (2 ** attempt)) + random.uniform(0, 0.25))
continue
except requests.ConnectionError:
if attempt == max_retries:
raise
time.sleep((0.5 * (2 ** attempt)) + random.uniform(0, 0.25))
continue
if response.ok:
with open("capture-response.bin", "wb") as output:
output.write(response.content)
break
try:
error = response.json()
except ValueError:
error = {"message": response.text}
message = str(error).lower()
is_rate_limit = response.status_code in (401, 429) and any(
term in message for term in ("rate limit", "quota", "limit exceeded")
)
if is_rate_limit:
if attempt == max_retries:
response.raise_for_status()
time.sleep(random.uniform(2, 5))
continue
if response.status_code == 500:
if attempt == max_retries:
response.raise_for_status()
time.sleep((0.5 * (2 ** attempt)) + random.uniform(0, 0.25))
continue
# 402, ordinary 401, 400, 403, 404, and unclassified errors need attention.
response.raise_for_status()
else:
raise RuntimeError("CaptureKit request exhausted its retry attempts")
The timeout is set slightly above the documented 60-second server-side limit to allow a response to arrive, but choose a client timeout appropriate to your own request path. If your application needs a shorter response deadline, use asynchronous capture for long-running work.
Node.js with bounded retry handling
const endpoint = process.env.CAPTUREKIT_CAPTURE_URL;
const apiKey = process.env.CAPTUREKIT_API_KEY;
if (!endpoint || !apiKey) throw new Error('Set CaptureKit endpoint and API key');
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const randomBetween = (min, max) => min + Math.random() * (max - min);
const maxRetries = 3;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
let response;
try {
response = await fetch(endpoint, {
method: 'POST',
headers: {
'x-api-key': apiKey,
'content-type': 'application/json',
},
body: JSON.stringify({ url: 'https://example.com' }), // Confirm endpoint fields.
signal: AbortSignal.timeout(65_000),
});
} catch (error) {
if (attempt === maxRetries) throw error;
await sleep(500 * (2 ** attempt) + randomBetween(0, 250));
continue;
}
if (response.ok) {
const bytes = new Uint8Array(await response.arrayBuffer());
// Save or process bytes according to the endpoint's documented response format.
console.log(`Capture response: ${bytes.byteLength} bytes`);
break;
}
const body = await response.text();
const message = body.toLowerCase();
const rateLimited = [401, 429].includes(response.status) &&
['rate limit', 'quota', 'limit exceeded'].some((term) => message.includes(term));
if (rateLimited) {
if (attempt === maxRetries) throw new Error(`Rate limit after retries: ${body}`);
await sleep(randomBetween(2000, 5000));
continue;
}
if (response.status === 500) {
if (attempt === maxRetries) throw new Error(`Server error after retries: ${body}`);
await sleep(500 * (2 ** attempt) + randomBetween(0, 250));
continue;
}
// Do not blindly retry payment, authentication, permission, or input failures.
throw new Error(`CaptureKit HTTP ${response.status}: ${body}`);
}
For a production Node service, use a supported Node version with fetch and AbortSignal.timeout, or replace those with the timeout mechanism in your HTTP library. Confirm whether the endpoint returns raw image bytes or a structured response before saving the result.
4. Make retry decisions safely
- Check status first. Do not treat every non-2xx response as a retryable failure.
- Read the error body. A 401 can be a credential problem or a rate limit; retry only when the error text identifies throttling or quota exhaustion.
- Retry only transient cases. Rate limit, 500, and network timeout are candidates for bounded retries. A 402, malformed request, ordinary authentication error, permission failure, or missing resource requires a corrective action.
- Bound attempts and time. Three retries is the upper bound in CaptureKit’s example guidance for transient errors. Set an overall deadline so retries do not exceed the caller’s own response budget.
- Account for uncertain outcomes. If a client times out after the server accepted a request, the client may not know whether processing completed. Before retrying, check whether the API supports an idempotency key or a way to retrieve an existing job; do not assume either feature exists.
- Record status and sanitized errors. Log request identifiers and response codes when available, but never log API keys or sensitive headers.
CaptureKit’s endpoint reference says one credit is charged per successful capture call. Its getting-started material says credits are deducted only when the call succeeds. A client timeout does not by itself prove that the server failed; use response or job state where available before repeating work.
5. Handle quotas, keys, and batches
Rate limits and quotas are workspace-wide across API keys. Creating additional keys does not create a separate workspace allowance. Use separate keys for access control and rotation, not as a way to multiply capacity.
For batch workloads, spread requests over time, monitor usage, and tune concurrency to observed responses and your plan. CaptureKit’s best-practices guide illustrates a 100 ms delay but says to adjust it to the plan; that interval is not a promise that every plan permits that rate. If limits recur regularly, consider an appropriate plan or contact support about higher volume or key quotas.
The pricing information reviewed lists Free trial at $0/month for 100 credits/month, Starter at $7/month for 1,000 credits/month, Pro at $29/month for 10,000 credits/month, and Ultimate at $89/month for 50,000 credits/month. Prices and quotas can change; check CaptureKit’s current billing page before choosing a plan. The reviewed material does not state the exact rate or concurrency ceilings for these plans.
6. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 with a rate or quota message | Subscription rate limit or API-key quota reached. | Wait 2–5 seconds and retry within a cap. Reduce concurrency or spread batch requests. Review workspace usage and quota. |
| 401 with an authentication message | Key is invalid, inactive, expired, missing, or sent in the wrong header. | Verify the key and send it as x-api-key. Do not retry unchanged. |
| 429 appears, but documentation says 401 | The endpoint reference and general guide describe different rate-limit statuses. | Parse the body and treat an explicitly identified rate limit as throttling regardless of whether this response is 401 or 429. |
| 402 on every attempt | Insufficient credits, payment issue, or unpaid invoice blocking traffic. | Resolve billing or credits first. Repeating the request will not fix it. |
| Repeated 500 responses | Transient or continuing server-side failure. | Use at most three retries with exponential backoff, then surface the failure and investigate or contact support. |
| Client timeout near 60 seconds | Real-time server-side timeout, client deadline, or a slow target page. | Use async mode for work that may exceed 60 seconds; check the client timeout and inspect job status if supported. |
| Retries still hit limits | Too many concurrent callers, synchronized retry bursts, or workspace-wide quota exhaustion. | Add jitter, lower concurrency, queue requests, and review plan or quota with CaptureKit. |
| 400, 403, or 404 repeats | Invalid parameters, insufficient permission, or absent resource. | Correct input, access, or resource handling instead of retrying. |
| Duplicate downstream files or work | A timeout caused a repeat after the first request may have completed. | Check job/result state before retrying and use idempotency support only if the endpoint documents it. |
7. Performance, reliability, and cost
- Performance: Backoff increases completion time by design. Keep it bounded, add jitter, and use a queue for bursts rather than making every caller retry immediately. Avoid an arbitrary fixed batch delay as a substitute for monitoring.
- Reliability: Distinguish transient failures from corrective-action failures. A status code alone is insufficient for 401, and a timeout can leave request completion uncertain. Use asynchronous jobs when a real-time capture may exceed the documented 60-second server timeout.
- Cost: CaptureKit documents one credit per successful capture call. A rate-limited or payment-required request is not executed according to its error guide; successful retries can each be successful capture calls, so avoid duplicate retries after uncertain timeouts. Check current plan credits and billing details before increasing volume.
- Capacity: Limits are shared across API keys in a workspace. More keys do not increase capacity. Reduce concurrency or discuss a higher plan or quota if the workload regularly reaches its limits.
8. Or skip the browser setup
If your goal is simply to capture a URL, ScreenshotNeo offers a one-request screenshot API and an MCP server. Its options include PNG, JPEG, WebP, or PDF output, full-page capture, element capture, wait conditions, custom headers, cookies, and more. See the ScreenshotNeo API documentation for request options and integration details.
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 the shot; newsletter popups and chat widgets are removed too. Each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers identify the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots per month are free with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
9. Frequently asked questions
Does CaptureKit always return 429 when I reach a rate limit?
No. The general error guide says rate limits return 401, while the Capture endpoint reference also shows a 429 schema. Inspect the actual status and error body.
Will adding API keys increase my quota?
No. CaptureKit documents quotas and rate limits as workspace-wide across API keys.
How many times should I retry a 500?
CaptureKit’s example guidance uses no more than three retries after the initial attempt, with exponential backoff.
Can I retry a 402 after a short delay?
Only after addressing the payment or credit condition. A delay alone does not correct it.
When should I use async mode?
Use it when a capture may take longer than the documented 60-second real-time server timeout.


