Error codes

Every failed RenderScreenshot request returns a non-2xx HTTP status and a JSON body with one error object. This page is the index: it lists each code, its status and whether a retry can help, and links to a page per code. It is part of the API documentation. For symptoms that are not errors, such as a blank image, see troubleshooting.

The error object

{
  "error": {
    "type": "validation_error",
    "code": "invalid_url",
    "message": "The URL 'not-a-url' is not a valid HTTP(S) URL",
    "param": "url",
    "retryable": false,
    "docs": "https://renderscreenshot.com/docs/errors/invalid-url"
  },
  "request_id": "3f1c9a52-7d4e-4b8a-9e21-6c0d5b7a8f13"
}
Field Always present Use it for
error.type Yes The family of the error. It maps one-to-one to the HTTP status.
error.code Yes Branching in code. Stable, snake_case.
error.message Yes Logs and humans. Render failures put the cause here.
error.retryable Yes A hint from the server. See the retry column below for our advice per code.
error.param Validation errors The parameter to fix, as a dotted path such as cache.ttl.
error.docs Validation, authentication and authorization errors A link to the explanation of the code.
request_id Yes Quote it when you contact support.

A failed request does not use a credit.

Status codes at a glance

HTTP error.type Meaning Should you retry?
400 validation_error The request is malformed No. Fix the parameter in error.param.
401 authentication_error No valid key or signature No. Fix the credentials.
403 authorization_error Valid key, action not allowed No. Change plan, credits or settings.
404 not_found_error Unknown endpoint, cache key or batch No.
422 target_error The target page could not be captured Once, unless the URL was refused.
429 rate_limit_error Too many requests Yes, after Retry-After seconds.
500 internal_error Our fault Yes, with exponential backoff.

Code index

Validation (400)

Code Meaning Retry?
invalid_url url is not an HTTP or HTTPS URL No
missing_required None of url, html or markdown was sent No
invalid_parameter A value is out of range No
invalid_format Unsupported output format No
invalid_preset Unknown preset name. GET /v1/presets lists them. No
invalid_selector CSS selector syntax error No

The API also returns parameter-specific validation codes. Each one sets error.param, so you can show the message next to the right field:

  • Request options: invalid_device, invalid_cache_ttl, invalid_media_type, invalid_timezone, invalid_locale, invalid_geolocation.
  • Target authentication and cookies: invalid_auth_type, missing_auth_credentials, missing_auth_token, invalid_cookie, invalid_same_site.
  • PDF: invalid_paper_format, invalid_pdf_scale, invalid_page_ranges, invalid_pdf_dimension, invalid_pdf_margin.
  • Batch and cache endpoints: missing_urls, batch_size_exceeded, missing_cache_key, invalid_date.

Authentication (401)

Code Meaning Retry?
missing_credentials No usable key or signature. An unknown or revoked key also returns this code today. No
invalid_api_key The key is not recognized No
revoked_key The key was revoked No
invalid_signature The signed URL does not match its signature No
expired_signature The signed URL is past its expires time No
invalid_expiration The signed URL's expires is not a Unix timestamp in seconds, or is more than 30 days ahead No

Authorization (403)

Code Meaning Retry?
insufficient_credits The account has no credits left No
workspace_credit_cap_reached The API key's workspace has used its monthly credit cap. The account may still have credits. No
email_not_verified The 10 trial credits are used, and the free credits are locked until the account email is verified No
feature_not_available The feature is not in your plan No

Target page (422)

Code Meaning Retry?
screenshot_failed Any other render failure. error.message holds the cause. Once
blocked The target was refused or blocked the renderer No
timeout The page did not load within wait.timeout Once, with a longer timeout
navigation_failed DNS, connection or HTTP failure before the page loaded Once
selector_not_found A required selector was not on the page No
ssl_error The target's TLS certificate is invalid No
content_check_failed A content check matched Once, if the page may still have been loading
console_error options.fail_on_error is set and the page logged a console error or threw (retryable: false) No

All of these return HTTP 422. blocked and console_error have retryable: false; the others have retryable: true. Match on error.code; error.message carries the browser's detail, for example blocked_url: host localhost is blocked.

Rate limits (429) and server errors (500)

Code Meaning Retry?
too_many_requests Per-minute limit reached. See rate limits. Yes, after Retry-After
concurrent_limit Too many renders running at once Yes, after a short wait
internal_error Unexpected server error Yes, with backoff
server_error The screenshot endpoint failed on our side Yes, with backoff
not_found (404) Unknown endpoint, cache key or batch ID No

Retry code for the SDKs

Base the decision on the HTTP status. It is the one field every SDK exposes the same way. Each listing reads the key from RS_API_KEY and lets non-retryable errors through. Tested on 2026-10-15 with renderscreenshot 1.1.0 for Node 22.18, the Python SDK 1.0.0 on Python 3.14 and the Ruby gem 1.0.0 on Ruby 4.0, against a local stub that replays the API's error bodies for 400, 422, 429 and 500.

Node.js

import { Client, TakeOptions, RenderScreenshotError } from 'renderscreenshot';
import { writeFile } from 'node:fs/promises';

const client = new Client(process.env.RS_API_KEY);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 429 and 5xx can succeed on a later attempt. 400, 401 and 403 cannot.
const isTransient = (status) => status === 429 || status >= 500;

async function takeWithRetry(options, attempts = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await client.take(options);
    } catch (err) {
      const transient = err instanceof RenderScreenshotError && isTransient(err.httpStatus);
      if (!transient || attempt === attempts) throw err;
      await sleep((err.retryAfter ?? 2 ** attempt) * 1000);
    }
  }
}

const image = await takeWithRetry(TakeOptions.url('https://example.com').preset('og_card'));
await writeFile('screenshot.png', image);

Python

import os
import time

from renderscreenshot import Client, RenderScreenshotError, TakeOptions

client = Client(os.environ['RS_API_KEY'])


def take_with_retry(options, attempts=3):
    for attempt in range(1, attempts + 1):
        try:
            return client.take(options)
        except RenderScreenshotError as e:
            # 429 and 5xx can succeed on a later attempt. 400, 401 and 403 cannot.
            transient = e.http_status == 429 or e.http_status >= 500
            if not transient or attempt == attempts:
                raise
            time.sleep(e.retry_after or 2 ** attempt)


image = take_with_retry(TakeOptions.url('https://example.com').preset('og_card'))
with open('screenshot.png', 'wb') as f:
    f.write(image)

Ruby

require 'renderscreenshot'

client = RenderScreenshot.client(ENV.fetch('RS_API_KEY'))
options = RenderScreenshot::TakeOptions.url('https://example.com').preset('og_card')

attempts = 0
begin
  attempts += 1
  image = client.take(options)
rescue RenderScreenshot::RateLimitError, RenderScreenshot::ServerError => e
  raise if attempts >= 3
  sleep(e.retry_after || 2**attempts)
  retry
rescue RenderScreenshot::Error => e
  # A 422 render failure may pass on a second try, unless the URL was refused.
  raise if e.http_status != 422 || e.message.start_with?('blocked_url:') || attempts >= 2
  retry
end

File.binwrite('screenshot.png', image)

The Ruby listing also retries a 422 once, because it can read the refusal prefix in e.message. In the stub test it made one extra request for a page that failed once, and none for http://localhost. For queues, idempotency and backoff with jitter, read error handling and retries.

See also

Last verified 2026-10-15 against the API source.

Related in this topic

Was this page helpful?