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
- Troubleshooting: blank images, cookie banners, login walls
- Rate limits: limits per plan and the
X-RateLimit-*headers - Signed URLs: fixing
invalid_signatureandexpired_signature - SDKs: error classes in all official SDKs
Last verified 2026-10-15 against the API source.