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."]
}
}
}
| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code constant (see table below) |
error.message | string | Human-readable error description |
error.status | integer | HTTP status code |
error.details | object|undefined | Field-level error details. Only present for VALIDATION_ERROR responses. Keys are field names, values are arrays of error messages. |
HTTP Status Codes
| Code | Meaning | Description |
|---|---|---|
200 | OK | Request successful, data returned in response body |
202 | Accepted | Screenshot queued for processing |
204 | No Content | Resource deleted successfully (no response body) |
400 | Bad Request | Request body is malformed or missing required headers |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | API key does not have permission for this action or domain |
404 | Not Found | Requested resource does not exist |
410 | Gone | Screenshot image has expired and been deleted |
402 | Payment Required | Monthly screenshot quota exhausted — upgrade your plan |
422 | Unprocessable Entity | Validation failed — check the message field for details |
429 | Too Many Requests | Rate limit exceeded — wait and retry |
500 | Server Error | Internal server error. If persistent, contact support |
Error Code Constants
Use these constants for programmatic error handling instead of relying on status codes alone:
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_API_KEY | 401 | API key is missing, invalid, expired, or revoked |
DOMAIN_NOT_ALLOWED | 403 | API key lacks permission for the requesting domain |
NOT_FOUND | 404 | Requested screenshot or resource not found |
NOT_READY | 404 | Screenshot image not ready yet (still processing) |
EXPIRED | 410 | Screenshot image has expired |
VALIDATION_ERROR | 422 | Request parameters failed validation |
RATE_LIMIT_EXCEEDED | 429 | Too many requests in the current window |
USAGE_LIMIT_EXCEEDED | 402 | Monthly screenshot quota reached |
TIMEOUT | — | Page load timed out (in screenshot error field) |
RENDER_ERROR | — | Browser rendering failed (in screenshot error field) |
INVALID_URL | — | URL could not be loaded (in screenshot error field) |
SERVER_ERROR | 500 | Internal 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:
| Header | Description | Example |
|---|---|---|
X-RateLimit-Limit | Maximum requests allowed per minute | 60 |
X-RateLimit-Remaining | Requests remaining in the current window | 45 |
X-RateLimit-Reset | Unix timestamp when the rate limit resets | 1741520400 |
Usage limit headers
When creating screenshots (POST /screenshots), the response also includes usage tracking headers:
| Header | Description | Example |
|---|---|---|
X-Usage-Limit | Monthly screenshot quota for your plan | 300 |
X-Usage-Remaining | Screenshots remaining in the current period | 250 |
X-Usage-Reset | End of the current billing period (ISO 8601) | 2026-03-31T23:59:59.999999Z |
Rate limits by plan
| Plan | Requests/minute | Screenshots/month | API Keys | Retention |
|---|---|---|---|---|
| Free | 5 | 300 | 1 | 24 hours |
| Starter | 15 | 3,000 | 2 | 48 hours |
| Pro | 40 | 15,000 | 5 | 7 days |
| Business | 100 | 50,000 | 10 | 30 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}`);
}