Status code overview
| Range | Meaning |
|---|---|
| 2xx | The request succeeded |
| 4xx | The request failed due to a client error |
| 5xx | The request failed due to a server error |
Error response format
All error responses return a JSON body with anerror object containing code and message fields:
{
"error": {
"code": "invalid_options",
"message": "Validation failed."
}
}
details field with additional context:
{
"error": {
"code": "invalid_options",
"message": "Validation failed.",
"details": {
"url": "\"url\" is required"
}
}
}
Error codes
| Status | Code | Description |
|---|---|---|
| 401 | missing_api_key | No API key provided. Pass it via the X-API-Key header or api_key query parameter. |
| 401 | invalid_api_key | The provided API key is invalid or has been revoked. |
| 401 | unauthorized | Authentication required. Provide a valid API key. |
| 402 | usage_limit_exceeded | Monthly screenshot limit reached. Upgrade your subscription at the dashboard. |
| 403 | forbidden | You do not have permission to access this resource. |
| 404 | not_found | The requested resource (e.g., screenshot ID) was not found. |
| 422 | invalid_options | Validation failed. Check the details field for specific parameter errors. |
| 422 | invalid_url | The provided URL is not valid. Ensure it includes a protocol (e.g., https://). |
| 422 | selector_not_found | The specified CSS selector was not found on the page. Only returned when error_on_selector_not_found is true. |
| 429 | rate_limit_exceeded | Too many requests per minute. See rate limits. |
| 500 | render_failed | Screenshot rendering failed. The target page may have caused an error. |
| 500 | internal_error | An unexpected server error occurred. Retry the request. If it persists, contact support. |
| 502 | host_error | Could not reach the target URL. The site may be down or blocking requests. |
| 503 | service_unavailable | The screenshot service is starting up. Retry in a few seconds. |
| 504 | navigation_timeout | Page navigation timed out. Try increasing the navigation_timeout or timeout options. |
Handling errors
Python
import requests
response = requests.post(
'https://api.screenshotly.dev/v1/capture',
headers={'X-API-Key': API_KEY, 'Content-Type': 'application/json'},
json={'url': 'https://example.com', 'options': {'response_type': 'json'}}
)
if response.status_code == 401:
print('Check your API key')
elif response.status_code == 402:
print('Monthly limit reached — upgrade your plan')
elif response.status_code == 429:
print('Rate limited — retry after a delay')
elif response.status_code >= 400:
error = response.json()['error']
print(f"Error [{error['code']}]: {error['message']}")
JavaScript
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: 'https://example.com',
options: { response_type: 'json' },
}),
});
if (!response.ok) {
const { error } = await response.json();
console.error(`Error [${error.code}]: ${error.message}`);
if (error.details) {
console.error('Details:', error.details);
}
}
Retry strategy
For transient errors (429, 5xx), implement exponential backoff:
async function captureWithRetry(url, options, maxRetries = 3) {
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;
// Don't retry client errors (except 429)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message}`);
}
// Wait before retrying: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error('Max retries exceeded');
}

