Guide March 17, 2026 10 min read

Convert Any URL to PDF with a REST API — Complete Guide

Generating PDFs is a perennial requirement across almost every type of software: invoices, reports, contracts, compliance records, documentation exports. The traditional solutions — WeasyPrint, wkhtmltopdf, iText, Prince — all require server-side installations, dependency management, and produce inconsistent output across environments.

A headless browser approach is the modern standard because it renders pages exactly as a real user sees them, with full CSS, web fonts, and JavaScript support. SnapAPI exposes this as a REST API: send a URL or HTML string, get a PDF back. No server setup, no binary installs, no rendering inconsistencies.

The Quickest Possible PDF

Generate a PDF from any URL with a single cURL command. This is useful for testing before writing any code.

curl -X POST "https://api.snapapi.pics/v1/screenshot" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://stripe.com/docs","format":"pdf","full_page":true}' \
  --output stripe-docs.pdf

# Check the output
wc -c stripe-docs.pdf  # Should be several hundred KB
open stripe-docs.pdf   # macOS

Replace YOUR_API_KEY with your key from snapapi.pics/register. The free tier gives you 200 requests/month at no cost.

JavaScript: PDF Generation in Node.js

import { SnapAPI } from 'snapapi-js';
import fs from 'fs';

const client = new SnapAPI(process.env.SNAPAPI_KEY);

// Generate PDF from a URL
const pdf = await client.screenshot({
  url: 'https://your-app.com/invoices/INV-2026-001',
  format: 'pdf',
  full_page: true,
  viewport: { width: 1200, height: 900 }
});

fs.writeFileSync('./invoice-001.pdf', pdf);
console.log(`PDF saved: ${(pdf.length / 1024).toFixed(1)} KB`);

Serving PDFs from an Express.js Endpoint

import express from 'express';
import { SnapAPI } from 'snapapi-js';

const app = express();
const client = new SnapAPI(process.env.SNAPAPI_KEY);

// GET /api/invoice/:id/pdf
app.get('/api/invoice/:id/pdf', async (req, res) => {
  const { id } = req.params;

  // Your invoice URL — the rendered HTML page in your app
  const invoiceUrl = `https://your-app.com/invoices/${id}?print=1`;

  try {
    const pdf = await client.screenshot({
      url: invoiceUrl,
      format: 'pdf',
      full_page: true,
      viewport: { width: 1200, height: 900 },
      // Pass session cookie to authenticate the invoice page
      cookies: [{ name: 'session', value: req.cookies.session, domain: 'your-app.com' }]
    });

    res.set({
      'Content-Type': 'application/pdf',
      'Content-Disposition': `attachment; filename="invoice-${id}.pdf"`,
      'Content-Length': pdf.length
    });
    res.send(pdf);
  } catch (err) {
    console.error('PDF generation failed:', err);
    res.status(500).json({ error: 'PDF generation failed' });
  }
});

Python: PDF Generation

import os
from snapapi import SnapAPI

client = SnapAPI(os.environ["SNAPAPI_KEY"])

# From a URL
pdf_bytes = client.screenshot(
    url="https://your-app.com/reports/monthly",
    format="pdf",
    full_page=True,
    width=1200
)

with open("monthly_report.pdf", "wb") as f:
    f.write(pdf_bytes)

print(f"PDF generated: {len(pdf_bytes) / 1024:.1f} KB")

Generating PDFs from HTML Strings

When you need complete control over the PDF layout — invoice templates, custom reports — pass raw HTML instead of a URL. This is ideal for server-side rendering where the HTML is generated dynamically.

import { SnapAPI } from 'snapapi-js';

const client = new SnapAPI(process.env.SNAPAPI_KEY);

// Generate HTML dynamically (e.g., from a template engine)
const invoiceHtml = generateInvoiceHtml({
  invoiceNumber: 'INV-2026-042',
  customer: { name: 'Acme Corp', email: 'billing@acme.com' },
  lineItems: [
    { description: 'SnapAPI Pro Plan', amount: 7900, quantity: 1 }
  ],
  total: 7900,
  currency: 'USD'
});

const pdf = await client.screenshot({
  html: invoiceHtml,
  format: 'pdf',
  viewport: { width: 1200, height: 900 }
});

// Send via email, store in S3, return as download...

Example Invoice HTML Template

function generateInvoiceHtml({ invoiceNumber, customer, lineItems, total, currency }) {
  const subtotal = lineItems.reduce((sum, item) => sum + item.amount * item.quantity, 0);

  return `<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    body { font-family: Inter, sans-serif; color: #111; max-width: 800px; margin: 0 auto; padding: 48px; }
    .header { display: flex; justify-content: space-between; margin-bottom: 48px; }
    .brand { font-size: 24px; font-weight: 700; color: #6366f1; }
    .invoice-meta { text-align: right; color: #64748b; font-size: 14px; }
    table { width: 100%; border-collapse: collapse; margin: 32px 0; }
    th { text-align: left; padding: 12px 16px; background: #f8fafc; border-bottom: 2px solid #e2e8f0; }
    td { padding: 12px 16px; border-bottom: 1px solid #e2e8f0; }
    .total-row td { font-weight: 700; font-size: 18px; border-bottom: none; }
    .footer { margin-top: 48px; color: #94a3b8; font-size: 12px; }
  </style>
</head>
<body>
  <div class="header">
    <div class="brand">YourCompany</div>
    <div class="invoice-meta">
      <div><strong>Invoice ${invoiceNumber}</strong></div>
      <div>${new Date().toLocaleDateString()}</div>
      <div>Bill to: ${customer.name}</div>
    </div>
  </div>
  <table>
    <thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th></tr></thead>
    <tbody>
      ${lineItems.map(item => `
        <tr>
          <td>${item.description}</td>
          <td>${item.quantity}</td>
          <td>${(item.amount / 100).toFixed(2)} ${currency}</td>
          <td>${(item.amount * item.quantity / 100).toFixed(2)} ${currency}</td>
        </tr>
      `).join('')}
    </tbody>
    <tfoot>
      <tr class="total-row">
        <td colspan="3">Total</td>
        <td>${(total / 100).toFixed(2)} ${currency}</td>
      </tr>
    </tfoot>
  </table>
  <div class="footer">Thank you for your business.</div>
</body></html>`;
}

Common Use Cases

Invoice and Receipt Generation

The most common PDF use case for SaaS products. When a customer upgrades their plan or completes a purchase, generate a PDF invoice and email it or make it available for download. The HTML-string approach works best here because you control the template entirely and do not need to expose an internal invoice URL to an external API.

Report Generation

Analytics dashboards, weekly summaries, compliance reports — all of these are rendered HTML pages in your app that a user might want to export. The URL approach works well: add a ?print=1 parameter to your existing report page that applies print-friendly CSS, then pass the URL to the API with appropriate auth cookies.

Web Archiving and Compliance

Regulatory requirements in finance, legal, and healthcare often require preserving web content at a specific point in time. Generating PDFs of competitor price pages, terms of service, or public filings creates a timestamped, immutable record. Schedule these with a cron job.

// Archive competitor pricing pages daily
const archiveTargets = [
  'https://competitor-a.com/pricing',
  'https://competitor-b.com/pricing'
];

for (const url of archiveTargets) {
  const pdf = await client.screenshot({
    url,
    format: 'pdf',
    full_page: true
  });

  const filename = `archive/${new URL(url).hostname}-${new Date().toISOString().split('T')[0]}.pdf`;
  await s3.putObject({ Bucket: 'archives', Key: filename, Body: pdf }).promise();
  console.log(`Archived: ${filename}`);
}

Documentation and Contract Exports

When users need to share a page from your app with someone who does not have access — a board member, an auditor, a vendor — offering a "Download as PDF" button is a better UX than expecting them to use browser print. Implement it as a one-liner API call behind a button.

PDF Configuration Options

Parameter Default Notes
format "png" Set to "pdf" for PDF output
full_page false true = capture full document height
viewport.width 1280 Use 1200 for A4-friendly output
delay 0 ms to wait after load (for charts/fonts)
cookies [] Array of cookie objects for auth

Tips for High-Quality PDFs

Web font loading tip: Google Fonts and other CDN-hosted fonts load asynchronously. If your PDF shows fallback fonts, add delay: 1000 to wait for fonts to load before capture.

Try SnapAPI Free

200 PDF requests/month on the free tier. No credit card, no setup. Works from cURL, JavaScript, Python, Go, PHP, Swift, and Kotlin.

Get Free API Key →

Frequently Asked Questions

How does the output compare to wkhtmltopdf or WeasyPrint?

Significantly better. wkhtmltopdf uses an outdated WebKit engine and struggles with modern CSS flexbox, grid, and custom fonts. WeasyPrint is Python-only and has limited CSS support. SnapAPI uses a current Chromium engine — the same one Chrome uses — so CSS and JS render identically to what users see in their browsers.

Can I generate multi-page PDFs?

Yes. Set full_page: true and the page will be split into multiple pages automatically based on the rendered document height. Use CSS page-break-before: always to force breaks at specific points in your HTML.

What is the maximum PDF file size?

There is no hard limit, but very long pages (40+ pages) may take 10-15 seconds to generate. For documents over 20 pages, consider splitting the source HTML into logical sections and concatenating the PDFs client-side with a library like pdf-lib.

Can I add headers, footers, and page numbers?

Use CSS @page rules and position: fixed elements in your HTML to create headers and footers that appear on every page. The counter(page) CSS function provides page numbers.