Convert Any Web Page to PDF via API (No Puppeteer, No Server)
Convert any web page to PDF via API without running Puppeteer or maintaining a server. Generate invoices, reports, and legal evidence captures in Node.js or Python in minutes.
Generating PDFs from web pages is a deceptively difficult engineering problem. Running Puppeteer or Playwright on your own server sounds simple until you hit memory leaks on long-running Lambda functions, browser crashes under load, mysterious font rendering differences between environments, and the joy of debugging headless Chromium on Alpine Linux. ScreenshotFreeAPI eliminates all of that: pass "format": "pdf" and the API handles the browser infrastructure while you receive a presigned download URL.
The Basic PDF Call
Switching from a PNG screenshot to a PDF is a single parameter change. The async job pattern is identical — POST to create, poll or webhook for completion, fetch the result.
curl -X POST https://api.screenshotfreeapi.com/screenshots/web \
-H "Authorization: Bearer sfa_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/invoices/inv-2026-0042",
"format": "pdf",
"fullPage": true,
"viewport": { "width": 1024, "height": 768 }
}'
# Response: 202 Accepted
# { "jobId": "job_03pdfxyz" }Use Case 1: Invoice Generation
The most common PDF generation use case is invoices. The pattern: render your invoice as an HTML page at a known URL (or generate HTML inline and POST it to /screenshots/html), then call ScreenshotFreeAPI to produce a pixel-perfect PDF. Your invoice page can use any CSS — web fonts, flexbox, grid, print media queries — and the output will match exactly what Chromium renders.
const API_KEY = process.env.SFA_API_KEY!;
const BASE_URL = 'https://api.screenshotfreeapi.com';
interface Invoice {
id: string;
customerName: string;
amount: number;
}
async function generateInvoicePDF(invoice: Invoice): Promise<string> {
// Step 1: Create the PDF job
const createRes = await fetch(`${BASE_URL}/screenshots/web`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: `https://yourapp.com/invoices/${invoice.id}?token=INTERNAL_TOKEN`,
format: 'pdf',
fullPage: true,
viewport: { width: 794, height: 1123 }, // A4 at 96dpi
}),
});
if (!createRes.ok) throw new Error(`Create failed: ${createRes.status}`);
const { jobId } = await createRes.json() as { jobId: string };
// Step 2: Poll until complete
let status = 'queued';
while (status !== 'completed') {
await new Promise(resolve => setTimeout(resolve, 2000));
const statusRes = await fetch(`${BASE_URL}/jobs/${jobId}/status`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const data = await statusRes.json() as { status: string };
status = data.status;
if (status === 'failed') throw new Error(`PDF generation failed for invoice ${invoice.id}`);
}
// Step 3: Fetch the result
const resultRes = await fetch(`${BASE_URL}/jobs/${jobId}/result`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const result = await resultRes.json() as { screenshots: Array<{ url: string }> };
return result.screenshots[0].url; // presigned S3 URL, valid 15 min
}Tip
For A4 PDF output, set "viewport": {"width": 794, "height": 1123} — that's 210mm × 297mm at 96dpi. Combined with "fullPage": true, multi-page invoices render correctly.
Use Case 2: Report Generation
Analytics dashboards, audit reports, and data exports are often easier to build as web pages first — using your existing charting libraries and CSS — and then convert to PDF for distribution. This approach means your report template is maintainable by frontend engineers, not a LaTeX or ReportLab specialist.
Use Case 3: Legal Evidence Capture
Legal teams and compliance departments sometimes need to capture the state of a web page at a specific moment in time — a terms-of-service page, a public statement, or a product listing — as admissible evidence. A PDF capture with a timestamped job record from ScreenshotFreeAPI provides a credible, immutable record. Combine with the full-page option to ensure no content is omitted.
Python Example
import asyncio
import os
import httpx
API_KEY = os.environ["SFA_API_KEY"]
BASE_URL = "https://api.screenshotfreeapi.com"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
async def generate_pdf(url: str) -> str:
async with httpx.AsyncClient(timeout=60.0) as client:
# Create the PDF job
r = await client.post(
f"{BASE_URL}/screenshots/web",
headers=HEADERS,
json={"url": url, "format": "pdf", "fullPage": True, "viewport": {"width": 1024, "height": 768}},
)
r.raise_for_status()
job_id = r.json()["jobId"]
# Poll until complete
while True:
await asyncio.sleep(2)
status_r = await client.get(f"{BASE_URL}/jobs/{job_id}/status", headers=HEADERS)
status_r.raise_for_status()
status = status_r.json()["status"]
if status == "completed":
break
if status == "failed":
raise RuntimeError(f"PDF job {job_id} failed")
# Fetch result
result_r = await client.get(f"{BASE_URL}/jobs/{job_id}/result", headers=HEADERS)
result_r.raise_for_status()
return result_r.json()["screenshots"][0]["url"]
if __name__ == "__main__":
pdf_url = asyncio.run(generate_pdf("https://example.com/report"))
print(f"PDF ready: {pdf_url}")Why Not Just Run Puppeteer Yourself?
Running your own Puppeteer or Playwright instance is viable at low scale. But as volume grows, you hit a predictable set of problems: browser processes leak memory and need periodic restarts; concurrent captures saturate your server's memory; Chromium crashes on malformed pages and your job silently fails; cold-start times on serverless functions (Lambda, Cloud Run) can be 10+ seconds; and keeping Chromium and its dependencies updated without breaking things is an ongoing maintenance tax. ScreenshotFreeAPI handles all of this so you don't have to.
Generate production-quality PDFs from any URL — no Puppeteer, no servers, no maintenance.
Start free — no credit card