Rate Limits
Rate limits protect the API from abuse and ensure fair usage for everyone. Every plan has a per-minute request limit and a monthly screenshot quota. If you exceed either, the API returns an error — but it also gives you headers with the information you need to handle it gracefully.
Limits by Plan
| Plan | Rate Limit | Monthly Quota | Overage Price |
|---|---|---|---|
| Free | 5 req/min | 200 screenshots | — |
| Starter | 15 req/min | 3,000 screenshots | $0.009/screenshot |
| Pro | 40 req/min | 15,000 screenshots | $0.006/screenshot |
| Growth | 100 req/min | 50,000 screenshots | $0.004/screenshot |
| Business | 150 req/min | 100,000 screenshots | $0.003/screenshot |
Rate Limit Headers
Every API response includes headers that tell you where you stand. Use these to build smart retry logic or to show usage in your dashboard.
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed per minute for your plan. |
X-RateLimit-Remaining | How many requests you have left in the current minute. |
X-RateLimit-Reset | Unix timestamp when the rate limit counter resets. |
Retry-After | Seconds to wait before retrying. Only present on 429 responses. |
Example response headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 40
X-RateLimit-Remaining: 37
X-RateLimit-Reset: 1741520460
Usage Quota Headers
In addition to per-minute rate limits, the API tracks your monthly screenshot usage. These headers are included in every response:
| Header | Description |
|---|---|
X-Usage-Limit | Total screenshots allowed in your current billing period. |
X-Usage-Remaining | Screenshots remaining in this period. |
X-Usage-Reset | ISO 8601 timestamp when the billing period ends and usage resets. |
X-Usage-Overage | Number of screenshots over the limit (if overage pricing applies). |
X-Usage-Overage-Price | Price per overage screenshot (if applicable). |
What Happens When You Hit the Limit
Rate limit exceeded (429 Too Many Requests)
If you send more requests per minute than your plan allows, the API returns a 429 error:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please wait before making more requests.",
"retry_after": 12
}
}
The Retry-After header tells you exactly how many seconds to wait.
Usage quota exceeded (402 Payment Required)
If you have used all your monthly screenshots and your plan does not have overage pricing, the API returns a 402 error:
{
"error": {
"code": "USAGE_LIMIT_EXCEEDED",
"message": "Monthly screenshot limit reached. Upgrade your plan or wait for the next billing period."
}
}
If your plan supports overage pricing (Starter and above), requests continue working — you are charged per extra screenshot at the overage rate.
Handling Rate Limits in Code
Python — exponential backoff
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.screenshotrun.com/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
def capture_with_retry(url, max_retries=3):
for attempt in range(max_retries):
response = requests.post(
f"{BASE_URL}/screenshots",
headers=headers,
json={"url": url},
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
response.raise_for_status()
return response.json()["data"]
raise Exception("Max retries exceeded")
Node.js — exponential backoff
async function captureWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(`${BASE_URL}/screenshots`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ url }),
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get("Retry-After") || "5");
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const { data } = await response.json();
return data;
}
throw new Error("Max retries exceeded");
}
PHP — exponential backoff
function captureWithRetry(string $url, int $maxRetries = 3): array
{
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
$response = Http::withToken(config('services.screenshotrun.key'))
->post('https://api.screenshotrun.com/v1/screenshots', [
'url' => $url,
]);
if ($response->status() === 429) {
$retryAfter = (int) $response->header('Retry-After', 5);
sleep($retryAfter);
continue;
}
$response->throw();
return $response->json('data');
}
throw new \Exception('Max retries exceeded');
}
Monitoring Usage
You can check your current usage at any time via the Account API:
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.screenshotrun.com/v1/account/usage
Best Practices
- Check headers proactively. Read
X-RateLimit-Remainingbefore hitting the limit. If it's low, slow down. - Use exponential backoff. When you get a
429, wait theRetry-Afterduration, then double the wait on each retry. - Use caching. Set
cache_ttlto avoid re-capturing the same URL. Cached responses don't count toward your quota. See Caching. - Use batch for bulk work. The Batch API is more efficient than individual requests for high-volume captures.
- Use webhooks. Instead of polling (which consumes rate limit), use webhooks to know when screenshots are ready.
- Queue requests. For large workloads, queue screenshot requests on your side and process them at a rate that stays within your limits.