Blog/Use cases

Archive a webpage to PDF

Use cases··6 min read

To archive webpage pdf output from a live site, POST a public http(s) URL to /v1/pdf. Chromium loads that URL at request time and prints it. That is a snapshot of whatever the public host returned in that window, not a legal-hold archive, not a WORM store, and not a substitute for your own bucket plus a retention policy. Private, loopback, metadata, and RFC1918 hosts return url_not_allowed. Product: https://relaypdf.com/url-to-pdf. Docs: https://relaypdf.com/docs/pdf. Logged-in pages are a different job: https://relaypdf.com/blog/url-to-pdf-authenticated-pages.

Use URL mode when the page already lives on a public host and you do not want to fetch HTML yourself. If you control the markup, send html instead. If you need a raster, use POST /v1/images. Mixing html, url, markdown, and templateId in one body is invalid_request. filename, if set, must end in .pdf.

What a URL snapshot is

The worker fetches the URL you pass. It is not your browser session. It does not replay a crawl you already stored. It does not pin the page against later edits on the origin. If the host returns a 200 with today's CSS and today's ads, that is what prints. If the host 301s, times out, or serves a bot wall, you get that state or a render_failed. Store the PDF bytes (or a copy of the 24-hour file URL contents) in your own object store if you need a durable record.

response is binary (default), url, or async. binary is 200 application/pdf with x-relaypdf-id, x-relaypdf-size, and content-disposition. url returns JSON: id, status, url, filename, sizeBytes, expiresAt. The file URL is GET /v1/files/:id, no Bearer, 24 hours. After expiresAt the object is gone. async is 202; poll GET /v1/jobs/:id. callbackUrl, if used, must be https. HTML/URL/Markdown/template PDF debit is $0.015 on success. Failed jobs, 429, and 402 are not billed.

Public URL only

url must be public http or https. Localhost, 127.0.0.1, ::1, link-local, cloud metadata, and RFC1918 ranges are rejected with url_not_allowed. That is intentional. The convert fleet is not a proxy into your VPC. Do not try to archive an internal wiki, a staging box, or an admin console by pointing url at a private address.

cookies (name, value, optional domain, path, url) and extraHTTPHeaders apply to the Chromium navigation of that public URL. They are not a login robot. They do not punch through SSO, CAPTCHA, or IP allowlists that exclude the worker. They do not open private hosts. If the page is behind your app session, render HTML on your server and send html, or follow the authenticated-pages sibling. Do not ship the user's cookie jar from the browser to RelayPDF as a way to print /app/*.

Auth limits

A page that redirects to a login form will archive the login form. A page that 401s or 403s will archive that response or fail the render. A page that hydrates only after a first-party token in localStorage will archive the empty shell unless you wait for a selector that never appears, then you hit timeout. None of that is a bug in URL mode. URL mode has no account on the target site.

If you still need a public URL plus a header the origin already accepts (a documented preview token, a public share link), extraHTTPHeaders can carry that header. Keep tokens short-lived and scoped to the document. Do not put pdf_live_ keys in those headers. Query-string API keys on api.relaypdf.com are rejected. Authorization on POST /v1/pdf is Bearer pdf_live_… from your backend only.

Not a legal-hold product

RelayPDF is a prepaid Chromium print API. It is not a records-management system. It does not claim legal hold, litigation hold, chain of custody, tamper evidence, or court-admissible archival. It does not offer PDF/A. File objects expire in 24 hours. There is no documented WORM bucket, hold flag, or custodian log. If counsel asks you to preserve a page, your process is: fetch or print, write the bytes to storage you control, and apply your own retention. Do not point auditors at GET /v1/files/:id.

A snapshot is also not a complete site archive. One URL is one print. Linked assets that fail to load, pages behind pagination, and PDFs that the origin already hosts are not crawled. There is no sitemap walker on /v1/pdf.

Print options that matter for a snapshot

Same options as HTML mode. printBackground defaults to true; leave it on if the page uses background color for meaning. format defaults to letter. waitUntil defaults to networkidle0 (load, domcontentloaded, networkidle0, networkidle2). For a news article that keeps a websocket open, networkidle0 may never fire; use load or a waitForSelector on the article body. waitForSelector is a CSS selector or { selector, timeout, visible }. waitForTimeout is extra milliseconds after the other waits, max 30000. options.timeout max is 60000. margin is CSS lengths. landscape is a boolean. headerTemplate and footerTemplate are Chromium margin fragments with pageNumber, totalPages, date, title. Field list: https://relaypdf.com/docs/options. There is no documented mediaType or emulateMedia field.

Trial keys are 20 requests per minute. Funded wallets are 60 per minute. rate_limited includes Retry-After. Do not fan out a full-site crawl against those limits and call it an archive.

Request

Server-side Node @relaypdf/sdk. Field names in options stay camelCase. Keep the key off the browser.

import { RelayPDF } from "@relaypdf/sdk";
const client = new RelayPDF({ apiKey: process.env.RELAYPDF_API_KEY });
const job = await client.pdf.fromUrl("https://example.com/post/42", {
  filename: "post-42.pdf",
  response: "url",
  options: {
    format: "letter",
    printBackground: true,
    waitUntil: "networkidle0",
    margin: { top: "12mm", right: "12mm", bottom: "14mm", left: "12mm" },
  },
});
// job.url is GET /v1/files/:id — 24h, no key // copy bytes into your bucket if you need retention Equivalent curl:
curl -X POST https://api.relaypdf.com/v1/pdf
  -H "Authorization: Bearer pdf_live_..."
  -H "Content-Type: application/json"
  -d '{
    "url": "https://example.com/post/42",
    "filename": "post-42.pdf",
    "response": "url",
    "options": { "format": "letter", "printBackground": true }
  }'

After the print

If response is url, GET the file from your backend before expiresAt and write it to S3, GCS, or disk you own. Persist the source URL, the request time, and the RelayPDF id next to the object if you need an audit row. That row is yours. RelayPDF will not keep it. If you skip the copy, you have a 24-hour link, not an archive.

For a screenshot instead of paged PDF, POST /v1/images with the same public-URL rules. Do not expect /v1/pdf to emit PNG.

What this is not

AskAnswer
Internal / staging URLurl_not_allowed; send html you rendered
User's logged-in /app viewSee authenticated-pages sibling
Legal hold / PDF/A / WORMNot a product; store bytes yourself
Permanent files/:idExpires in 24 hours
Full-site crawlOne URL per POST
Browser fetch with the API keyLeaks the key

Errors

Failures return { error: { code, message } }. invalid_request for mixed sources or a filename that is not .pdf. url_not_allowed for a private or metadata host. render_failed when Chromium times out or cannot print; unbilled. payment_required on an empty wallet. rate_limited with Retry-After. Map those codes in the job that requested the snapshot. Do not retry payment_required in a loop.

Ship it

Public URL, POST /v1/pdf, copy the 24-hour file into storage you control, do not call the result a legal hold. CTA: https://relaypdf.com/url-to-pdf. Endpoint: https://relaypdf.com/docs/pdf. Options: https://relaypdf.com/docs/options. Auth walls: https://relaypdf.com/blog/url-to-pdf-authenticated-pages.

Ready to generate?

One API for HTML, Markdown, URLs, and Office. REST, SDK, CLI, or MCP.