Blog
Tutorial

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.

bash
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.

typescript
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

python
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

Priya Nair

Senior Engineer at ScreenshotFreeAPI