URL to PDF: how to snapshot a web page as a PDF via API
Pass { "source": { "type": "url", "url": "https://..." } } to Docweave's /api/v1/pdf endpoint and it loads the page in headless Chromium, waits for it to finish rendering, and returns a PDF snapshot — including whatever JavaScript, fonts, and CSS the live page needs. Use url when the page already exists and looks right in a browser; use html when you're generating a document from data you already have, since it's faster and has no network/auth surface to worry about.
What the url source type actually does
Docweave supports three input shapes for POST /api/v1/pdf: raw html, a templateId plus JSON data, or a url. With url, the render worker opens a real headless Chromium instance (via Playwright), navigates to the address you gave it, waits for the network to go idle and any custom waitFor condition to resolve, then prints the resulting DOM to PDF. It is, in effect, "Print to PDF" from a real browser, done server-side and returned as bytes over the API instead of a save dialog.
This matters because a lot of pages don't look right if you just fetch their HTML and hand it to a naive PDF converter — client-side rendered dashboards, pages that fetch data after load, pages gated behind a session cookie, print stylesheets that only apply in a real browser. The headless Chromium PDF approach handles all of that because it's a browser, not a text-to-PDF templating hack.
When to use url vs html
The honest tradeoff: url is slower and has more failure modes (DNS, TLS, auth, timeouts, a slow third-party script) because it depends on a live network fetch. html is faster and fully deterministic because you're handing over the exact markup to render — no network round trip, no SSRF surface, no "it worked yesterday but the page changed today."
- Use
urlfor: public marketing pages, existing dashboards or reports you want archived as-is, any page where the source of truth is "what a browser renders at this address" rather than data you control. - Use
htmlfor: invoices, receipts, certificates, reports, and quotes generated from your own data — anything you can template server-side. It's the default recommendation in our HTML-to-PDF API guide. - Use a stored template (
templateId+ JSON) when the same layout runs repeatedly with different data — see the template docs for the compile step.
REST example: snapshot a live page
Always send Accept: application/pdf and save the raw response bytes — the API streams the PDF directly rather than wrapping it in JSON:
curl -X POST https://docweave.dev/api/v1/pdf \
-H "Authorization: Bearer dw_live_your_key" \
-H "Content-Type: application/json" \
-H "Accept: application/pdf" \
-d '{
"source": {
"type": "url",
"url": "https://example.com/reports/q2-2026"
},
"idempotencyKey": "q2-2026-snapshot-v1"
}' \
--output q2-2026-snapshot.pdfThe same call from Node, saving the response buffer to disk (see the full Node.js walkthrough for retries and error handling):
import { writeFileSync } from "node:fs";
const res = await fetch("https://docweave.dev/api/v1/pdf", {
method: "POST",
headers: {
Authorization: "Bearer dw_live_your_key",
"Content-Type": "application/json",
Accept: "application/pdf",
},
body: JSON.stringify({
source: { type: "url", url: "https://example.com/reports/q2-2026" },
idempotencyKey: "q2-2026-snapshot-v1",
}),
});
if (!res.ok) {
throw new Error(`Render failed: ${res.status} ${await res.text()}`);
}
const buf = Buffer.from(await res.arrayBuffer());
writeFileSync("q2-2026-snapshot.pdf", buf);MCP example
If you're wiring this into an agent rather than calling REST directly, the open-source npx @docweave/mcp server exposes the identical render path as a single tool — this is the path we describe on the AI-agent PDF page:
await generate_pdf({
source: {
type: "url",
url: "https://example.com/reports/q2-2026",
},
outputPath: "./q2-2026-snapshot.pdf",
});SSRF and auth caveats — read this before you ship
Rendering a URL means your server is fetching an address on behalf of the caller. That's a textbook SSRF primitive if the target isn't validated: a malicious url value pointed at http://169.254.169.254/ or an internal 10.0.0.0/8 address could otherwise be used to probe or exfiltrate infrastructure metadata. Docweave's renderer validates and blocks private/link-local/loopback targets and non-http(s) schemes before Chromium ever navigates to them. If you're building your own url-to-PDF pipeline instead of using an API that handles this, do not skip this step — see the HTML-to-PDF API glossary entry for what a proper guard looks like.
- Pages behind login won't render correctly unless you pass session cookies or an auth header explicitly — headless Chromium has no browser profile to inherit your cookies from.
- Pages with client-side redirects, infinite scroll, or lazy-loaded images may need an explicit
waitForselector or delay; otherwise you can capture a page mid-load. - Rate limits and slow third-party scripts (analytics, chat widgets) on the target page directly extend your render latency — url renders are almost always slower than html renders for this reason.
- For public pages you don't control, treat the result as a point-in-time snapshot, not a live mirror — content changes won't retroactively update a PDF you already generated.
A URL render is only as reliable as the page it's rendering. If you control the data, template it as HTML — it's faster, has no network dependency, and can't fail because a third-party script timed out.
Where url rendering fits in the bigger picture
Most teams end up using url for a narrow set of cases — archiving a dashboard state, capturing a public page for compliance records, generating a screenshot-style PDF of something they don't have the underlying data for — and html or templates for everything transactional (invoices, contracts, quotes). If you're comparing this against other providers before committing, our best PDF generation API roundup and the PDFShift comparison cover how url-rendering support varies across vendors. Full parameter reference, including waitFor, viewport size, and PDF options like margins and headers, is in the API docs.
FAQ
Can Docweave render pages that require login?
Yes, but you need to pass authentication explicitly — either a cookie header or a bearer token the target page accepts — since headless Chromium starts with no session state. There's no way to "log in as the user" automatically; you supply the credentials the render should use.
Is url rendering slower than html rendering?
Generally yes. A url render has to do a real network fetch of the target page plus every asset it loads (fonts, images, scripts), while an html render works entirely from the payload you sent. If speed matters and you control the source data, prefer html or a stored template.
What stops someone from using the url field to probe my internal network?
Docweave's renderer validates the target before navigating: private IP ranges, loopback, link-local addresses, and non-http(s) schemes are rejected outright. This is a standard SSRF guard and it's launch-blocking for any API that accepts an arbitrary URL as input.
Does the PDF match exactly what I see in my own browser?
It matches what headless Chromium renders at the viewport and wait conditions you specify, which is usually identical to what you see, but pages with print-specific CSS, geolocation, or browser-extension-dependent behavior can differ. If you need pixel-perfect control, html with a print stylesheet is more predictable than snapshotting a live url.
Related
Generate your first PDF in minutes.
Get an API key