Converting a URL or HTML to a PDF sounds simple. In practice, it’s one of those problems that gets complicated fast — page breaks, margins, header/footer templates, background colors, print stylesheets, and the endless quirks of browser rendering engines.
The traditional approach involves running a headless browser (Puppeteer or Playwright), managing Chrome processes, and wrestling with the page.pdf() API. It works, but it’s a lot of infrastructure for what should be a straightforward conversion.
The len.sh /v1/pdf endpoint turns any URL or HTML into a PDF with a single HTTP request. This guide covers every parameter, real-world use cases, and code examples in multiple languages.
Quick Start
The simplest possible PDF generation — convert a URL to A4:
curl "https://api.len.sh/v1/pdf?url=https://example.com&access_key=YOUR_API_KEY" \
--output document.pdf
That’s a working PDF. The default settings (A4 paper, 0.4in margins, portrait orientation, backgrounds included) cover most use cases.
All PDF Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
url | string | required* | URL to render as PDF |
html | string | required* | Raw HTML to render (use POST) |
paper_size | string | A4 | Page size: A4, Letter, Legal, Tabloid, Ledger |
landscape | boolean | false | Landscape orientation |
scale | number | 1.0 | Scale factor (0.1-2.0) |
margin_top | string | 0.4in | Top margin (CSS unit: in, cm, mm, px) |
margin_right | string | 0.4in | Right margin |
margin_bottom | string | 0.4in | Bottom margin |
margin_left | string | 0.4in | Left margin |
print_background | boolean | true | Include background colors/images |
page_ranges | string | — | Pages to include, e.g. 1-3, 5 |
header_template | string | — | HTML template for page headers |
footer_template | string | — | HTML template for page footers |
wait_until | string | load | When to consider the page loaded |
timeout | integer | 15000 | Max wait time in ms (1-30000) |
delay | integer | 0 | Wait N ms before rendering (0-10000) |
user_agent | string | — | Custom user agent string |
block_ads | boolean | false | Hide ad elements |
block_cookie_banners | boolean | false | Hide cookie banners |
js | string | — | Custom JavaScript to inject (max 10KB) |
css | string | — | Custom CSS to inject (max 10KB) |
cache_ttl | integer | 86400 | Cache duration in seconds |
response_type | string | pdf | pdf (raw bytes) or json (metadata) |
*One of url or html is required.
Paper Sizes
The paper_size parameter supports standard page sizes:
| Size | Dimensions | Common Use |
|---|---|---|
A4 | 210mm x 297mm | International standard, business documents |
Letter | 8.5in x 11in | US standard, business documents |
Legal | 8.5in x 14in | US legal documents, contracts |
Tabloid | 11in x 17in | Posters, large-format printouts |
Ledger | 17in x 11in | Spreadsheets, landscape charts |
# US Letter size
curl "https://api.len.sh/v1/pdf?url=https://example.com&paper_size=Letter&access_key=YOUR_API_KEY" \
--output letter.pdf
# Legal size for contracts
curl "https://api.len.sh/v1/pdf?url=https://example.com/contract&paper_size=Legal&access_key=YOUR_API_KEY" \
--output contract.pdf
Margins
Margins accept any CSS unit: in, cm, mm, or px. You can set each side independently:
# 1-inch margins on all sides
curl "https://api.len.sh/v1/pdf?url=https://example.com&margin_top=1in&margin_right=1in&margin_bottom=1in&margin_left=1in&access_key=YOUR_API_KEY" \
--output with-margins.pdf
# Narrow margins for maximum content area
curl "https://api.len.sh/v1/pdf?url=https://example.com&margin_top=0.25in&margin_right=0.25in&margin_bottom=0.25in&margin_left=0.25in&access_key=YOUR_API_KEY" \
--output narrow.pdf
# No margins (full bleed)
curl "https://api.len.sh/v1/pdf?url=https://example.com&margin_top=0&margin_right=0&margin_bottom=0&margin_left=0&access_key=YOUR_API_KEY" \
--output bleed.pdf
Headers and Footers
The header_template and footer_template parameters accept HTML strings that are rendered on every page. Chrome’s PDF engine supports special CSS classes for dynamic content:
| Class | Content |
|---|---|
date | Current date |
title | Document title |
url | Document URL |
pageNumber | Current page number |
totalPages | Total page count |
curl -X POST https://api.len.sh/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/report",
"paper_size": "A4",
"margin_top": "1in",
"margin_bottom": "1in",
"header_template": "<div style=\"font-size:10px;text-align:center;width:100%\">Quarterly Report - Confidential</div>",
"footer_template": "<div style=\"font-size:10px;text-align:center;width:100%\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
}' \
--output report.pdf
Note: header and footer templates need sufficient margin space. If your margins are too small, the header/footer content may overlap with the page content.
Page Ranges
Extract specific pages from a multi-page document:
# First 3 pages only
curl "https://api.len.sh/v1/pdf?url=https://example.com/long-doc&page_ranges=1-3&access_key=YOUR_API_KEY" \
--output first-three.pdf
# Pages 1, 3, and 5-7
curl "https://api.len.sh/v1/pdf?url=https://example.com/long-doc&page_ranges=1,3,5-7&access_key=YOUR_API_KEY" \
--output selected.pdf
This is useful for generating executive summaries from full reports, or extracting specific sections from long documents.
Real-World Use Cases
1. Invoice Generation
Invoices are the most common PDF use case. If you have an invoice template that renders as a web page, converting it to PDF is one API call:
async function generateInvoice(invoiceId) {
const params = new URLSearchParams({
url: `https://yourapp.com/invoices/${invoiceId}`,
paper_size: "Letter",
margin_top: "0.75in",
margin_right: "0.75in",
margin_bottom: "0.75in",
margin_left: "0.75in",
print_background: "true",
access_key: process.env.LENSH_API_KEY,
});
const response = await fetch(`https://api.len.sh/v1/pdf?${params}`);
return Buffer.from(await response.arrayBuffer());
}
// Usage
const pdfBuffer = await generateInvoice("INV-2026-001");
// Save to disk, upload to S3, attach to email, etc.
2. Report Export
Dashboards and reports often need a “Download PDF” button. Instead of building a separate PDF rendering pipeline, screenshot the existing dashboard:
import requests
import os
def export_report(report_url: str, output_path: str):
response = requests.get(
"https://api.len.sh/v1/pdf",
params={
"url": report_url,
"paper_size": "A4",
"landscape": "true", # Better for wide dashboards
"margin_top": "0.5in",
"margin_right": "0.5in",
"margin_bottom": "0.5in",
"margin_left": "0.5in",
"print_background": "true",
"delay": "2000", # Wait for charts to render
"wait_until": "networkidle2",
"access_key": os.environ["LENSH_API_KEY"],
},
stream=True,
)
response.raise_for_status()
with open(output_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
export_report(
"https://yourapp.com/reports/q4-2026",
"q4-report.pdf"
)
The delay=2000 and wait_until=networkidle2 parameters ensure charts and async data have time to load before the PDF is captured.
3. Documentation Export
Convert documentation pages to PDF for offline distribution:
# Export a single doc page
curl "https://api.len.sh/v1/pdf?url=https://docs.yourapp.com/getting-started&paper_size=A4&print_background=true&block_ads=true&block_cookie_banners=true&access_key=YOUR_API_KEY" \
--output getting-started.pdf
The block_ads and block_cookie_banners parameters clean up the page before rendering, removing distracting elements that shouldn’t appear in documentation PDFs.
4. Contract and Legal Documents
For legal documents, you typically want precise margins and page numbering:
curl -X POST https://api.len.sh/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/contracts/2026-NDA",
"paper_size": "Letter",
"margin_top": "1in",
"margin_right": "1in",
"margin_bottom": "1in",
"margin_left": "1in",
"print_background": "false",
"footer_template": "<div style=\"font-size:9px;text-align:center;width:100%\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span> | Confidential</div>"
}' \
--output nda.pdf
5. Generating PDFs from Raw HTML
You don’t need a hosted URL — you can render HTML directly via POST:
async function htmlToPdf(htmlContent) {
const response = await fetch("https://api.len.sh/v1/pdf", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.LENSH_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: htmlContent,
paper_size: "A4",
margin_top: "0.75in",
margin_right: "0.75in",
margin_bottom: "0.75in",
margin_left: "0.75in",
print_background: true,
}),
});
return Buffer.from(await response.arrayBuffer());
}
// Usage
const html = `
<html>
<body style="font-family: system-ui; padding: 40px;">
<h1>Invoice #INV-001</h1>
<table style="width: 100%; border-collapse: collapse;">
<tr style="border-bottom: 1px solid #ddd;">
<th style="text-align: left; padding: 8px;">Item</th>
<th style="text-align: right; padding: 8px;">Amount</th>
</tr>
<tr style="border-bottom: 1px solid #ddd;">
<td style="padding: 8px;">API Usage - Pro Plan</td>
<td style="text-align: right; padding: 8px;">€19.00</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Total</td>
<td style="text-align: right; padding: 8px; font-weight: bold;">€19.00</td>
</tr>
</table>
</body>
</html>
`;
const pdf = await htmlToPdf(html);
This is useful for dynamically generated content where you don’t have a hosted URL — email receipts, one-off documents, or content assembled from database records.
Injecting Custom CSS
Sometimes the web version of a page looks great but the PDF version needs adjustments — hiding navigation, adjusting font sizes, or changing the layout for print. Use the css parameter to inject styles:
curl -X POST https://api.len.sh/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/dashboard",
"paper_size": "A4",
"landscape": true,
"css": "nav, footer, .sidebar { display: none !important; } .main-content { width: 100% !important; } body { font-size: 12px; }"
}' \
--output dashboard.pdf
This hides the navigation, footer, and sidebar, makes the main content full-width, and reduces the font size — all without modifying the original page.
Injecting Custom JavaScript
For pages that need interaction before rendering (closing modals, expanding sections, clicking “show all”):
curl -X POST https://api.len.sh/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/report",
"paper_size": "A4",
"delay": 1000,
"js": "document.querySelectorAll(\"details\").forEach(d => d.open = true); document.querySelector(\".cookie-banner\")?.remove();"
}' \
--output report.pdf
The JavaScript runs after the page loads but before the PDF is captured. The delay parameter gives the DOM time to settle after the script executes.
Scaling and Performance
Caching
PDFs are cached on Cloudflare’s edge network. The same URL with the same parameters returns a cached result in ~50-100ms. The default cache TTL is 24 hours.
# First request: generates the PDF (~2-5 seconds)
curl "https://api.len.sh/v1/pdf?url=https://example.com&access_key=YOUR_API_KEY" \
--output doc.pdf
# Second request: served from cache (~50-100ms)
curl "https://api.len.sh/v1/pdf?url=https://example.com&access_key=YOUR_API_KEY" \
--output doc.pdf
For dynamic content that changes frequently, set cache_ttl=0 to bypass the cache.
Parallel Generation
If you need to generate multiple PDFs, run requests in parallel:
const urls = [
"https://yourapp.com/invoices/001",
"https://yourapp.com/invoices/002",
"https://yourapp.com/invoices/003",
];
const pdfs = await Promise.all(
urls.map(async (url) => {
const params = new URLSearchParams({
url,
paper_size: "Letter",
print_background: "true",
access_key: process.env.LENSH_API_KEY,
});
const response = await fetch(`https://api.len.sh/v1/pdf?${params}`);
return {
url,
buffer: Buffer.from(await response.arrayBuffer()),
};
})
);
Rate limits vary by plan, but even the free tier supports reasonable concurrency for batch operations.
Print Stylesheets
If the target page has a @media print stylesheet, the PDF will use it. This is the right way to handle print-specific styling for pages you control:
/* In your site's CSS */
@media print {
nav, footer, .sidebar, .cookie-banner {
display: none;
}
.main-content {
width: 100%;
margin: 0;
padding: 0;
}
a[href]::after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
}
The len.sh PDF engine respects these styles automatically. No additional configuration needed.
Getting Started
- Sign up for a free len.sh account
- Try the simplest request:
curl "https://api.len.sh/v1/pdf?url=https://example.com&access_key=YOUR_API_KEY" \
--output test.pdf
- Customize with paper size, margins, and background settings
- Add headers/footers for professional documents
- Integrate into your application with the code examples above
The free tier includes 100 requests per month — enough to build and validate your PDF pipeline. All PDF parameters are available on every plan, with no feature gating.