dOCR
Guides

Screenshots

Render any URL or HTML string to an image or PDF — sizing, devices, caching, async delivery, and signed URLs.

The Screenshot API renders a web page (url) or a raw HTML string (html) to a PNG, JPEG, WebP image, or a PDF. It runs on the same key, the same unified credits, and the same webhooks as the rest of dOCR.

This guide walks through the API end to end. For the exact request and response schema, see the API reference.

Render your first screenshot

Send a POST to /api/v1/screenshots with your API key and a url. By default the request is synchronous — dOCR renders the page and returns the finished record, including a hosted url you can download or embed.

curl
curl https://app.docr.dev/api/v1/screenshots \
  -H "Authorization: Bearer $DOCR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com", "format": "png", "fullPage": true }'
JavaScript (fetch)
const res = await fetch("https://app.docr.dev/api/v1/screenshots", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.DOCR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com", format: "png", fullPage: true }),
});
const { screenshot } = await res.json();
console.log(screenshot.url); // hosted image URL
Python (requests)
import os, requests

res = requests.post(
    "https://app.docr.dev/api/v1/screenshots",
    headers={"Authorization": f"Bearer {os.environ['DOCR_API_KEY']}"},
    json={"url": "https://example.com", "format": "png", "fullPage": True},
)
print(res.json()["screenshot"]["url"])

The response:

{
  "screenshot": {
    "id": "6a382443304f240b189f228a",
    "kind": "screenshot",
    "status": "ready",
    "format": "png",
    "url": "https://res.cloudinary.com/docr/image/upload/screenshot-6a382443.png",
    "width": 1280,
    "height": 3840,
    "fullPage": true,
    "cacheHit": false,
    "creditsUsed": 1,
    "error": null,
    "createdAt": "2026-06-25T17:50:00.000Z",
    "updatedAt": "2026-06-25T17:50:02.100Z"
  }
}

Provide exactly one of url or html. Sending both — or neither — returns 422.

Render HTML directly

Pass an html string instead of a url to render markup you already have — no hosting required. This is handy for receipts, certificates, social cards, and email previews.

curl
curl https://app.docr.dev/api/v1/screenshots \
  -H "Authorization: Bearer $DOCR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "html": "<h1 style=\"font-family:sans-serif\">Hello, dOCR</h1>", "format": "png" }'

Formats

Set format to one of:

FormatContent-TypeNotes
pngimage/pngDefault. Lossless; supports transparency.
jpegimage/jpegSmaller files. Tune with imageQuality.
webpimage/webpModern, small; supports transparency.
pdfapplication/pdfPrints the page with backgrounds. Not available on signed URLs.

imageQuality (0–100, default 80) applies to jpeg and webp and is ignored for png.

Sizing

By default dOCR renders a 1280×1024 viewport. Adjust it with:

  • viewportWidth (100–3840, default 1280) and viewportHeight (100–4320, default 1024) — the browser window size.
  • fullPage (default false) — capture the entire scrollable height of the page instead of just the viewport. The returned height reflects the real rendered height.
  • deviceScaleFactor (1–3, default 1) — pixel density. Set 2 for a retina-resolution image at twice the pixels.
{ "url": "https://example.com", "viewportWidth": 1440, "deviceScaleFactor": 2 }

Device presets

Instead of setting the viewport by hand, pass a device to emulate a common screen. A preset overrides viewportWidth, viewportHeight, and deviceScaleFactor.

deviceResolutionDPR
iphone_15393 × 8523
iphone_se375 × 6672
pixel_8412 × 9152.625
ipad820 × 11802
macbook1440 × 9002
desktop1280 × 10241
{ "url": "https://example.com", "device": "iphone_15" }

Waiting and timing

Pages that load content asynchronously may need a moment to settle before the capture. Combine these controls as needed:

  • waitUntil (default load) — the navigation lifecycle event to wait for: load, domcontentloaded, networkidle0 (no network connections for 500 ms), or networkidle2 (≤ 2 connections for 500 ms).
  • waitForSelector — wait until a specific CSS selector appears in the DOM.
  • delay (0–30 seconds, default 0) — a fixed pause after the page loads.
  • timeout (1–90 seconds, default 60) — how long to wait for navigation before the render fails.
{
  "url": "https://example.com/dashboard",
  "waitUntil": "networkidle0",
  "waitForSelector": "#chart",
  "delay": 1
}

Appearance

  • darkMode — emulate prefers-color-scheme: dark so sites that support it render their dark theme.
  • omitBackground — render a transparent background (PNG and WebP) instead of the page's default white.

Blocking distractions

Hide common page furniture before capturing:

  • blockAds — block known ad networks.
  • blockCookieBanners — hide cookie-consent dialogs.
  • blockChats — hide chat and support widgets.
{ "url": "https://example.com", "blockCookieBanners": true, "blockChats": true }

Storage vs. raw bytes

By default (store: true) dOCR persists the render and returns a hosted url in the screenshot record. Set store: false to skip storage and stream the raw image bytes straight back in the response body, with the matching Content-Type. This is ideal when you want to pipe the bytes somewhere yourself and don't need a hosted copy.

Stream raw bytes
const res = await fetch("https://app.docr.dev/api/v1/screenshots", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.DOCR_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com", store: false }),
});
const bytes = Buffer.from(await res.arrayBuffer()); // image/png

Caching

Rendering the same page repeatedly is wasteful. Set cache: true and dOCR hashes the rendering options into a cache key; an identical later request returns the stored render instantly. A cache hit sets cacheHit: true and costs 0 credits.

Use cacheTtl (4 hours–30 days, in seconds) to control how long a cached render stays valid. The cache key is derived only from options that affect the output — delivery options like async, webhookUrl, and store are ignored.

{ "url": "https://example.com", "cache": true, "cacheTtl": 86400 }

Asynchronous rendering

For slow pages, or when you'd rather not hold the connection open, set async: true. The request returns immediately with a 202 and a pending record. dOCR renders in the background and notifies you on completion.

{ "url": "https://example.com", "async": true, "webhookUrl": "https://api.yourapp.com/hooks/docr" }

You can receive the result two ways:

  1. Webhooks — dOCR fires a screenshot.completed (or screenshot.failed) event to your configured endpoints. Pass webhookUrl to also deliver it to a one-off URL for this render.
  2. Polling — call GET /api/v1/screenshots/{id} until status becomes ready (or failed).

The screenshot.completed payload carries the render's id, hosted url, and format:

{
  "event": "screenshot.completed",
  "data": {
    "screenshotId": "6a382443304f240b189f228a",
    "url": "https://res.cloudinary.com/docr/image/upload/screenshot-6a382443.png",
    "format": "png"
  },
  "timestamp": "2026-06-25T17:50:02.100Z"
}

Signed URLs

Sometimes you want an image you can drop straight into an <img src> or a Markdown file — no API key, no backend call. The signed endpoint GET /api/v1/take returns the rendered bytes directly and authenticates with a signature instead of a Bearer token.

A signed URL carries:

  • t — your organization id (the signing tenant).
  • signature — an HMAC-SHA256 of the canonical query string.
  • Any rendering option from POST /screenshots as a query parameter (url, format, fullPage, device, darkMode, cache, …). PDF is not supported here.

The canonical string is every parameter except signature, sorted by key and joined as key=value pairs with &, using the decoded values. Sign that string with your organization's signing secret, then URL-encode the parameters when you build the final link.

Generate signatures on your server. Your signing secret must never reach the browser — anyone who has it can render against your account's credits. Retrieve it from the dashboard under Developers → API Credentials, or programmatically from GET /api/signing-secret while signed in. The secret is never returned by the public, API-key-authed endpoints.

Sign a URL (Node.js)
import crypto from "node:crypto";

function signedScreenshotUrl(params: Record<string, string>, secret: string) {
  const canonical = Object.keys(params)
    .filter((k) => k !== "signature")
    .sort()
    .map((k) => `${k}=${params[k]}`)
    .join("&");

  const signature = crypto.createHmac("sha256", secret).update(canonical).digest("hex");

  const query = new URLSearchParams({ ...params, signature });
  return `https://app.docr.dev/api/v1/take?${query.toString()}`;
}

const url = signedScreenshotUrl(
  { t: "org_123", url: "https://example.com", format: "png", fullPage: "true" },
  process.env.DOCR_SIGNING_SECRET!,
);
// <img src={url} />
Sign a URL (Python)
import hmac, hashlib
from urllib.parse import urlencode

def signed_screenshot_url(params: dict, secret: str) -> str:
    canonical = "&".join(
        f"{k}={params[k]}" for k in sorted(params) if k != "signature"
    )
    signature = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
    return "https://app.docr.dev/api/v1/take?" + urlencode({**params, "signature": signature})

url = signed_screenshot_url(
    {"t": "org_123", "url": "https://example.com", "format": "png", "fullPage": "true"},
    DOCR_SIGNING_SECRET,
)

Signed responses include an X-docr-Cache header (hit or miss) and a one-hour Cache-Control, so browsers and CDNs cache the image. Combine with cache=true to also reuse the render server-side.

Listing and retrieving

EndpointReturns
GET /api/v1/screenshotsYour most recent screenshots (up to 100).
GET /api/v1/screenshots/{id}A single screenshot — poll this for async status.
GET /api/v1/capturesA unified list of screenshots and uploaded documents.
GET /api/v1/captures/{id}A single capture of either kind.

A capture is the umbrella over everything you bring into dOCR — both rendered screenshots and uploaded documents. Use the kind field (screenshot or upload) to tell them apart when listing.

Credits

Every successful render costs 1 credit from the same monthly allowance used by extraction. Cache hits and failed renders cost nothing. See Billing & limits for plan allowances and overage pricing.

Capture a region or element

Instead of the whole page you can capture just one element or a fixed region:

  • selector — a CSS selector; dOCR screenshots that single element.
  • clipX / clipY / clipWidth / clipHeight — a pixel rectangle (width and height must be set together).
{ "url": "https://example.com", "selector": "#hero" }
{ "url": "https://example.com", "clipX": 0, "clipY": 0, "clipWidth": 1200, "clipHeight": 630 }

Authenticated & customized pages

Render pages that need a session or specific request context:

  • cookies["session=abc; Domain=example.com; Secure"]
  • headers["X-Feature-Flag: on"], authorization, userAgent, timeZone

And reshape the page before capture: hideSelectors, styles (inject CSS), scripts (inject JS), click, hover, plus blockResources, blockTrackers, and blockRequests for faster, cleaner shots.

PDF options

When format is pdf: pdfLandscape, pdfPaperFormat (a4, letter, …), pdfPrintBackground, pdfMargin (or per-side pdfMarginTop/Right/Bottom/Left), and pdfFitOnePage.

Output format & size

Formats now include avif and tiff alongside png/jpeg/webp/pdf. Use imageWidth / imageHeight to resize the output into a thumbnail without re-rendering.

Metadata

Ask for page metadata alongside the render — returned in the screenshot's metadata object: metadataPageTitle, metadataOpenGraph, metadataIcon, metadataImageSize, metadataContent, metadataHttpStatusCode, metadataHttpHeaders.

{ "url": "https://example.com", "metadataOpenGraph": true, "metadataHttpStatusCode": true }

Render guards

Fail a render deterministically instead of capturing a bad page: failIfContentContains, failIfContentMissing, failIfRequestFailed, and ignoreHostErrors (capture 4xx/5xx pages anyway).

Usage & devices

Option reference

OptionTypeDefaultDescription
urlstringURL to render. Exactly one of url/html.
htmlstringRaw HTML to render. Exactly one of url/html.
formatpng | jpeg | webp | pdfpngOutput format.
fullPagebooleanfalseCapture the full scrollable page.
viewportWidthinteger (100–3840)1280Viewport width in px.
viewportHeightinteger (100–4320)1024Viewport height in px.
devicepresetDevice preset; overrides viewport + DPR.
deviceScaleFactornumber (1–3)1Pixel density.
imageQualityinteger (0–100)80JPEG/WebP quality.
omitBackgroundbooleanfalseTransparent background.
blockAdsbooleanfalseBlock ad networks.
blockCookieBannersbooleanfalseHide cookie banners.
blockChatsbooleanfalseHide chat widgets.
delayinteger (0–30)0Seconds to wait after load.
waitUntilload | domcontentloaded | networkidle0 | networkidle2loadNavigation event to wait for.
waitForSelectorstringWait for a CSS selector.
timeoutinteger (1–90)60Navigation timeout in seconds.
darkModebooleanEmulate dark color scheme.
cachebooleanfalseReuse identical renders (0 credits on hit).
cacheTtlinteger (14400–2592000)Cache lifetime in seconds.
asyncbooleanfalseRender in the background.
webhookUrlstringOne-off URL for the completion event.
storebooleantruePersist and return a hosted url.

Next steps

On this page