Nodium Screenshot API -- Error Reference
Complete reference for all error codes, HTTP status codes, and error handling best practices.
Base URL: https://api.nodium.io/api/v1/screenshot
Table of Contents
- Authentication Errors (400/403) - Validation Errors (400) - Quota & Rate Limiting (402/429) - Server Errors (500) - Service Availability (503)
Error Response Format
All errors are returned as JSON with the following structure:
{
"error": {
"code": "error_code_here",
"message": "Human-readable description of the error."
}
}The HTTP status code is set to the appropriate value (400, 402, 403, 429, 500, or 503). The Content-Type header is application/json for all error responses, regardless of the requested output format.
Response headers on error:
| Header | Description |
|---|---|
x-nodium-trace-id | Unique trace ID for this request. Include this in support tickets for faster debugging. |
x-nodium-error-code | Machine-readable error code (same as error.code in the body). |
Error Codes
Authentication Errors (400/403)
access_key_required
| HTTP Status | 400 Bad Request |
| Description | The request does not include an API key. |
| Common Causes | Missing access_key query parameter, missing X-Access-Key header, and no access_key in the JSON body. |
| Solution | Provide your API key using one of the three supported methods: query parameter (?access_key=KEY), HTTP header (X-Access-Key: KEY), or JSON body field ("access_key": "KEY"). |
Example response:
{
"error": {
"code": "access_key_required",
"message": "An access key is required. Provide it via the access_key parameter, X-Access-Key header, or JSON body."
}
}access_key_invalid
| HTTP Status | 400 Bad Request |
| Description | The provided API key is not recognized, has been revoked, or is malformed. |
| Common Causes | Typographical error in the key, using a deleted or rotated key, using a key from a different environment (e.g., test key in production). |
| Solution | Verify your API key in the Nodium Dashboard under API Keys. Generate a new key if the current one has been revoked. Ensure the key has the screenshot scope enabled. |
Example response:
{
"error": {
"code": "access_key_invalid",
"message": "The provided access key is invalid or has been revoked."
}
}access_key_suspended
| HTTP Status | 403 Forbidden |
| Description | The account associated with the API key has been suspended. |
| Common Causes | Billing failure, terms of service violation, or administrative suspension. |
| Solution | Check your account status in the Nodium Dashboard. Resolve any outstanding billing issues or contact support at support@nodium.io. |
Example response:
{
"error": {
"code": "access_key_suspended",
"message": "Your account has been suspended. Please contact support."
}
}Validation Errors (400)
request_not_valid
| HTTP Status | 400 Bad Request |
| Description | One or more request parameters are invalid, missing, or conflicting. |
| Common Causes | Missing required source parameter (url, html, or markdown), providing multiple source parameters, invalid parameter types (e.g., string where integer is expected), unsupported parameter combinations. |
| Solution | Check the error message for details on which parameter is invalid. Refer to the Screenshot Options for valid parameter values and types. |
Example response:
{
"error": {
"code": "request_not_valid",
"message": "Exactly one source parameter (url, html, or markdown) is required."
}
}url_not_valid
| HTTP Status | 400 Bad Request |
| Description | The provided URL is malformed or does not use a supported protocol. |
| Common Causes | Missing protocol scheme (e.g., example.com instead of https://example.com), invalid characters in the URL, using an unsupported scheme (e.g., ftp://). |
| Solution | Ensure the URL starts with http:// or https:// and is properly encoded. Special characters in query strings should be URL-encoded. |
Example response:
{
"error": {
"code": "url_not_valid",
"message": "The URL 'example.com' is not valid. URLs must start with http:// or https://."
}
}url_not_reachable
| HTTP Status | 400 Bad Request |
| Description | The target URL cannot be reached. DNS resolution succeeded but the connection failed. |
| Common Causes | Target server is down, firewall blocking the connection, the server refused the connection, SSL/TLS certificate errors. |
| Solution | Verify the target URL is accessible from a browser. Check if the server requires specific headers or authentication. If the site uses a self-signed certificate, this may cause connection failures. |
Example response:
{
"error": {
"code": "url_not_reachable",
"message": "The URL 'https://down.example.com' could not be reached. Connection refused."
}
}html_too_large
| HTTP Status | 400 Bad Request |
| Description | The html or markdown parameter value exceeds the maximum allowed size. |
| Common Causes | Passing extremely large HTML content, embedding large base64 images inline, including entire stylesheets inline. |
| Solution | Reduce the size of the HTML/Markdown content. Host large assets (images, stylesheets) externally and reference them by URL. Consider using the url parameter instead if the content is hosted. |
Example response:
{
"error": {
"code": "html_too_large",
"message": "The HTML content exceeds the maximum allowed size of 2MB."
}
}format_not_supported
| HTTP Status | 400 Bad Request |
| Description | The requested output format is not supported. |
| Common Causes | Typographical error in the format value, using an unsupported format string. |
| Solution | Use one of the supported formats. Image: png, jpeg (alias jpg), webp, avif, heif, tiff, jp2, gif. Document: pdf, html, markdown. Video (for /animate): mp4, webm, gif, mov, avi. |
Example response:
{
"error": {
"code": "format_not_supported",
"message": "The format 'bmp' is not supported. Supported formats: png, jpeg, webp, avif, heif, tiff, jp2, gif, pdf, html, markdown."
}
}Quota & Rate Limiting (402/429)
screenshots_limit_reached
| HTTP Status | 402 Payment Required |
| Description | The monthly screenshot quota for your plan has been exhausted. |
| Common Causes | High usage, unexpected traffic spike, inefficient API calls (e.g., not using caching for repeated screenshots). |
| Solution | Check your usage in the Dashboard. Upgrade your plan for a higher quota. Enable caching (cache=true) to reduce quota consumption on repeated requests. Use the /usage endpoint to monitor remaining quota programmatically. |
Example response:
{
"error": {
"code": "screenshots_limit_reached",
"message": "Monthly screenshot limit reached (10000/10000). Upgrade your plan or wait for the next billing period."
}
}credits_exhausted
| HTTP Status | 402 Payment Required |
| Description | The prepaid credit balance has been fully consumed. |
| Common Causes | All purchased credits have been used. |
| Solution | Purchase additional credits in the Dashboard or switch to a subscription plan. |
Example response:
{
"error": {
"code": "credits_exhausted",
"message": "Your credit balance is exhausted. Purchase additional credits to continue."
}
}concurrency_limit_reached
| HTTP Status | 429 Too Many Requests |
| Description | The maximum number of concurrent requests for your plan has been reached. |
| Common Causes | Sending too many requests simultaneously, long-running requests occupying all slots, not handling responses before sending new requests. |
| Solution | Wait for in-flight requests to complete before sending new ones. Implement a request queue with concurrency limiting. Upgrade your plan for higher concurrency. Check concurrency.remaining from the /usage endpoint. The Retry-After header indicates when to retry. |
Example response:
{
"error": {
"code": "concurrency_limit_reached",
"message": "Concurrency limit reached (10/10). Wait for ongoing requests to complete."
}
}rate_limit_exceeded
| HTTP Status | 429 Too Many Requests |
| Description | The request rate limit has been exceeded. Too many requests were sent in a short time window. |
| Common Causes | Burst traffic, tight loop sending requests without delays, no backoff on retries. |
| Solution | Implement exponential backoff on retries. Spread requests over time. Check the Retry-After response header for the recommended wait duration. Use the /bulk endpoint to batch multiple screenshots into a single request. |
Example response:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 5 seconds."
}
}Server Errors (500)
timeout_error
| HTTP Status | 500 Internal Server Error |
| Description | The screenshot operation exceeded the configured timeout. |
| Common Causes | Target page takes too long to load, heavy JavaScript execution, waiting for a selector that never appears, extremely large full-page captures. |
| Solution | Increase the timeout parameter (max 90s sync, 300s async). Use async=true for long-running operations. Optimize wait_until settings -- networkidle0 can be slow on pages with persistent connections; try networkidle2 or load instead. Set full_page_max_height to limit capture size. |
Example response:
{
"error": {
"code": "timeout_error",
"message": "The operation timed out after 60 seconds."
}
}network_error
| HTTP Status | 500 Internal Server Error |
| Description | A network-level error occurred while connecting to the target URL. |
| Common Causes | DNS resolution failed, target server is unreachable, connection reset by the server, SSL/TLS handshake failure, the target blocks datacenter IP ranges. |
| Solution | Verify the target URL is accessible. If the target blocks datacenter IPs, use a residential proxy via the proxy_url parameter or ip_country_code. This error is safe to retry with exponential backoff. |
Example response:
{
"error": {
"code": "network_error",
"message": "Failed to connect to 'https://example.com'. Connection timed out."
}
}rendering_error
| HTTP Status | 500 Internal Server Error |
| Description | The browser encountered an error while rendering the page or generating the output. |
| Common Causes | Page triggers a browser crash, extremely complex CSS or SVG content, out-of-memory during rendering, GPU rendering failure. |
| Solution | Simplify the page content. Reduce the viewport size or device_scale_factor. Disable full_page if the page is very tall. Try adding fail_if_gpu_rendering_fails=false to allow CPU fallback. This error is safe to retry. |
Example response:
{
"error": {
"code": "rendering_error",
"message": "An error occurred while rendering the page."
}
}storage_error
| HTTP Status | 500 Internal Server Error |
| Description | The screenshot was captured successfully but could not be uploaded to the configured storage destination. |
| Common Causes | Invalid storage credentials, bucket does not exist, permission denied on the bucket, storage service outage. |
| Solution | Verify your storage configuration in the Dashboard under Settings > Storage. Ensure the bucket exists and the credentials have write permissions. Check the storage provider's status page for outages. |
Example response:
{
"error": {
"code": "storage_error",
"message": "Failed to upload screenshot to S3 bucket 'my-bucket'. Access denied."
}
}internal_application_error
| HTTP Status | 500 Internal Server Error |
| Description | An unexpected internal error occurred in the Nodium service. This is not caused by your request parameters. |
| Common Causes | Temporary infrastructure issue, service deployment in progress. |
| Solution | This error is safe to retry. Use exponential backoff with jitter. If the error persists after multiple retries, contact support with the x-nodium-trace-id from the response headers. |
Example response:
{
"error": {
"code": "internal_application_error",
"message": "An internal error occurred. Please retry your request."
}
}Service Availability (503)
temporary_unavailable
| HTTP Status | 503 Service Unavailable |
| Description | The service is temporarily overloaded or undergoing maintenance. |
| Common Causes | High platform-wide load, scheduled maintenance, infrastructure scaling event. |
| Solution | This error is safe to retry. Wait and retry with exponential backoff. Check the Retry-After header for the recommended wait duration. Monitor the Nodium status page for ongoing incidents. |
Example response:
{
"error": {
"code": "temporary_unavailable",
"message": "The service is temporarily unavailable. Please retry shortly."
}
}HTTP Status Codes
Summary of all HTTP status codes returned by the API.
| Status Code | Meaning | When Returned |
|---|---|---|
200 | OK | Successful synchronous screenshot. Response body contains the screenshot data (or JSON metadata if response_type=json). |
202 | Accepted | Successful async request (async=true). The screenshot is being processed and will be delivered via webhook. |
204 | No Content | Successful request with response_type=empty. Headers contain metadata but the body is empty. |
400 | Bad Request | Invalid parameters, authentication issues, or validation errors. |
402 | Payment Required | Quota exceeded or credits exhausted. |
403 | Forbidden | Account suspended. |
413 | Payload Too Large | Request body exceeds the maximum allowed size. |
429 | Too Many Requests | Concurrency limit or rate limit exceeded. Check Retry-After header. |
500 | Internal Server Error | Server-side errors including timeouts, network failures, rendering errors, and internal errors. |
503 | Service Unavailable | Temporary overload or maintenance. Check Retry-After header. |
Retry Strategy
Not all errors should be retried. Use the following guidelines:
Retryable errors
These errors are transient and the same request may succeed on retry:
| Error Code | Recommended Backoff |
|---|---|
concurrency_limit_reached | Respect Retry-After header, or wait 1--5 seconds |
rate_limit_exceeded | Respect Retry-After header, or wait 5--30 seconds |
timeout_error | Wait 5--10 seconds, consider increasing timeout parameter |
network_error | Wait 2--10 seconds |
rendering_error | Wait 2--5 seconds |
storage_error | Wait 5--10 seconds |
internal_application_error | Wait 2--10 seconds |
temporary_unavailable | Respect Retry-After header, or wait 10--60 seconds |
Non-retryable errors
These errors indicate a problem with the request itself. Retrying the same request will produce the same error:
access_key_requiredaccess_key_invalidaccess_key_suspendedrequest_not_validurl_not_validhtml_too_largeformat_not_supportedscreenshots_limit_reachedcredits_exhausted
Exponential backoff with jitter
For retryable errors, use exponential backoff with random jitter to avoid thundering herd problems:
import time
import random
import requests
def take_screenshot(params, max_retries=5):
base_delay = 1 # seconds
for attempt in range(max_retries):
response = requests.get(
"https://api.nodium.io/api/v1/screenshot/take",
params=params,
headers={"X-Access-Key": "YOUR_KEY"}
)
if response.status_code == 200:
return response.content
error = response.json().get("error", {})
error_code = error.get("code", "")
# Do not retry non-retryable errors
non_retryable = [
"access_key_required", "access_key_invalid", "access_key_suspended",
"request_not_valid", "url_not_valid", "html_too_large",
"format_not_supported", "screenshots_limit_reached", "credits_exhausted"
]
if error_code in non_retryable:
raise Exception(f"Non-retryable error: {error_code} - {error.get('message')}")
# Respect Retry-After header if present
retry_after = response.headers.get("Retry-After")
if retry_after:
delay = int(retry_after)
else:
# Exponential backoff with jitter
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"Attempt {attempt + 1} failed ({error_code}). Retrying in {delay:.1f}s...")
time.sleep(delay)
raise Exception(f"Max retries ({max_retries}) exceeded")async function takeScreenshot(params, maxRetries = 5) {
const nonRetryable = [
"access_key_required", "access_key_invalid", "access_key_suspended",
"request_not_valid", "url_not_valid", "html_too_large",
"format_not_supported", "screenshots_limit_reached", "credits_exhausted"
];
for (let attempt = 0; attempt < maxRetries; attempt++) {
const url = new URL("https://api.nodium.io/api/v1/screenshot/take");
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
const response = await fetch(url, {
headers: { "X-Access-Key": "YOUR_KEY" }
});
if (response.ok) {
return await response.arrayBuffer();
}
const { error } = await response.json();
if (nonRetryable.includes(error.code)) {
throw new Error(`Non-retryable: ${error.code} - ${error.message}`);
}
const retryAfter = response.headers.get("Retry-After");
const delay = retryAfter
? parseInt(retryAfter) * 1000
: Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, 30000);
console.log(`Attempt ${attempt + 1} failed (${error.code}). Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
throw new Error(`Max retries (${maxRetries}) exceeded`);
}Error Handling Best Practices
1. Always check the HTTP status code
Do not assume every response is a successful screenshot. Check the status code before processing the response body.
response=$(curl -s -w "\n%{http_code}" \
"https://api.nodium.io/api/v1/screenshot/take?access_key=KEY&url=https://example.com&format=png")
http_code=$(echo "$response" | tail -1)
if [ "$http_code" -ne 200 ]; then
echo "Error: HTTP $http_code"
echo "$response" | head -n -1
exit 1
fi2. Log the trace ID
Every response includes an x-nodium-trace-id header. Log this value for debugging. When contacting support, always include the trace ID for faster resolution.
3. Monitor your quota proactively
Use the /usage endpoint to check remaining quota before sending bulk requests:
curl -s "https://api.nodium.io/api/v1/screenshot/usage?access_key=KEY" | jq .4. Use caching to reduce quota consumption
Enable cache=true for screenshots that do not need to be fresh on every request. Cached screenshots are not counted against your quota.
5. Set appropriate timeouts
Configure the timeout parameter based on the complexity of your target page. For most pages, the default (60s) is sufficient. For heavy single-page applications, consider increasing to 90s or using async mode.
6. Handle rate limits gracefully
Implement a request queue with concurrency control that respects your plan's limits. The /usage endpoint reports concurrency.remaining to help you manage this dynamically.
7. Use response_type=json for programmatic access
When integrating the API into automated workflows, use response_type=json to receive structured metadata alongside the screenshot URL. This makes error handling and result processing more predictable than parsing binary responses.