RenderScreenshot turns a URL or a string of HTML into a PDF with one HTTP request. Headless Chrome loads the page, applies your paper size, margins and header and footer templates, and the API returns the PDF bytes. You get invoices, reports, statements and archived web pages without a browser fleet to run.
curl -X POST https://api.renderscreenshot.com/v1/screenshot \ -H "Authorization: Bearer $RS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "output": { "format": "pdf" }, "pdf": { "paper": "a4", "margin": "15mm", "print_background": true } }' \ --fail --output example.pdf
The same key also makes PNG, JPEG and WebP screenshots, so one integration covers "download as PDF" and "share as image". Every option on this page is listed in the PDF parameters reference. If you are still comparing services, our roundup of HTML to PDF APIs puts seven options side by side.
Four requests, four documents
Each request below is a complete POST body. Change the url (or swap it for html) and send it.
| Document | What the request does | Body |
|---|---|---|
| A4 invoice with page numbers | A4 paper, side margins, a footer that reads "Page 2 of 3" | see header and footer templates |
| US Letter statement | Letter is the default paper size, so only margins are set | {"url": "...", "output": {"format": "pdf"}, "pdf": {"margin": "0.75in"}} |
| Wide dashboard export | Landscape A4 with backgrounds, so chart fills print | {"url": "...", "output": {"format": "pdf"}, "pdf": {"paper": "a4", "landscape": true, "print_background": true}} |
| One-page summary | Scales the whole page down until it fits on one sheet | {"url": "...", "output": {"format": "pdf"}, "pdf": {"paper": "letter", "fit_one_page": true}} |
For the common case there is also a preset. "preset": "pdf_document" gives you Letter paper, background graphics and 1 cm margins, as listed in the presets reference.
How a PDF request flows
sequenceDiagram
participant App as Your app
participant API as RenderScreenshot API
participant CF as Headless Chrome (Cloudflare)
participant R2 as R2 storage + CDN
App->>API: POST /v1/screenshot (url or html, output.format pdf, pdf options)
API->>API: Auth, validation, credit check
API->>CF: Load page, wait, print with paper, margins, templates
CF->>R2: Store the PDF
API-->>App: PDF bytes + X-Cache-URL + page metadata headers
Three response details matter for documents:
X-Cache-URLis a public CDN link to the same PDF. Store it on the invoice row or put it in an email, and nobody needs your key to open it. It lives for the cache TTL (24 hours by default; setcache.ttlup to 30 days).X-Screenshot-Titleand the other metadata headers come from the real page, so you can name the file after the document title without parsing the PDF. See response headers.Content-Dispositionisinlineby default. Setoutput.filenameto control the name a browser offers when someone saves it.
When you regenerate the same document, add "cache": { "available": true }. An identical request inside the TTL returns the stored PDF for 0 credits instead of rendering again (caching).
Header and footer templates
Chrome prints the pdf.header and pdf.footer strings into the page margins. Inside them, elements with these classes are filled in on each page: pageNumber, totalPages, date, title and url.
{ "url": "https://example.com/invoices/2048", "output": { "format": "pdf", "filename": "invoice-2048" }, "pdf": { "paper": "a4", "print_background": true, "margin": "16mm", "margin_bottom": "22mm", "header": "<span></span>", "footer": "<div style='font-size:9px;width:100%;text-align:center;color:#475569'>Page <span class='pageNumber'></span> of <span class='totalPages'></span></div>" } }
We tested the template rules in Chromium 148 with Puppeteer before writing this section. Three of them catch nearly everyone:
- Set a font size inside the template. Our footer without a
font-sizeprinted at about 1 pt, a speck at the bottom of the page. Templates do not inherit your page's CSS, so style them inline. - Leave a margin for the template. With a bottom margin of 0, Chrome drew "Page 1 of 2" on top of the last lines of body text. Give the side that holds a template at least 15 to 20 mm.
- Send a header when you only want a footer. When we passed only a footer template, Chrome printed its default header on every page: the date on the left and the document title on the right. An empty
<span></span>header prevents that.
Images in templates should be data: URIs, because the template is not loaded like a normal page.
Your code, in six languages
The SDKs build the same request. These follow the SDK docs; we checked that each builder method exists in the current Node, Python and Ruby packages.
// Node.js 20+: npm install renderscreenshot import { writeFile } from 'node:fs/promises'; import { Client, TakeOptions } from 'renderscreenshot'; const client = new Client(process.env.RS_API_KEY); const pdf = await client.take( TakeOptions.url('https://example.com/invoices/2048') .format('pdf') .pdfPaperSize('a4') .pdfMargin('16mm') .pdfPrintBackground() .pdfHeader('<span></span>') .pdfFooter('<div style="font-size:9px;width:100%;text-align:center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>') ); await writeFile('invoice.pdf', pdf);
# Python 3.9+ import os from renderscreenshot import Client, TakeOptions client = Client(os.environ['RS_API_KEY']) pdf = client.take( TakeOptions.url('https://example.com/invoices/2048') .format('pdf') .pdf_paper_size('a4') .pdf_margin('16mm') .pdf_print_background() ) with open('invoice.pdf', 'wb') as f: f.write(pdf)
# Ruby 3.0+ require 'renderscreenshot' client = RenderScreenshot.client(ENV.fetch('RS_API_KEY')) pdf = client.take( RenderScreenshot::TakeOptions.url('https://example.com/invoices/2048') .format('pdf').pdf_paper_size('a4').pdf_margin('16mm').pdf_print_background ) File.binwrite('invoice.pdf', pdf)
<?php use RenderScreenshot\Client; use RenderScreenshot\TakeOptions; $client = new Client(getenv('RS_API_KEY')); $pdf = $client->take( TakeOptions::url('https://example.com/invoices/2048') ->format('pdf')->pdfPaperSize('a4')->pdfMargin('16mm')->pdfPrintBackground() ); file_put_contents('invoice.pdf', $pdf);
The Go and Java snippets show the call only; the imports are in the Go and Java SDK pages. For a complete Java app, see HTML to PDF in Spring Boot.
ctx := context.Background() client, err := rs.New(os.Getenv("RS_API_KEY")) if err != nil { log.Fatal(err) } pdf, err := client.Take(ctx, rs.URL("https://example.com/invoices/2048"). Format(rs.FormatPDF).PDFPaperSize(rs.PaperA4).PDFMarginUniform("16mm").PDFPrintBackground()) if err != nil { log.Fatal(err) } os.WriteFile("invoice.pdf", pdf, 0o644)
Client client = new Client(System.getenv("RS_API_KEY")); byte[] pdf = client.take( TakeOptions.url("https://example.com/invoices/2048") .format("pdf").pdfPaperSize("a4").pdfMargin("16mm").pdfPrintBackground()); Files.write(Path.of("invoice.pdf"), pdf);
fit_one_page, prefer_css_page_size and the per-side margins are also plain JSON fields if you call the HTTP API directly.
Print CSS checklist
Most "the PDF looks wrong" tickets are CSS, not the API. Work through this list before you change any parameter:
- [ ] Pick screen or print styles on purpose. If your page has
@media printrules, send"browser": { "media": "print" }. If it has none and the PDF should look like the screen, leave the default. See the print media guide. - [ ] Keep backgrounds on for designed documents. In our Chromium test, turning
printBackgroundoff removed the blue fill from an invoice's table header, and its white text came out grey.print_backgroundistrueby default here; only turn it off for plain text output. - [ ] Decide who owns the page size. Chrome ignores
@page { size: A5 }unless you ask it not to: our test PDF came out Letter untilpreferCSSPageSizewas set, then A5. The API field ispdf.prefer_css_page_size. - [ ] Control breaks. Use
break-before: pageon chapter headings,break-inside: avoidon table rows and total boxes, andthead { display: table-header-group }so the table header repeats on every page. - [ ] Wait for fonts and data. A PDF prints when the wait condition passes. For web fonts and pages that fetch data, send
"wait": { "until": "networkidle" }or wait for a selector that appears when the data is in (wait options). - [ ] Expect page counts to move between machines. The same report printed 6 pages on macOS and 11 pages in a Linux container, because a different serif font was installed. Ship your own web fonts if page breaks must not move.
Pricing
One successful render is one credit, whether the PDF is one page or two hundred. Failed renders are free, and there is no overage: at the limit, requests stop with insufficient_credits until the period resets or you upgrade (credits and billing).
| Plan | Credits per month | Price |
|---|---|---|
| Free | 150 | $0, no credit card |
| Starter | 2,500 | $10 |
| Growth | 12,000 | $40 |
| Scale | 60,000 | $100 |
A team that sends 3,000 invoices a month needs the Growth plan, which works out at $13.33 per 1,000 PDFs at that volume. Current prices are always on the pricing page.
When to use something else
- Books, footnotes and PDF/UA output. A print engine such as Prince implements paged-media CSS that Chrome does not. See our DocRaptor alternative page for where that matters.
- Pages on a private network. The renderer only loads public addresses, so intranet pages need a renderer inside your network.
- You still run wkhtmltopdf. Start with the wkhtmltopdf alternative guide: it shows what its engine drops and has drop-in replacements for Ruby, PHP and Python.
Get a free API key and print your first document.
Chromium findings on this page come from local tests on 2026-10-18 with Node 22.18.0, Puppeteer 24.43.1 and Chrome 148.0.7778.97 on macOS (Apple M1 Pro), and with Browserless 2.57.0 (Chrome 156) in Docker for the page-count comparison. They describe Chrome's print engine, not API latency.