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 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 }'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 URLimport 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 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:
| Format | Content-Type | Notes |
|---|---|---|
png | image/png | Default. Lossless; supports transparency. |
jpeg | image/jpeg | Smaller files. Tune with imageQuality. |
webp | image/webp | Modern, small; supports transparency. |
pdf | application/pdf | Prints 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, default1280) andviewportHeight(100–4320, default1024) — the browser window size.fullPage(defaultfalse) — capture the entire scrollable height of the page instead of just the viewport. The returnedheightreflects the real rendered height.deviceScaleFactor(1–3, default1) — pixel density. Set2for 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.
device | Resolution | DPR |
|---|---|---|
iphone_15 | 393 × 852 | 3 |
iphone_se | 375 × 667 | 2 |
pixel_8 | 412 × 915 | 2.625 |
ipad | 820 × 1180 | 2 |
macbook | 1440 × 900 | 2 |
desktop | 1280 × 1024 | 1 |
{ "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(defaultload) — the navigation lifecycle event to wait for:load,domcontentloaded,networkidle0(no network connections for 500 ms), ornetworkidle2(≤ 2 connections for 500 ms).waitForSelector— wait until a specific CSS selector appears in the DOM.delay(0–30 seconds, default0) — a fixed pause after the page loads.timeout(1–90 seconds, default60) — how long to wait for navigation before the render fails.
{
"url": "https://example.com/dashboard",
"waitUntil": "networkidle0",
"waitForSelector": "#chart",
"delay": 1
}Appearance
darkMode— emulateprefers-color-scheme: darkso 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.
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/pngCaching
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:
- Webhooks — dOCR fires a
screenshot.completed(orscreenshot.failed) event to your configured endpoints. PasswebhookUrlto also deliver it to a one-off URL for this render. - Polling — call
GET /api/v1/screenshots/{id}untilstatusbecomesready(orfailed).
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 /screenshotsas 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.
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} />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
| Endpoint | Returns |
|---|---|
GET /api/v1/screenshots | Your most recent screenshots (up to 100). |
GET /api/v1/screenshots/{id} | A single screenshot — poll this for async status. |
GET /api/v1/captures | A 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
GET /api/v1/usage— your current credit usage (included/used/available).GET /api/v1/devices— the device presets available to thedeviceoption.
Option reference
| Option | Type | Default | Description |
|---|---|---|---|
url | string | — | URL to render. Exactly one of url/html. |
html | string | — | Raw HTML to render. Exactly one of url/html. |
format | png | jpeg | webp | pdf | png | Output format. |
fullPage | boolean | false | Capture the full scrollable page. |
viewportWidth | integer (100–3840) | 1280 | Viewport width in px. |
viewportHeight | integer (100–4320) | 1024 | Viewport height in px. |
device | preset | — | Device preset; overrides viewport + DPR. |
deviceScaleFactor | number (1–3) | 1 | Pixel density. |
imageQuality | integer (0–100) | 80 | JPEG/WebP quality. |
omitBackground | boolean | false | Transparent background. |
blockAds | boolean | false | Block ad networks. |
blockCookieBanners | boolean | false | Hide cookie banners. |
blockChats | boolean | false | Hide chat widgets. |
delay | integer (0–30) | 0 | Seconds to wait after load. |
waitUntil | load | domcontentloaded | networkidle0 | networkidle2 | load | Navigation event to wait for. |
waitForSelector | string | — | Wait for a CSS selector. |
timeout | integer (1–90) | 60 | Navigation timeout in seconds. |
darkMode | boolean | — | Emulate dark color scheme. |
cache | boolean | false | Reuse identical renders (0 credits on hit). |
cacheTtl | integer (14400–2592000) | — | Cache lifetime in seconds. |
async | boolean | false | Render in the background. |
webhookUrl | string | — | One-off URL for the completion event. |
store | boolean | true | Persist and return a hosted url. |