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:

json
{
  "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:

HeaderDescription
x-nodium-trace-idUnique trace ID for this request. Include this in support tickets for faster debugging.
x-nodium-error-codeMachine-readable error code (same as error.code in the body).

Error Codes

Authentication Errors (400/403)


access_key_required

HTTP Status400 Bad Request
DescriptionThe request does not include an API key.
Common CausesMissing access_key query parameter, missing X-Access-Key header, and no access_key in the JSON body.
SolutionProvide 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:

json
{
  "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 Status400 Bad Request
DescriptionThe provided API key is not recognized, has been revoked, or is malformed.
Common CausesTypographical error in the key, using a deleted or rotated key, using a key from a different environment (e.g., test key in production).
SolutionVerify 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:

json
{
  "error": {
    "code": "access_key_invalid",
    "message": "The provided access key is invalid or has been revoked."
  }
}

access_key_suspended

HTTP Status403 Forbidden
DescriptionThe account associated with the API key has been suspended.
Common CausesBilling failure, terms of service violation, or administrative suspension.
SolutionCheck your account status in the Nodium Dashboard. Resolve any outstanding billing issues or contact support at support@nodium.io.

Example response:

json
{
  "error": {
    "code": "access_key_suspended",
    "message": "Your account has been suspended. Please contact support."
  }
}

Validation Errors (400)


request_not_valid

HTTP Status400 Bad Request
DescriptionOne or more request parameters are invalid, missing, or conflicting.
Common CausesMissing required source parameter (url, html, or markdown), providing multiple source parameters, invalid parameter types (e.g., string where integer is expected), unsupported parameter combinations.
SolutionCheck the error message for details on which parameter is invalid. Refer to the Screenshot Options for valid parameter values and types.

Example response:

json
{
  "error": {
    "code": "request_not_valid",
    "message": "Exactly one source parameter (url, html, or markdown) is required."
  }
}

url_not_valid

HTTP Status400 Bad Request
DescriptionThe provided URL is malformed or does not use a supported protocol.
Common CausesMissing protocol scheme (e.g., example.com instead of https://example.com), invalid characters in the URL, using an unsupported scheme (e.g., ftp://).
SolutionEnsure the URL starts with http:// or https:// and is properly encoded. Special characters in query strings should be URL-encoded.

Example response:

json
{
  "error": {
    "code": "url_not_valid",
    "message": "The URL 'example.com' is not valid. URLs must start with http:// or https://."
  }
}

url_not_reachable

HTTP Status400 Bad Request
DescriptionThe target URL cannot be reached. DNS resolution succeeded but the connection failed.
Common CausesTarget server is down, firewall blocking the connection, the server refused the connection, SSL/TLS certificate errors.
SolutionVerify 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:

json
{
  "error": {
    "code": "url_not_reachable",
    "message": "The URL 'https://down.example.com' could not be reached. Connection refused."
  }
}

html_too_large

HTTP Status400 Bad Request
DescriptionThe html or markdown parameter value exceeds the maximum allowed size.
Common CausesPassing extremely large HTML content, embedding large base64 images inline, including entire stylesheets inline.
SolutionReduce 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:

json
{
  "error": {
    "code": "html_too_large",
    "message": "The HTML content exceeds the maximum allowed size of 2MB."
  }
}

format_not_supported

HTTP Status400 Bad Request
DescriptionThe requested output format is not supported.
Common CausesTypographical error in the format value, using an unsupported format string.
SolutionUse 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:

json
{
  "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 Status402 Payment Required
DescriptionThe monthly screenshot quota for your plan has been exhausted.
Common CausesHigh usage, unexpected traffic spike, inefficient API calls (e.g., not using caching for repeated screenshots).
SolutionCheck 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:

json
{
  "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 Status402 Payment Required
DescriptionThe prepaid credit balance has been fully consumed.
Common CausesAll purchased credits have been used.
SolutionPurchase additional credits in the Dashboard or switch to a subscription plan.

Example response:

json
{
  "error": {
    "code": "credits_exhausted",
    "message": "Your credit balance is exhausted. Purchase additional credits to continue."
  }
}

concurrency_limit_reached

HTTP Status429 Too Many Requests
DescriptionThe maximum number of concurrent requests for your plan has been reached.
Common CausesSending too many requests simultaneously, long-running requests occupying all slots, not handling responses before sending new requests.
SolutionWait 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:

json
{
  "error": {
    "code": "concurrency_limit_reached",
    "message": "Concurrency limit reached (10/10). Wait for ongoing requests to complete."
  }
}

rate_limit_exceeded

HTTP Status429 Too Many Requests
DescriptionThe request rate limit has been exceeded. Too many requests were sent in a short time window.
Common CausesBurst traffic, tight loop sending requests without delays, no backoff on retries.
SolutionImplement 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:

json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 5 seconds."
  }
}

Server Errors (500)


timeout_error

HTTP Status500 Internal Server Error
DescriptionThe screenshot operation exceeded the configured timeout.
Common CausesTarget page takes too long to load, heavy JavaScript execution, waiting for a selector that never appears, extremely large full-page captures.
SolutionIncrease 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:

json
{
  "error": {
    "code": "timeout_error",
    "message": "The operation timed out after 60 seconds."
  }
}

network_error

HTTP Status500 Internal Server Error
DescriptionA network-level error occurred while connecting to the target URL.
Common CausesDNS resolution failed, target server is unreachable, connection reset by the server, SSL/TLS handshake failure, the target blocks datacenter IP ranges.
SolutionVerify 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:

json
{
  "error": {
    "code": "network_error",
    "message": "Failed to connect to 'https://example.com'. Connection timed out."
  }
}

rendering_error

HTTP Status500 Internal Server Error
DescriptionThe browser encountered an error while rendering the page or generating the output.
Common CausesPage triggers a browser crash, extremely complex CSS or SVG content, out-of-memory during rendering, GPU rendering failure.
SolutionSimplify 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:

json
{
  "error": {
    "code": "rendering_error",
    "message": "An error occurred while rendering the page."
  }
}

storage_error

HTTP Status500 Internal Server Error
DescriptionThe screenshot was captured successfully but could not be uploaded to the configured storage destination.
Common CausesInvalid storage credentials, bucket does not exist, permission denied on the bucket, storage service outage.
SolutionVerify 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:

json
{
  "error": {
    "code": "storage_error",
    "message": "Failed to upload screenshot to S3 bucket 'my-bucket'. Access denied."
  }
}

internal_application_error

HTTP Status500 Internal Server Error
DescriptionAn unexpected internal error occurred in the Nodium service. This is not caused by your request parameters.
Common CausesTemporary infrastructure issue, service deployment in progress.
SolutionThis 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:

json
{
  "error": {
    "code": "internal_application_error",
    "message": "An internal error occurred. Please retry your request."
  }
}

Service Availability (503)


temporary_unavailable

HTTP Status503 Service Unavailable
DescriptionThe service is temporarily overloaded or undergoing maintenance.
Common CausesHigh platform-wide load, scheduled maintenance, infrastructure scaling event.
SolutionThis 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:

json
{
  "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 CodeMeaningWhen Returned
200OKSuccessful synchronous screenshot. Response body contains the screenshot data (or JSON metadata if response_type=json).
202AcceptedSuccessful async request (async=true). The screenshot is being processed and will be delivered via webhook.
204No ContentSuccessful request with response_type=empty. Headers contain metadata but the body is empty.
400Bad RequestInvalid parameters, authentication issues, or validation errors.
402Payment RequiredQuota exceeded or credits exhausted.
403ForbiddenAccount suspended.
413Payload Too LargeRequest body exceeds the maximum allowed size.
429Too Many RequestsConcurrency limit or rate limit exceeded. Check Retry-After header.
500Internal Server ErrorServer-side errors including timeouts, network failures, rendering errors, and internal errors.
503Service UnavailableTemporary 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 CodeRecommended Backoff
concurrency_limit_reachedRespect Retry-After header, or wait 1--5 seconds
rate_limit_exceededRespect Retry-After header, or wait 5--30 seconds
timeout_errorWait 5--10 seconds, consider increasing timeout parameter
network_errorWait 2--10 seconds
rendering_errorWait 2--5 seconds
storage_errorWait 5--10 seconds
internal_application_errorWait 2--10 seconds
temporary_unavailableRespect 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_required
  • access_key_invalid
  • access_key_suspended
  • request_not_valid
  • url_not_valid
  • html_too_large
  • format_not_supported
  • screenshots_limit_reached
  • credits_exhausted

Exponential backoff with jitter

For retryable errors, use exponential backoff with random jitter to avoid thundering herd problems:

python
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")
javascript
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.

bash
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
fi

2. 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:

bash
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.