Features Full Page Screenshot Wait for Selector & Delay Block Cookie Banners Custom Viewport & Device Website to PDF HTML to Image Dark Mode Image Format & Quality MCP Server Webhook Pricing Docs Blog Log In Sign Up

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

PlanRate LimitMonthly QuotaOverage Price
Free5 req/min200 screenshots
Starter15 req/min3,000 screenshots$0.009/screenshot
Pro40 req/min15,000 screenshots$0.006/screenshot
Growth100 req/min50,000 screenshots$0.004/screenshot
Business150 req/min100,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.

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per minute for your plan.
X-RateLimit-RemainingHow many requests you have left in the current minute.
X-RateLimit-ResetUnix timestamp when the rate limit counter resets.
Retry-AfterSeconds to wait before retrying. Only present on 429 responses.

Example response headers

http
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:

HeaderDescription
X-Usage-LimitTotal screenshots allowed in your current billing period.
X-Usage-RemainingScreenshots remaining in this period.
X-Usage-ResetISO 8601 timestamp when the billing period ends and usage resets.
X-Usage-OverageNumber of screenshots over the limit (if overage pricing applies).
X-Usage-Overage-PricePrice 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:

json
{
  "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:

json
{
  "error": {
    "code": "USAGE_LIMIT_EXCEEDED",
    "message": "Monthly screenshot limit reached. Upgrade your plan or wait for the next billing period."
  }
}
Note

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

python
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

javascript
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

php
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:

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://api.screenshotrun.com/v1/account/usage

Best Practices

  • Check headers proactively. Read X-RateLimit-Remaining before hitting the limit. If it's low, slow down.
  • Use exponential backoff. When you get a 429, wait the Retry-After duration, then double the wait on each retry.
  • Use caching. Set cache_ttl to 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.