Skip to main content
Screenshotly enforces rate limits to ensure fair usage and service reliability.

Limits by plan

PlanMonthly screenshotsStorageRate limitPrice
Free100500 MB5 req/minFree
Starter5,0005 GB20 req/min$29/mo
Pro25,00015 GB60 req/min$79/mo
Business75,00050 GB120 req/min$199/mo
Rate limits are applied per API key within a 60-second sliding window. Check your current usage in the dashboard.

Rate limit headers

Every API response includes headers to help you track your usage:
HeaderDescription
X-RateLimit-LimitMaximum requests per minute for your plan
X-RateLimit-RemainingRemaining requests in the current window
X-RateLimit-ResetUnix timestamp when the rate limit window resets
When you hit the rate limit, a Retry-After header is also included indicating how many seconds to wait.

429 Too Many Requests

When you exceed your per-minute rate limit, the API returns a 429 status code:
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests. Please try again later."
  }
}

402 Usage Limit Exceeded

When you exceed your monthly screenshot quota, the API returns a 402 status code:
{
  "error": {
    "code": "usage_limit_exceeded",
    "message": "Monthly screenshot limit reached. Please upgrade your subscription."
  }
}

Checking your usage

Use the usage endpoint to check your remaining credits programmatically:
curl https://api.screenshotly.dev/v1/screenshots/usage \
  -H "X-API-Key: $SCREENSHOTLY_API_KEY"
When using response_type: "json" for captures, the response includes a remaining_credits field.

Handling rate limits

Exponential backoff

When you receive a 429 response, wait before retrying:
async function captureWithBackoff(url, options) {
  const maxRetries = 5;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch('https://api.screenshotly.dev/v1/capture', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.SCREENSHOTLY_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ url, options }),
    });

    if (response.ok) return response;
    if (response.status !== 429) throw new Error(`HTTP ${response.status}`);

    const retryAfter = response.headers.get('Retry-After');
    const delay = retryAfter ? parseInt(retryAfter) * 1000 : Math.pow(2, attempt) * 1000;
    console.log(`Rate limited. Retrying in ${delay}ms...`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }

  throw new Error('Rate limit retry attempts exhausted');
}

Best practices

  • Monitor usage — check the X-RateLimit-Remaining header or the usage endpoint before large operations
  • Stagger requests — spread requests over time instead of sending them all at once
  • Use batch processing — the batch endpoint processes up to 50 URLs per request
  • Respect Retry-After — use the header value for optimal retry timing
  • Upgrade your plan — if you consistently hit limits, consider upgrading at pricing
  • Cache results — store screenshot URLs and reuse them instead of recapturing the same pages