Generate PDFs from Any Webpage Using an API
February 6, 2026 ยท 4 min read
Photo via Unsplash
PDF generation is one of the most common developer tasks. Invoices, reports, receipts, contracts, articles saved for offline reading -- PDFs are everywhere. But generating them programmatically has always been painful. Libraries like wkhtmltopdf are outdated, Puppeteer requires managing browser infrastructure, and most PDF libraries cannot render modern CSS. SnapAPI makes it simple: send a URL or HTML, get back a PDF rendered in a real Chromium browser.
Why Use an API for PDF Generation?
- Perfect CSS rendering: Full support for flexbox, grid, custom fonts, and CSS print styles
- No infrastructure: No headless Chrome to install, update, or scale
- Consistent output: Same rendering engine produces identical PDFs every time
- Fast: Typical PDF generation takes under 3 seconds
Basic URL to PDF with curl
The simplest way to generate a PDF. Just set format: "pdf":
curl -X POST https://api.snapapi.pics/v1/screenshot \
-H "X-Api-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://en.wikipedia.org/wiki/PDF",
"format": "pdf",
"responseType": "image"
}' \
--output article.pdf
This captures the entire page and saves it as a properly formatted PDF document. The responseType: "image" returns raw binary data (in this case, the PDF file itself) rather than a JSON-wrapped base64 string.
URL to PDF with Node.js
Here is a complete Node.js example:
const fs = require("fs");
const SNAPAPI_KEY = "your_api_key";
async function urlToPdf(url, outputPath = "output.pdf") {
const response = await fetch("https://api.snapapi.pics/v1/screenshot", {
method: "POST",
headers: {
"X-Api-Key": SNAPAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
url,
format: "pdf",
fullPage: true,
blockCookieBanners: true,
responseType: "image",
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`PDF generation failed: ${response.status} ${error}`);
}
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync(outputPath, buffer);
console.log(`PDF saved: ${outputPath} (${buffer.length} bytes)`);
return buffer;
}
// Generate PDF from a webpage
urlToPdf("https://stripe.com/docs/api", "stripe-api-docs.pdf");
URL to PDF with Python
import requests
SNAPAPI_KEY = "your_api_key"
def url_to_pdf(url: str, output_path: str = "output.pdf") -> int:
response = requests.post(
"https://api.snapapi.pics/v1/screenshot",
headers={
"X-Api-Key": SNAPAPI_KEY,
"Content-Type": "application/json",
},
json={
"url": url,
"format": "pdf",
"fullPage": True,
"blockCookieBanners": True,
"responseType": "image",
},
timeout=30,
)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
print(f"PDF saved: {output_path} ({len(response.content)} bytes)")
return len(response.content)
# Generate PDF from any URL
url_to_pdf("https://news.ycombinator.com", "hackernews.pdf")
HTML to PDF: Invoice Template
The real power of PDF generation comes from rendering custom HTML. Here is a complete invoice template that you can populate with dynamic data:
function generateInvoiceHtml(invoice) {
const rows = invoice.items
.map(
(item) => `
<tr>
<td>${item.description}</td>
<td style="text-align: center;">${item.quantity}</td>
<td style="text-align: right;">$${item.unitPrice.toFixed(2)}</td>
<td style="text-align: right;">$${(item.quantity * item.unitPrice).toFixed(2)}</td>
</tr>
`
)
.join("");
const subtotal = invoice.items.reduce(
(sum, item) => sum + item.quantity * item.unitPrice, 0
);
const tax = subtotal * (invoice.taxRate || 0);
const total = subtotal + tax;
return `
<!DOCTYPE html>
<html>
<head>
<style>
@page { size: A4; margin: 0; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #1a1a2e;
padding: 60px;
font-size: 14px;
line-height: 1.5;
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 50px;
}
.company-name {
font-size: 28px;
font-weight: 800;
color: #8b5cf6;
}
.invoice-label {
font-size: 36px;
font-weight: 300;
color: #94a3b8;
text-align: right;
}
.invoice-number {
font-size: 14px;
color: #64748b;
text-align: right;
margin-top: 4px;
}
.addresses {
display: flex;
justify-content: space-between;
margin-bottom: 40px;
}
.address-block h4 {
font-size: 12px;
text-transform: uppercase;
color: #94a3b8;
margin-bottom: 8px;
letter-spacing: 0.5px;
}
.address-block p {
color: #334155;
line-height: 1.6;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 30px;
}
th {
background: #f8fafc;
padding: 12px 16px;
text-align: left;
font-size: 12px;
text-transform: uppercase;
color: #64748b;
border-bottom: 2px solid #e2e8f0;
letter-spacing: 0.5px;
}
td {
padding: 14px 16px;
border-bottom: 1px solid #f1f5f9;
color: #334155;
}
.totals {
display: flex;
justify-content: flex-end;
}
.totals-table {
width: 280px;
}
.totals-table tr td {
border: none;
padding: 6px 16px;
}
.totals-table .total-row {
font-size: 18px;
font-weight: 700;
color: #1a1a2e;
border-top: 2px solid #1a1a2e;
}
.footer {
position: absolute;
bottom: 60px;
left: 60px;
right: 60px;
text-align: center;
color: #94a3b8;
font-size: 12px;
border-top: 1px solid #e2e8f0;
padding-top: 20px;
}
</style>
</head>
<body>
<div class="header">
<div>
<div class="company-name">${invoice.company.name}</div>
<p style="color: #64748b; margin-top: 4px;">${invoice.company.address}</p>
</div>
<div>
<div class="invoice-label">INVOICE</div>
<div class="invoice-number">#${invoice.number}</div>
<div class="invoice-number">Date: ${invoice.date}</div>
<div class="invoice-number">Due: ${invoice.dueDate}</div>
</div>
</div>
<div class="addresses">
<div class="address-block">
<h4>Bill To</h4>
<p><strong>${invoice.client.name}</strong><br>
${invoice.client.address}<br>
${invoice.client.email}</p>
</div>
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th style="text-align: center;">Qty</th>
<th style="text-align: right;">Unit Price</th>
<th style="text-align: right;">Amount</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
<div class="totals">
<table class="totals-table">
<tr>
<td>Subtotal</td>
<td style="text-align: right;">$${subtotal.toFixed(2)}</td>
</tr>
<tr>
<td>Tax (${((invoice.taxRate || 0) * 100).toFixed(0)}%)</td>
<td style="text-align: right;">$${tax.toFixed(2)}</td>
</tr>
<tr class="total-row">
<td>Total</td>
<td style="text-align: right;">$${total.toFixed(2)}</td>
</tr>
</table>
</div>
<div class="footer">
Thank you for your business. Payment is due within 30 days.
</div>
</body>
</html>
`;
}
Generate the Invoice PDF
async function generateInvoicePdf(invoice) {
const html = generateInvoiceHtml(invoice);
const response = await fetch("https://api.snapapi.pics/v1/screenshot", {
method: "POST",
headers: {
"X-Api-Key": "your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
format: "pdf",
width: 794, // A4 width in pixels at 96 DPI
height: 1123, // A4 height in pixels at 96 DPI
responseType: "image",
}),
});
if (!response.ok) throw new Error(`Failed: ${response.status}`);
return Buffer.from(await response.arrayBuffer());
}
// Usage
const invoice = {
number: "INV-2026-0042",
date: "February 6, 2026",
dueDate: "March 8, 2026",
company: {
name: "Acme Corp",
address: "123 Business St, San Francisco, CA 94102",
},
client: {
name: "Jane Smith",
address: "456 Client Ave, New York, NY 10001",
email: "jane@example.com",
},
items: [
{ description: "Web Development - Homepage Redesign", quantity: 40, unitPrice: 150 },
{ description: "API Integration", quantity: 16, unitPrice: 175 },
{ description: "QA Testing & Bug Fixes", quantity: 8, unitPrice: 125 },
],
taxRate: 0.08,
};
const pdf = await generateInvoicePdf(invoice);
fs.writeFileSync(`${invoice.number}.pdf`, pdf);
console.log(`Invoice PDF generated: ${invoice.number}.pdf`);
Full-Page PDF
By default, a PDF capture renders the visible viewport. To capture the entire scrollable page as a continuous PDF, use fullPage: true:
curl -X POST https://api.snapapi.pics/v1/screenshot \
-H "X-Api-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://tailwindcss.com/docs/installation",
"format": "pdf",
"fullPage": true,
"blockCookieBanners": true,
"responseType": "image"
}' \
--output tailwind-docs.pdf
This is particularly useful for generating PDF copies of documentation pages, long-form articles, or legal documents.
Python: Generate Invoice PDFs
Here is the same invoice generation flow in Python:
import requests
SNAPAPI_KEY = "your_api_key"
def generate_invoice_pdf(invoice: dict, output_path: str) -> None:
# Build line items HTML
rows = ""
subtotal = 0
for item in invoice["items"]:
amount = item["quantity"] * item["unit_price"]
subtotal += amount
rows += f"""
<tr>
<td>{item["description"]}</td>
<td style="text-align: center;">{item["quantity"]}</td>
<td style="text-align: right;">${item["unit_price"]:.2f}</td>
<td style="text-align: right;">${amount:.2f}</td>
</tr>"""
tax = subtotal * invoice.get("tax_rate", 0)
total = subtotal + tax
html = f"""<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: system-ui; padding: 60px; color: #1a1a2e; font-size: 14px; }}
table {{ width: 100%; border-collapse: collapse; }}
th {{ background: #f8fafc; padding: 12px; text-align: left; border-bottom: 2px solid #e2e8f0; }}
td {{ padding: 12px; border-bottom: 1px solid #f1f5f9; }}
h1 {{ color: #8b5cf6; font-size: 24px; }}
</style>
</head>
<body>
<h1>{invoice["company"]}</h1>
<p style="color: #64748b;">Invoice #{invoice["number"]} | {invoice["date"]}</p>
<hr style="margin: 20px 0; border-color: #e2e8f0;">
<p><strong>Bill To:</strong> {invoice["client"]}</p>
<table style="margin-top: 30px;">
<thead>
<tr>
<th>Description</th>
<th style="text-align: center;">Qty</th>
<th style="text-align: right;">Rate</th>
<th style="text-align: right;">Amount</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
<div style="text-align: right; margin-top: 20px;">
<p>Subtotal: ${subtotal:.2f}</p>
<p>Tax ({invoice.get("tax_rate", 0) * 100:.0f}%): ${tax:.2f}</p>
<p style="font-size: 20px; font-weight: 700; margin-top: 8px;">Total: ${total:.2f}</p>
</div>
</body>
</html>"""
response = requests.post(
"https://api.snapapi.pics/v1/screenshot",
headers={
"X-Api-Key": SNAPAPI_KEY,
"Content-Type": "application/json",
},
json={
"html": html,
"format": "pdf",
"width": 794,
"height": 1123,
"responseType": "image",
},
timeout=30,
)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
print(f"Invoice PDF saved: {output_path} ({len(response.content)} bytes)")
# Generate an invoice
generate_invoice_pdf(
{
"number": "INV-2026-0042",
"date": "February 6, 2026",
"company": "Acme Corp",
"client": "Jane Smith, jane@example.com",
"tax_rate": 0.08,
"items": [
{"description": "Web Development", "quantity": 40, "unit_price": 150},
{"description": "API Integration", "quantity": 16, "unit_price": 175},
{"description": "QA Testing", "quantity": 8, "unit_price": 125},
],
},
"invoice.pdf",
)
Express.js PDF Endpoint
Serve PDFs dynamically from your web application:
const express = require("express");
const app = express();
app.get("/invoice/:id/pdf", async (req, res) => {
try {
// Fetch invoice data from your database
const invoice = await db.getInvoice(req.params.id);
if (!invoice) return res.status(404).send("Invoice not found");
const html = generateInvoiceHtml(invoice);
const response = await fetch("https://api.snapapi.pics/v1/screenshot", {
method: "POST",
headers: {
"X-Api-Key": process.env.SNAPAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
html,
format: "pdf",
width: 794,
height: 1123,
responseType: "image",
}),
});
if (!response.ok) throw new Error("PDF generation failed");
const buffer = Buffer.from(await response.arrayBuffer());
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${invoice.number}.pdf"`
);
res.send(buffer);
} catch (err) {
console.error("PDF generation error:", err);
res.status(500).send("Failed to generate PDF");
}
});
app.listen(3000);
Pro tip: Set Content-Disposition to attachment to force a download, or inline to display the PDF in the browser.
CSS Print Styles
When generating PDFs from existing web pages, you can leverage CSS print media queries for optimal output. The API renders PDFs using Chromium's print functionality, so @media print rules are respected:
/* Your site's CSS */
@media print {
/* Hide navigation and footer */
nav, footer, .sidebar { display: none !important; }
/* Ensure content fills the page */
.content { max-width: 100%; margin: 0; padding: 20px; }
/* Avoid page breaks inside elements */
.card, .section { break-inside: avoid; }
/* Force page break before chapters */
.chapter { break-before: page; }
/* Black text for readability */
body { color: #000 !important; background: #fff !important; }
}
Common Use Cases
- Invoices and receipts: Generate professional invoices from your billing data
- Reports and dashboards: Export analytics dashboards as PDF reports for stakeholders
- Contracts and proposals: Generate client-facing documents from templates
- Articles and blog posts: Offer "Download as PDF" for your content
- E-commerce: Generate packing slips, shipping labels, and product catalogs
- Education: Create certificates, transcripts, and course materials
Next Steps
- Full API Documentation -- all PDF parameters and options
- Screenshot API with Python -- more Python integration examples
- Screenshot API with Node.js -- Node.js guide
- Web Archiving -- archive pages as PDF documents
Try SnapAPI Free
Get 200 free screenshots and PDFs per month. No credit card required.
Get Started Free →Related Reading
- PDF Generation Use Case โ invoices, reports, and documents at scale
- Web Archiving API โ archive pages as PDFs for compliance
- Free Screenshot API โ includes free PDF generation
- HTML to PDF API Guide โ convert raw HTML templates to PDFs
- Screenshot API Pricing Guide 2026 โ SnapAPI vs ScreenshotOne vs URLBox cost breakdown