Features Pricing Docs Blog Playground Log In Sign Up

Error Codes & Rate Limits

This page covers error handling, HTTP status codes, error code constants, and rate limiting in the ScreenshotAPI.

Error Response Format

All API errors follow a consistent JSON format:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The url field is required.",
    "status": 422,
    "details": {
      "url": ["The url field is required."]
    }
  }
}
FieldTypeDescription
error.codestringMachine-readable error code constant (see table below)
error.messagestringHuman-readable error description
error.statusintegerHTTP status code
error.detailsobject|undefinedField-level error details. Only present for VALIDATION_ERROR responses. Keys are field names, values are arrays of error messages.

HTTP Status Codes

CodeMeaningDescription
200OKRequest successful, data returned in response body
202AcceptedScreenshot queued for processing
204No ContentResource deleted successfully (no response body)
400Bad RequestRequest body is malformed or missing required headers
401UnauthorizedMissing or invalid API key
403ForbiddenAPI key does not have permission for this action or domain
404Not FoundRequested resource does not exist
410GoneScreenshot image has expired and been deleted
402Payment RequiredMonthly screenshot quota exhausted — upgrade your plan
422Unprocessable EntityValidation failed — check the message field for details
429Too Many RequestsRate limit exceeded — wait and retry
500Server ErrorInternal server error. If persistent, contact support

Error Code Constants

Use these constants for programmatic error handling instead of relying on status codes alone:

Error CodeHTTP StatusDescription
INVALID_API_KEY401API key is missing, invalid, expired, or revoked
DOMAIN_NOT_ALLOWED403API key lacks permission for the requesting domain
NOT_FOUND404Requested screenshot or resource not found
NOT_READY404Screenshot image not ready yet (still processing)
EXPIRED410Screenshot image has expired
VALIDATION_ERROR422Request parameters failed validation
RATE_LIMIT_EXCEEDED429Too many requests in the current window
USAGE_LIMIT_EXCEEDED402Monthly screenshot quota reached
TIMEOUTPage load timed out (in screenshot error field)
RENDER_ERRORBrowser rendering failed (in screenshot error field)
INVALID_URLURL could not be loaded (in screenshot error field)
SERVER_ERROR500Internal server error

Common Error Scenarios

Missing required field

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The url field is required.",
    "status": 422,
    "details": {
      "url": ["The url field is required."]
    }
  }
}

Invalid URL

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The url field must be a valid URL.",
    "status": 422,
    "details": {
      "url": ["The url field must be a valid URL."]
    }
  }
}

Parameter out of range

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The width field must be between 320 and 3840.",
    "status": 422,
    "details": {
      "width": ["The width field must be between 320 and 3840."]
    }
  }
}

Rate limit exceeded

json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 45 seconds.",
    "status": 429
  }
}

Monthly usage limit reached

json
{
  "error": {
    "code": "USAGE_LIMIT_EXCEEDED",
    "message": "Monthly screenshot limit reached. Upgrade your plan for more screenshots.",
    "status": 402
  }
}

Screenshot not ready

json
{
  "error": {
    "code": "NOT_READY",
    "message": "Screenshot is not ready yet.",
    "status": 404
  }
}

Screenshot expired

json
{
  "error": {
    "code": "EXPIRED",
    "message": "Screenshot has expired.",
    "status": 410
  }
}

Rate Limits

Rate limits are applied per user account based on your plan. Every API response includes rate limit headers:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed per minute60
X-RateLimit-RemainingRequests remaining in the current window45
X-RateLimit-ResetUnix timestamp when the rate limit resets1741520400

Usage limit headers

When creating screenshots (POST /screenshots), the response also includes usage tracking headers:

HeaderDescriptionExample
X-Usage-LimitMonthly screenshot quota for your plan300
X-Usage-RemainingScreenshots remaining in the current period250
X-Usage-ResetEnd of the current billing period (ISO 8601)2026-03-31T23:59:59.999999Z

Rate limits by plan

PlanRequests/minuteScreenshots/monthAPI KeysRetention
Free5300124 hours
Starter153,000248 hours
Pro4015,00057 days
Business10050,0001030 days

Note: These limits reflect the current plan configuration and may be adjusted over time. Your actual limits are always available via the X-RateLimit-* and X-Usage-* response headers, or through GET /api/v1/account.

Need higher limits? View our plans or contact us for a custom plan.

Handling Errors in Code

PHP

php
$response = Http::withToken(env('SCREENSHOT_API_KEY'))
    ->post('https://screenshotrun.com/api/v1/screenshots', [
        'url' => 'https://example.com',
    ]);

if ($response->successful()) {
    $screenshot = $response->json('data');
    // Handle success...
} elseif ($response->status() === 429) {
    $retryAfter = $response->header('X-RateLimit-Reset');
    // Wait and retry...
} elseif ($response->status() === 422) {
    $error = $response->json('error');
    // Handle validation error...
} else {
    $error = $response->json('error');
    Log::error("API Error: {$error['code']} - {$error['message']}");
}

Python

python
import requests
import time

response = requests.post(
    "https://screenshotrun.com/api/v1/screenshots",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"url": "https://example.com"}
)

if response.ok:
    screenshot = response.json()["data"]
    # Handle success...
elif response.status_code == 429:
    reset_at = int(response.headers.get("X-RateLimit-Reset", 0))
    wait_seconds = max(reset_at - int(time.time()), 1)
    time.sleep(wait_seconds)
    # Retry the request...
else:
    error = response.json()["error"]
    print(f"Error [{error['code']}]: {error['message']}")

JavaScript

javascript
const response = await fetch("https://screenshotrun.com/api/v1/screenshots", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com" }),
});

if (response.ok) {
  const { data } = await response.json();
  // Handle success...
} else if (response.status === 429) {
  const resetAt = response.headers.get("X-RateLimit-Reset");
  const waitMs = Math.max(resetAt * 1000 - Date.now(), 1000);
  await new Promise((resolve) => setTimeout(resolve, waitMs));
  // Retry the request...
} else {
  const { error } = await response.json();
  console.error(`Error [${error.code}]: ${error.message}`);
}