Get API key

API Reference

Complete reference for all Rendex rendering API endpoints.

Prefer an SDK? npm install @copperline/rendex or pip install rendex — see the Quick Start for examples.

Base URL

https://api.rendex.dev

POST /v1/screenshot

Capture a screenshot and return the image as binary data (PNG, JPEG, WebP, or PDF).

Free tier:image output carries a light “rendex.dev” watermark tiled across the whole render (a footer on PDFs) — your content stays readable underneath, and resolution and quality are otherwise identical to paid plans. Every paid plan — starting with Basic ($19/month) — is fully watermark-free; the JSON response sets metadata.watermarked: true when a render was marked.

Request Headers

HeaderRequiredValue
AuthorizationYesBearer YOUR_API_KEY
Content-TypeYesapplication/json

Request Body

ParameterTypeDefaultDescription
urlstringThe URL to capture. Provide exactly one of url, html, or markdown.
htmlstringRaw HTML to render directly (max 5 MB). Provide exactly one of url, html, or markdown.
markdownstringMarkdown to render (max 5 MB). Rendex converts it to a styled HTML document with sensible default typography, then captures it as an image or PDF. Provide exactly one of url, html, or markdown.
dataobjectMustache template data. When present, the html or markdown string is treated as a logic-less Mustache template and rendered against this object before capture. Valid only with html or markdown \u2014 returns 400 if combined with url. Capped at 256 KB serialized. Syntax: {{var}} (HTML-escaped), {{{var}}} (raw), {{#items}}\u2026{{/items}} (loop), {{^x}}\u2026{{/x}} (inverted), {{a.b}} (nested access).
format"png" | "jpeg" | "webp" | "pdf""png"Output format. Use "pdf" to generate a PDF document instead of an image.
widthnumber1280Viewport width (320–3840).
heightnumber800Viewport height (240–2160).
fullPagebooleanfalseCapture the full scrollable page.
darkModebooleanfalseEmulate dark color scheme.
blockAdsbooleantrueBlock ads and trackers.
qualitynumber80JPEG/WebP quality (1–100, default 80). Ignored for PNG and PDF.
delaynumber0Wait time in ms after page load (0–10000).
deviceScaleFactornumber2Device pixel ratio (1–3) for retina captures. Default produces 2× Retina output.
blockResourceTypesstring[]Resource types to block. Options: "font", "image", "media", "stylesheet", "other".
timeoutnumber30Navigation timeout in seconds (5–60).
waitUntil"load" | "domcontentloaded" | "networkidle0" | "networkidle2""networkidle2"When to consider navigation complete.
waitForSelectorstringCSS selector to wait for before capturing (max 500 chars).
bestAttemptbooleantrueIf true, returns a partial screenshot on timeout instead of failing.
selectorstringCSS selector of a specific element to capture instead of the full page (max 500 chars).
device"desktop" | "iphone_15" | "iphone_se" | "pixel_8" | "ipad" | "ipad_pro"Device preset that sets viewport, device scale factor, and user agent in one parameter. Explicit width, height, and userAgent still override the preset.
hideSelectorsstring[]CSS selectors to hide (set to display: none) before capture \u2014 popups, sticky nav, chat bubbles (max 50, each max 500 chars). Never errors when a selector matches nothing.
blockCookieBannersbooleanfalseHide common cookie/consent walls (OneTrust, Cookiebot, Quantcast, and similar) via a curated CSS list before capture.
resizeWidthnumberDownscale the output to this width in pixels (16\u20133840). Image formats only \u2014 ignored for PDF. Aspect ratio is preserved when only one of resizeWidth/resizeHeight is set. Never upscales.
resizeHeightnumberDownscale the output to this height in pixels (16\u20132160). Image formats only \u2014 ignored for PDF. Aspect ratio is preserved when only one of resizeWidth/resizeHeight is set. Never upscales.
cssstringCustom CSS to inject into the page before capture (max 50 KB). Useful for hiding elements, changing fonts, or overriding styles.
jsstringCustom JavaScript to execute on the page before capture (max 50 KB). Runs after navigation completes.
cookiesobject[]Array of cookie objects to set before navigation (max 50). Each cookie requires name and value. Optional fields: domain, path, httpOnly, secure, sameSite ("Strict" | "Lax" | "None"), expires (Unix timestamp).
headersRecord<string, string>Custom HTTP headers to send with the page request. Cannot override Host, Connection, Content-Length, or Transfer-Encoding.
userAgentstringCustom browser user agent string (max 512 chars).
pdfFormat"A4" | "Letter" | "Legal" | "Tabloid" | "A3""A4"PDF page size. Only applies when format is "pdf".
pdfLandscapebooleanfalseLandscape orientation for PDF output. Only applies when format is "pdf".
pdfPrintBackgroundbooleantrueInclude background colors and images in PDF output. Only applies when format is "pdf".
pdfScalenumber1Scale factor for PDF rendering (0.1\u20132). Only applies when format is "pdf".
pdfMarginobjectPDF page margins with top, right, bottom, left as CSS values (e.g. "1in", "20mm"). Defaults to zero margins. Only applies when format is "pdf".
geostringTwo-letter ISO country code (e.g. "US", "DE", "JP") for geo-targeted captures. The page is loaded from the specified country. Pro and Enterprise plans only.
geoCitystringCity name for more precise geo-targeting (max 100 chars). Requires geo to be set.
geoStatestringState or region for geo-targeting (max 100 chars). Requires geo to be set.
hostedbooleanfalseStore the result in R2 and return { url, expiresAt } — a signed, CDN-backed URL — instead of the image/PDF bytes. Drop the URL straight into an <img> or og:image.
extractbooleanfalseAlso return clean reader-mode extracted content from the same render pass. Only valid on POST /v1/screenshot/json — the binary endpoint rejects it. Shape is set by extractFormat.
extractFormat"markdown" | "json" | "html""markdown"Shape of the extracted content when extract is true.
asyncbooleanfalseProcess the capture asynchronously. Returns a jobId immediately instead of waiting for the result. Poll /v1/jobs/:jobId for status.
webhookUrlstringURL to receive a POST callback when an async capture completes. Must be a valid HTTPS URL. Requires async: true.
cacheTtlnumber86400How long to cache the result in seconds (3600–2592000, i.e. 1 hour to 30 days).

Rendering options, rendered

Each capture parameter above changes the pixels you get back. Here is what the most-used ones do \u2014 and every panel below is a live Rendex render of the same page, not a mockup:

The same page captured two ways: fullPage false shows only the viewport; fullPage true shows the entire scrollable page.
The same pricing page rendered in light mode and in dark mode by Rendex.
A full pricing page with one plan card highlighted on the left, and just that element captured on the right via a CSS selector.
A page rendered at 1280 by 800 on the left and the same image downscaled to 640 by 400 with resizeWidth on the right.

Response

Returns the image or PDF binary with appropriate Content-Type header. Screenshot metadata is returned in response headers:

  • x-screenshot-url — Captured URL
  • x-screenshot-width — Image width
  • x-screenshot-height — Image height
  • x-screenshot-size — File size in bytes
  • x-screenshot-captured-at — ISO timestamp
  • x-rendex-quality — Capture quality (full, degraded, or best_attempt)
  • x-rendex-wait-strategy — Actual wait strategy used
  • x-rendex-load-time-ms — Page load time in milliseconds
  • x-ratelimit-remaining — Remaining requests
  • x-ratelimit-reset — Rate limit reset time

GET /v1/screenshot

Capture a screenshot via query parameters and return the image as binary data. Enables <img> tag embedding and browser-based access.

Authentication

In addition to the Authorization and x-api-key headers, the GET endpoint also accepts a ?key= query parameter. This enables direct usage in <img src="..."> tags and browser address bars.

Query Parameters

Most POST body parameters are available as query strings, except blockResourceTypes, cookies, headers, html, hideSelectors, pdfMargin, async, and webhookUrl (complex types that don't serialize cleanly in URLs). The hosted, extract, extractFormat, and hideSelectors options are available on the POST endpoints only.

ParameterTypeDefaultDescription
urlstringRequired. The URL to capture.
format"png" | "jpeg" | "webp" | "pdf""png"Output format.
widthnumber1280Viewport width (320–3840).
heightnumber800Viewport height (240–2160).
fullPagebooleanfalseCapture the full scrollable page.
darkModebooleanfalseEmulate dark color scheme.
blockAdsbooleantrueBlock ads and trackers.
qualitynumber80JPEG/WebP quality (1–100, default 80). Ignored for PNG and PDF.
delaynumber0Wait time in ms after page load (0–10000).
deviceScaleFactornumber2Device pixel ratio (1–3) for retina captures. Default produces 2× Retina output.
timeoutnumber30Navigation timeout in seconds (5–60).
waitUntil"load" | "domcontentloaded" | "networkidle0" | "networkidle2""networkidle2"When to consider navigation complete.
waitForSelectorstringCSS selector to wait for before capturing (max 500 chars).
bestAttemptbooleantrueIf true, returns a partial screenshot on timeout instead of failing.
selectorstringCSS selector of a specific element to capture instead of the full page (max 500 chars).
device"desktop" | "iphone_15" | "iphone_se" | "pixel_8" | "ipad" | "ipad_pro"Device preset \u2014 sets viewport, device scale factor, and user agent in one parameter. Explicit width, height, and userAgent still override the preset.
blockCookieBannersbooleanfalseHide common cookie/consent walls (OneTrust, Cookiebot, Quantcast, and similar) via a curated CSS list before capture.
resizeWidthnumberDownscale the output to this width in pixels (16–3840). Image formats only — ignored for PDF. Aspect ratio is preserved when only one of resizeWidth/resizeHeight is set. Never upscales.
resizeHeightnumberDownscale the output to this height in pixels (16–2160). Image formats only — ignored for PDF. Aspect ratio is preserved when only one of resizeWidth/resizeHeight is set. Never upscales.
userAgentstringCustom browser user agent string (max 512 chars).
pdfFormat"A4" | "Letter" | "Legal" | "Tabloid" | "A3""A4"PDF page size. Only applies when format is "pdf".
pdfLandscapebooleanfalseLandscape orientation. Only applies when format is "pdf".
pdfPrintBackgroundbooleantrueInclude backgrounds in PDF. Only applies when format is "pdf".
pdfScalenumber1PDF scale factor (0.1\u20132). Only applies when format is "pdf".
geostringTwo-letter ISO country code for geo-targeted captures. Pro and Enterprise plans only.
geoCitystringCity for geo-targeting. Requires geo.
geoStatestringState/region for geo-targeting. Requires geo.

Example

https://api.rendex.dev/v1/screenshot?url=https://example.com&format=webp&key=YOUR_API_KEY

Response

Returns the same binary image with metadata headers as the POST endpoint. See the POST response section above for header details.


POST /v1/screenshot/json

Same parameters as /v1/screenshot, but returns the image as a base64-encoded JSON response. Set extract: true here to also receive clean reader-mode content in an extracted field alongside the image — available on every plan, including Free.

Response Body

{
  "success": true,
  "data": {
    "image": "base64-encoded-string",
    "contentType": "image/png",
    "url": "https://example.com",
    "width": 1280,
    "height": 800,
    "format": "png",
    "bytesSize": 145832,
    "capturedAt": "2026-03-26T12:00:00.000Z",
    "quality": "full",
    "waitStrategy": "networkidle2",
    "loadTimeMs": 2450
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-03-26T12:00:00.000Z",
    "usage": {
      "credits": 1,
      "remaining": 499
    }
  }
}

POST /v1/render/link

Mint a signed, self-contained hosted-render URL you can paste straight into <meta property="og:image"> or an <img src> — no storage or backend on your side. The body is the same capture parameters as POST /v1/screenshot (URL, HTML, or Markdown plus data, format, viewport, etc.), with an optional expiresIn. Available on every plan, including Free.

The returned link encodes the (signed, tamper-proof) render params, the owning account, and an expiry. GET /v1/render renders it on the first request, caches the result in R2 under a deterministic key, and serves the cached copy on every repeat — so a widely-shared link costs one render, not one per crawl. Credits are charged to the link owner on each cache-miss render only. async, webhookUrl, extract, and geo-targeting are not valid on a render link.

Request Body

ParameterTypeDefaultDescription
…capture paramsobjectAll POST /v1/screenshot capture parameters — one of url/html/markdown, format, width, height, data, etc.
expiresInnumber2592000Link validity in seconds (3600–2592000). Defaults to cacheTtl or 30 days.

Example Request

curl -X POST https://api.rendex.dev/v1/render/link \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<html><body><h1>{{title}}</h1></body></html>",
    "data": { "title": "Launch Day" },
    "format": "png",
    "width": 1200,
    "height": 630,
    "expiresIn": 2592000
  }'

Response

{
  "success": true,
  "data": {
    "url": "https://api.rendex.dev/v1/render?p=...&uid=...&exp=1714003200&sig=...",
    "expiresAt": "2026-04-25T00:00:00.000Z",
    "format": "png",
    "cacheTtl": 2592000
  }
}

Paste data.url into your page's og:image meta tag. Returns 400 if combined with unsupported options (async, webhookUrl, extract, or geo-targeting).


GET /v1/render

Serves a render link minted by POST /v1/render/link. Public and signature-authenticated— the HMAC signature in the URL is the auth boundary, so no API key is required. Renders on the first hit and returns the image/PDF bytes; subsequent hits serve the cached copy. You normally don't call this directly — embed the URL returned by POST /v1/render/link.

Query Parameters

All four parameters are included automatically in the minted URL — you do not construct them yourself.

ParameterTypeDescription
pstringSigned render params token.
uidstringOwning account ID.
expnumberExpiry (Unix seconds).
sigstringHMAC signature over the params.

Response

Returns the rendered image or PDF binary. The x-rendex-cache response header is hit or miss. An expired or invalid signature returns 403 FORBIDDEN.


POST /v1/extract

Turn a single URL into clean reader-mode content — Markdown, JSON, or article HTML — pulled from the fully-rendered page (so it works on JavaScript-heavy sites and SPAs that fetch-only readers miss). Strips nav, ads, and boilerplate. Text only — for image plus text in one call, use POST /v1/screenshot/json with extract: true. Available on every plan, including Free.

Request Body

ParameterTypeDefaultDescription
urlstringRequired. The webpage URL to extract readable content from.
extractFormat"markdown" | "json" | "html""markdown"Output shape — markdown (LLM-friendly prose), json (structured fields), or html (cleaned reader-mode HTML).
waitUntil"load" | "domcontentloaded" | "networkidle0" | "networkidle2""networkidle2"When to consider navigation complete.
timeoutnumber30Navigation timeout in seconds (5–60).
device"desktop" | "iphone_15" | "iphone_se" | "pixel_8" | "ipad" | "ipad_pro"Device preset — extract the mobile or tablet version of a page.
blockCookieBannersbooleanHide common cookie/consent walls before extraction.
hideSelectorsstring[]CSS selectors to hide (e.g. overlays, popups) before extraction (max 50).

Example Request

curl -X POST https://api.rendex.dev/v1/extract \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/blog/post",
    "extractFormat": "markdown"
  }'

Response

{
  "success": true,
  "data": {
    "url": "https://example.com/blog/post",
    "format": "markdown",
    "content": "# Article title\n\nClean reader-mode prose...",
    "title": "Article title",
    "byline": "Jane Doe",
    "excerpt": "A short summary of the article.",
    "siteName": "Example Blog",
    "length": 4820,
    "loadTimeMs": 2310
  }
}

Returns url, format, content, title, byline, excerpt, siteName, length, and loadTimeMs. Returns 422 EXTRACTION_FAILED when no article-like content is found.


POST /v1/artifact

Turn Markdown or HTML plus a small branding theme into a branded PDF, a PNG, and a hosted share page in one call. Built for AI agents that produce a finished report and need downloadable, shareable URLs. Pass the document body in content (Markdown by default, or set inputFormat: "html"), an optional branding theme, and an optional pageSetup. Choose the output formats: pdf, png, or both. Each requested format charges 1 credit from the same shared pool, using the same rdx_ key; the charge is refunded if a render fails. Available on every plan; the Free tier includes 100 renders per month.

Request Body

ParameterTypeDefaultDescription
contentstringRequired. The Markdown or HTML body to render (up to ~4MB).
inputFormat"markdown" | "html""markdown"How to interpret content. markdown is converted to styled HTML; html is used as a body fragment.
formats("pdf" | "png")[]["pdf", "png"]Which artifact formats to produce (1 or 2). Each format charges 1 credit.
brandingobjectOptional theme with logo (http(s) URL), accentColor, font, header, and footer.
pageSetupobjectOptional paper and viewport setup with size (A4, Letter, Legal, Tabloid, A3), orientation, margin, scale, width, height, and fullPage.
dataobjectOptional Mustache data applied to content (plus the branding fields) before conversion.
expiresInnumber86400Seconds until the hosted URLs expire (3600–2592000). Default 24h.

Example Request

curl -X POST https://api.rendex.dev/v1/artifact \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "# Weekly report\n\n- p95 down **12%**\n- 2 features shipped",
    "formats": ["pdf", "png"],
    "branding": { "header": "Acme Inc.", "accentColor": "#EA580C" },
    "pageSetup": { "size": "A4" }
  }'

Response

{
  "success": true,
  "data": {
    "pdfUrl": "https://api.rendex.dev/v1/images/usr_123/8f1c....pdf?expires=1750896000&sig=...",
    "pngUrl": "https://api.rendex.dev/v1/images/usr_123/2b7e....png?expires=1750896000&sig=...",
    "shareUrl": "https://api.rendex.dev/v1/images/usr_123/a90d....html?expires=1750896000&sig=...",
    "expiresAt": "2026-06-25T00:00:00.000Z"
  }
}

Returns short-lived signed URLs: pdfUrl and/or pngUrl (one per requested format), a shareUrl to a hosted preview page, and expiresAt. Returns a 400 VALIDATION_ERROR for an invalid body.


POST /v1/screenshot/batch

Submit multiple URLs for asynchronous capture in a single request. Returns immediately with a batch ID and individual job IDs that you can poll for results. Each URL in the batch counts as one credit.

Request Headers

HeaderRequiredValue
AuthorizationYesBearer YOUR_API_KEY
Content-TypeYesapplication/json

Request Body

ParameterTypeDefaultDescription
urlsstring[]Required. Array of URLs to capture (1\u2013500). Batch is a paid-plan feature; the size cap depends on your plan: Basic (10), Starter (25), Pro (100), Enterprise (500).
defaultsobject{}Shared capture settings applied to every URL — viewport, format, full-page, dark mode, wait strategy, quality, delay, CSS/JS injection, user agent, geo-targeting, and PDF page options. The per-job-only parameters url, html, async, webhookUrl, cookies, headers, selector, waitForSelector, blockResourceTypes, and pdfMargin are not accepted in batch defaults.
webhookUrlstringHTTPS URL to receive a POST callback when the entire batch completes.
cacheTtlnumber86400How long to cache results in seconds (3600–2592000).

Example Request

{
  "urls": [
    "https://example.com",
    "https://example.org",
    "https://example.net"
  ],
  "defaults": {
    "format": "png",
    "width": 1280,
    "height": 800,
    "fullPage": false,
    "darkMode": true
  },
  "webhookUrl": "https://your-app.com/webhook/batch-done",
  "cacheTtl": 86400
}

Response (202 Accepted)

{
  "success": true,
  "data": {
    "batchId": "b8f3e1a2-...",
    "totalJobs": 3,
    "jobs": [
      { "jobId": "j1a2b3c4-...", "url": "https://example.com", "status": "queued" },
      { "jobId": "j5d6e7f8-...", "url": "https://example.org", "status": "queued" },
      { "jobId": "j9a0b1c2-...", "url": "https://example.net", "status": "queued" }
    ]
  },
  "meta": {
    "requestId": "req_xyz789",
    "timestamp": "2026-03-26T12:00:00.000Z",
    "usage": {
      "credits": 3,
      "remaining": 497
    }
  }
}

GET /v1/jobs/:jobId

Poll the status of an asynchronous capture job. Use this after submitting a request with async: true or after creating a batch.

Path Parameters

ParameterTypeDescription
jobIdstring (UUID)Required. The job ID returned from an async capture or batch request.

Response

{
  "success": true,
  "data": {
    "jobId": "j1a2b3c4-...",
    "status": "completed",
    "resultUrl": "https://api.rendex.dev/v1/images/user123/j1a2b3c4.png?expires=1711540800&sig=...",
    "error": null,
    "createdAt": "2026-03-26T12:00:00.000Z",
    "completedAt": "2026-03-26T12:00:05.000Z"
  },
  "meta": {
    "requestId": "req_poll456",
    "timestamp": "2026-03-26T12:00:06.000Z"
  }
}

Job Statuses

StatusDescription
queuedJob is waiting to be processed.
processingJob is currently being captured.
completedCapture succeeded. The resultUrl field contains a signed URL to download the image.
failedCapture failed. The error field contains the reason.

GET /v1/batches/:batchId

Poll the status of a batch and all its individual jobs.

Path Parameters

ParameterTypeDescription
batchIdstring (UUID)Required. The batch ID returned from POST /v1/screenshot/batch.

Response

{
  "success": true,
  "data": {
    "batchId": "b8f3e1a2-...",
    "status": "completed",
    "totalJobs": 3,
    "completedJobs": 3,
    "failedJobs": 0,
    "createdAt": "2026-03-26T12:00:00.000Z",
    "completedAt": "2026-03-26T12:00:12.000Z",
    "jobs": [
      {
        "jobId": "j1a2b3c4-...",
        "status": "completed",
        "resultUrl": "https://api.rendex.dev/v1/images/...",
        "error": null,
        "createdAt": "2026-03-26T12:00:00.000Z",
        "completedAt": "2026-03-26T12:00:05.000Z"
      }
    ]
  },
  "meta": {
    "requestId": "req_batch789",
    "timestamp": "2026-03-26T12:00:13.000Z"
  }
}

The response includes a summary of the batch (totalJobs, completedJobs, failedJobs) and an array of all individual job statuses with their result URLs.


GET /v1/images/*

Retrieve a captured image or PDF using a signed URL. These URLs are returned in the resultUrl field of completed async jobs and batches.

No API key required. Image URLs are pre-signed with an expiration timestamp. Simply follow the resultUrlfrom a completed job response — no additional authentication is needed.

Query Parameters

ParameterTypeDescription
expiresstringUnix timestamp when the URL expires. Included automatically in signed URLs.
sigstringSignature token for URL verification. Included automatically in signed URLs.

Response

Returns the image or PDF binary with the appropriate Content-Type header and cache headers based on the remaining URL lifetime.

Error Codes

StatusCodeDescription
400INVALID_URLMissing or malformed URL parameters.
403FORBIDDENSignature is invalid or the URL has expired.
404NOT_FOUNDImage not found or has been deleted.

GET /health

Returns a simple health check.

{
  "status": "ok",
  "product": "rendex",
  "version": "1.4.0",
  "timestamp": "2026-03-26T12:00:00.000Z"
}

GET /v1/account

Read your plan and this month's usage — used, limit, remaining, and the reset date — plus the per-minute rate limit and a recommended upgrade link. Read-only and free; it never spends a credit. Handy for showing remaining credits, gating on plan, or surfacing an upgrade prompt in an agent or dashboard.

GET /v1/usage is an alias for this endpoint — both return the same summary, so use whichever name you reach for.

Response

{
  "success": true,
  "data": {
    "plan": "free",
    "usage": {
      "used": 50,
      "limit": 100,
      "remaining": 50,
      "unlimited": false,
      "resetsAt": "2026-07-01T00:00:00.000Z"
    },
    "rateLimitPerMinute": 10,
    "upgrade": {
      "recommendedPlan": "starter",
      "recommendedPlanCredits": 10000,
      "upgradeUrl": "https://rendex.dev/pricing",
      "manageBillingUrl": "https://rendex.dev/dashboard/billing"
    }
  }
}

upgrade is null when you are already on the top plan; on Enterprise, usage.unlimited is true and the numeric fields may be null.


Webhooks

When you provide a webhookUrl on an async or batch request, Rendex sends a signed POST callback when the job completes or fails. Batch requests receive a single batch.completed webhook when all jobs finish.

Event Types

EventTriggerKey Fields
job.completedAn async screenshot finished successfully.jobId, resultUrl, metadata
job.failedAn async screenshot failed after all retries.jobId, error
batch.completedAll jobs in a batch finished (success, partial, or failed).batchId, totalJobs, completedJobs, failedJobs, jobs[]

Payload — job.completed

{
  "event": "job.completed",
  "jobId": "j1a2b3c4-...",
  "status": "completed",
  "resultUrl": "https://api.rendex.dev/v1/images/...",
  "metadata": {
    "url": "https://example.com",
    "format": "png",
    "width": 1280,
    "height": 800,
    "loadTimeMs": 2450
  },
  "completedAt": "2026-03-26T12:00:05.000Z"
}

Payload — job.failed

{
  "event": "job.failed",
  "jobId": "j5d6e7f8-...",
  "status": "failed",
  "error": "The page took too long to load. Try increasing the timeout or using bestAttempt=true.",
  "completedAt": "2026-03-26T12:00:30.000Z"
}

Payload — batch.completed

{
  "event": "batch.completed",
  "batchId": "b8f3e1a2-...",
  "status": "completed",
  "totalJobs": 3,
  "completedJobs": 2,
  "failedJobs": 1,
  "completedAt": "2026-03-26T12:00:12.000Z",
  "jobs": [
    { "jobId": "j1a2b3c4-...", "status": "completed", "resultUrl": "https://..." },
    { "jobId": "j5d6e7f8-...", "status": "completed", "resultUrl": "https://..." },
    { "jobId": "j9a0b1c2-...", "status": "failed", "error": "Could not reach the target URL." }
  ]
}

Headers

Every webhook request includes these headers:

HeaderDescription
x-rendex-signatureHMAC-SHA256 hex digest for payload verification.
x-rendex-timestampUnix timestamp (seconds) when the webhook was signed.
x-rendex-eventEvent type: job.completed, job.failed, or batch.completed.
x-rendex-delivery-idUnique ID per delivery attempt. Same across retries — use for idempotency.
User-AgentRendex-Webhook/1.0

Signature Verification

Webhooks are signed with HMAC-SHA256 using the format timestamp.body. Always verify signatures to confirm the webhook came from Rendex. Reject timestamps older than 5 minutes to prevent replay attacks.

Node.js / TypeScript

import crypto from "crypto";

function verifyWebhook(body: string, headers: Headers, secret: string): boolean {
  const signature = headers.get("x-rendex-signature");
  const timestamp = headers.get("x-rendex-timestamp");
  if (!signature || !timestamp) return false;

  // Reject timestamps older than 5 minutes to prevent replay attacks
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Python

import hmac, hashlib, time

def verify_webhook(body: bytes, headers: dict, secret: str) -> bool:
    signature = headers.get("x-rendex-signature")
    timestamp = headers.get("x-rendex-timestamp")
    if not signature or not timestamp:
        return False

    # Reject timestamps older than 5 minutes
    if time.time() - int(timestamp) > 300:
        return False

    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{body.decode()}".encode(),
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(signature, expected)
Signing secret: Your webhook signing secret is the same WEBHOOK_SIGNING_SECRET configured in your Rendex deployment. Contact support if you need to rotate it.

Retry Behavior

AttemptDelayBehavior
1stImmediateFirst delivery attempt.
2nd~5 secondsExponential backoff with jitter.
3rd~15 secondsSecond retry.
4th~45 secondsFinal retry (capped at 120s).

4xx responses (client errors) are not retried — fix your endpoint and re-trigger the job. 5xx responses and timeouts are retried up to 3 times. Webhook delivery times out after 10 seconds — your endpoint must respond within that window.

Use the x-rendex-delivery-id header for idempotency. The same ID is sent across all retry attempts for a given delivery, so you can safely deduplicate.


Async Jobs & Limits

Job Lifecycle

When you set async: true or submit a batch, each capture goes through this state machine:

queued → processing → completed
                           ↘ failed
StatusMeaning
queuedJob is in the queue waiting for a worker.
processingA worker has picked up the job and is capturing the screenshot.
completedCapture succeeded. resultUrl contains the signed image URL.
failedCapture failed. error contains a safe, user-facing message.

Transitions are guarded — a job can only move from queued to processing, and from processing to completed or failed. This prevents duplicate processing.

Polling is Free

GET /v1/jobs/:jobId and GET /v1/batches/:batchId do not consume credits. Poll as often as you need — you are only billed for actual screenshot captures. We recommend polling every 2–5 seconds.

Concurrent Job Limits

Each plan has a maximum number of simultaneous active jobs (queued + processing). Exceeding this returns a 429 error.

PlanMax Concurrent Jobs
Free10
Starter50
Pro200
Enterprise1,000

Batch Size Limits

PlanMax URLs per Batch
Free— (Starter+)
Starter25
Pro100
Enterprise500

Batch requests are billed per URL, not per request. A 10-URL batch costs 10 credits.

Error Messages

Failed job errors are sanitized for security — raw internal errors are never exposed. Common messages:

MessageCause
The page took too long to load.Target page didn't load within the timeout. Increase timeout or enable bestAttempt.
The specified element was not found.CSS selector didn't match any element on the page.
Invalid cookie domain.Cookie domain doesn't match the target URL's domain.
Could not reach the target URL.DNS failure, connection refused, or the URL is behind a firewall.
Failed to store capture result.Storage error (R2 or custom S3). Retry or check storage configuration.
Screenshot capture failed.Generic error. Check your parameters and try again.

Capture Behavior

Best Attempt Fallback Chain

When bestAttempt is true(the default), Rendex tries progressively less strict wait strategies if the page doesn't settle within the timeout:

networkidle0 → networkidle2 → load → domcontentloaded

The response header x-rendex-quality tells you what happened:

ValueMeaning
fullPage loaded with your chosen wait strategy — best quality.
degradedPage loaded with a less strict strategy. Some content may be missing.
best_attemptAll strategies timed out. Screenshot was captured anyway — expect incomplete content.

Full-Page Capture & Auto-Scroll

When fullPage is true, Rendex scrolls the page incrementally before capture. This triggers lazy-loaded images, scroll-based animations (Framer Motion, AOS, GSAP), and deferred content. After scrolling completes, an 800ms settlement wait ensures content finishes rendering.

Full-page captures have a maximum height of 16,384 pixels. Pages taller than this are truncated and the response includes an x-rendex-truncated: true header.

Response Headers

Successful screenshot responses include metadata in headers. Some headers are conditional — they only appear when relevant.

HeaderAlways?Description
x-screenshot-urlYesThe URL that was captured.
x-screenshot-widthYesViewport width in pixels.
x-screenshot-heightYesViewport height in pixels.
x-screenshot-sizeYesImage file size in bytes.
x-screenshot-captured-atYesISO 8601 timestamp of capture.
x-rendex-formatYesOutput format: png, jpeg, webp, or pdf.
x-rendex-qualityYesfull, degraded, or best_attempt.
x-rendex-wait-strategyYesThe wait strategy that succeeded.
x-rendex-load-time-msYesTotal capture time in milliseconds.
x-rendex-truncatedNoPresent and true when a full-page capture exceeded 16,384px.
x-rendex-auto-scrolledNoPresent and true when auto-scroll was used.
x-rendex-rendering-engineNoPresent for geo captures. Value: geo-proxy.
x-rendex-geo-countryNoPresent for geo captures. The ISO country code used.

Caching

Synchronous image responses (PNG, JPEG, WebP) include cache headers: Cache-Control: public, max-age=3600, s-maxage=3600 (1 hour). PDF responses are not cached — they always generate fresh content.

Async job result URLs (signed URLs) expire based on the cacheTtl parameter (default: 24 hours, range: 1 hour to 30 days). Once expired, the URL returns 403 FORBIDDEN.

URL Validation & SSRF Protection

All target URLs and webhook URLs are validated before processing. The following are blocked to prevent server-side request forgery:

  • Private network ranges: 10.x.x.x, 172.16-31.x.x, 192.168.x.x
  • Loopback addresses: 127.0.0.1, localhost, ::1
  • Link-local: 169.254.x.x
  • Metadata endpoints: 169.254.169.254
  • Non-HTTP(S) protocols

If a blocked URL is submitted, the API returns 400 VALIDATION_ERROR.


PDF Output

Set format: "pdf" to render any page, raw HTML, or Markdown as a PDF document. PDF output is available on every plan, including Free.

PDF Parameters

ParameterTypeDefaultDescription
pdfFormat"A4" | "Letter" | "Legal" | "Tabloid" | "A3""A4"Page size. Letter is standard US (8.5" × 11"), A4 is international (210mm × 297mm).
pdfLandscapebooleanfalseLandscape orientation. Good for wide dashboards and comparison tables.
pdfPrintBackgroundbooleantrueInclude background colors, gradients, and images. Set to false for printer-friendly output.
pdfScalenumber1Scale factor (0.1–2). Use 0.8 to fit wide content, or 1.2 to enlarge text.
pdfMarginobjectZero marginsPage margins with top, right, bottom, left. Accepts CSS values: "1in", "20mm", "2cm", "72px".

Example — PDF from URL

curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/invoice/123",
    "format": "pdf",
    "pdfFormat": "Letter",
    "pdfLandscape": false,
    "pdfPrintBackground": true,
    "pdfScale": 1,
    "pdfMargin": {
      "top": "0.5in",
      "right": "0.5in",
      "bottom": "0.75in",
      "left": "0.5in"
    }
  }' -o invoice.pdf

Example — PDF from Raw HTML

Use html instead of url to render HTML directly — perfect for invoices, receipts, reports, and social cards. Max HTML size is 5 MB.

curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<html><body><h1>Invoice #123</h1><p>Amount: $49.00</p></body></html>",
    "format": "pdf",
    "pdfFormat": "A4",
    "pdfMargin": { "top": "1in", "right": "1in", "bottom": "1in", "left": "1in" }
  }' -o invoice.pdf

Example — PDF from Markdown

Pass markdown and Rendex converts it to a styled HTML document with sensible default typography before rendering — no templating required. Ideal for AI agents (which emit Markdown natively), READMEs, and docs. Works for image formats too, not just PDF.

curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "markdown": "# Release Notes\n\n## v1.2.0\n\n- Markdown input support\n- Sensible default typography\n\nGreat for AI agents, READMEs, and docs.",
    "format": "pdf",
    "pdfFormat": "A4"
  }' -o notes.pdf

Example — Mustache Data Templating

Supply a data object alongside html or markdown and Rendex treats the source string as a logic-less Mustache template, filling in {{placeholders}} before render. Generate invoices, reports, or certificates from a single template — just swap the data payload per request. Not valid with url (returns 400).

curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer rdx_..." -H "Content-Type: application/json" \
  -d '{
    "markdown": "# Invoice {{number}}\n\nTotal: **{{total}}**\n\n{{#items}}- {{name}}: {{price}}\n{{/items}}",
    "data": { "number": "A-100", "total": "$2,400", "items": [{"name":"Design","price":"$2,000"},{"name":"Hosting","price":"$400"}] },
    "format": "pdf"
  }' -o invoice.pdf

PDF-Specific Behavior

  • PDF responses include a Content-Disposition: inline; filename="rendex-capture.pdf" header.
  • PDFs are never cached — no Cache-Control header is sent.
  • Set fullPage: true to scroll the page before rendering so lazy-loaded images and scroll-triggered content are present in the PDF. Pagination itself is always handled automatically by the browser's print layout, regardless of this flag.
  • quality has no effect on PDFs (it only applies to JPEG and WebP).
  • PDFs support CSS/JS injection, cookies, headers, and custom viewports — but viewport width affects the page layout, not the PDF page size.

Geo-Targeted Captures

Capture screenshots as seen from a specific country, state, or city. Geo-targeting routes through a proxy network instead of Cloudflare Browser Rendering. Available on Pro and Enterprise plans.

Example

curl -X POST https://api.rendex.dev/v1/screenshot \
  -H "Authorization: Bearer rdx_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com",
    "geo": "DE",
    "geoState": "Bavaria",
    "geoCity": "Munich"
  }' -o screenshot.png

Supported Countries (59)

Pass a 2-letter ISO 3166-1 alpha-2 code. All countries below support city and state targeting.

AF AfghanistanAL AlbaniaDZ AlgeriaAR ArgentinaAU AustraliaAT AustriaBD BangladeshBE BelgiumBR BrazilBG BulgariaCA CanadaCL ChileCN ChinaCO ColombiaHR CroatiaCZ Czech RepublicDK DenmarkEG EgyptEE EstoniaFI FinlandFR FranceDE GermanyGR GreeceHK Hong KongHU HungaryIN IndiaID IndonesiaIE IrelandIL IsraelIT ItalyJP JapanKE KenyaKR South KoreaLV LatviaLT LithuaniaMY MalaysiaMX MexicoNL NetherlandsNZ New ZealandNG NigeriaNO NorwayPK PakistanPE PeruPH PhilippinesPL PolandPT PortugalRO RomaniaRU RussiaSA Saudi ArabiaSG SingaporeSK SlovakiaZA South AfricaES SpainSE SwedenCH SwitzerlandTW TaiwanTH ThailandTR TurkeyUA UkraineAE UAEGB United KingdomUS United StatesVN Vietnam

Geo Output Differences

Geo-targeted captures use a different rendering engine. This means some features work differently:

AspectStandard CaptureGeo Capture
Output formatPNG, JPEG, WebP, PDFPNG only
ViewportConfigurable (320–3840 × 240–2160)Fixed 1280 × 800
Rendering enginecf-browsergeo-proxy
Wait strategyConfigurable (networkidle0/2, load, etc.)Managed by proxy

Unsupported Parameters with Geo

These parameters are silently ignored or will return a 400 error if explicitly set (non-default values):

htmlcssjscookiesheadersuserAgentselectorwaitForSelectordarkModeblockAdsblockResourceTypesfullPagedeviceScaleFactorpdfFormatpdfLandscapepdfPrintBackgroundpdfMarginpdfScale

If you need these features with geo-targeting, capture with geo first, then re-process the URL with a standard capture.


Cookies & Headers

Inject cookies and custom HTTP headers to capture authenticated pages, set language preferences, or test A/B experiments.

Requires a paid plan (Basic and up). Cookie injection, custom request headers, and URL-embedded credentials (user:pass@host) are blocked on the Free tier — such requests return 403 PLAN_UPGRADE_REQUIRED. This is a liability control: any request that passes third-party credentials through Rendex is tied to a billing record.

Example

{
  "url": "https://app.example.com/dashboard",
  "cookies": [
    {
      "name": "session_id",
      "value": "abc123xyz",
      "domain": ".example.com",
      "path": "/",
      "httpOnly": true,
      "secure": true,
      "sameSite": "Lax"
    },
    {
      "name": "theme",
      "value": "dark"
    }
  ],
  "headers": {
    "Accept-Language": "en-US,en;q=0.9"
  }
}

Cookie Fields

FieldTypeRequiredConstraints
namestringYesMax 256 characters.
valuestringYesMax 4,096 characters.
domainstringNoMax 256 chars. Must match the target URL domain or a parent domain. Auto-derived from URL if omitted.
pathstringNoMax 256 chars. Defaults to "/".
httpOnlybooleanNoPrevents client-side JS from reading the cookie.
securebooleanNoOnly send over HTTPS.
sameSitestringNo"Strict", "Lax", or "None".
expiresnumberNoUnix timestamp for cookie expiration.

Maximum 50 cookies per request. If you omit domain, the target URL's hostname is used automatically. Domain validation prevents setting cookies for unrelated domains — the cookie domain must exactly match or be a parent of the target URL.

Custom Headers

Pass a headers object with string key-value pairs. Each value is limited to 4,096 characters. The following headers cannot be overridden for security:

  • Host
  • Connection
  • Content-Length
  • Transfer-Encoding

Common use cases: Accept-Language for locale testing, Authorization for authenticated pages, custom headers for feature flags.

Custom Storage (Bring Your Own S3)

Pro and Enterprise plans can route async capture results to your own S3-compatible bucket instead of Rendex's default storage. See the Custom Storage guide for setup instructions, supported providers (AWS S3, Cloudflare R2, Google Cloud Storage, Backblaze B2, MinIO, DigitalOcean Spaces), and IAM policy templates.


Error Responses

All errors return a JSON body with this structure:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters.",
    "details": [...]
  },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-03-26T12:00:00.000Z"
  }
}
StatusCodeDescription
202Accepted. Async job or batch queued successfully (not an error).
400VALIDATION_ERRORInvalid request parameters.
400CAPTURE_FAILEDScreenshot capture failed (e.g. selector not found).
401UNAUTHORIZEDMissing or invalid API key.
403FORBIDDENAPI key disabled or insufficient permissions.
403PLAN_UPGRADE_REQUIREDFeature requires a higher plan (e.g. geo-targeting on Free/Starter).
404NOT_FOUNDResource not found (job, batch, or image).
408TIMEOUTPage did not load within the configured timeout.
413PAYLOAD_TOO_LARGERendered output (after templating/markdown) or render-link URL exceeds the size cap.
422EXTRACTION_FAILEDNo article-like content found to extract (POST /v1/extract).
429RATE_LIMITEDRate limit exceeded. Check Retry-After header.
429QUEUE_LIMITToo many concurrent async jobs. Wait for jobs to complete or upgrade.
500INTERNAL_ERRORServer error. Retry after a brief delay.
Was this page helpful?