How to Generate PDF-Style Page Captures for Invoices and Reports

2026-04-21 | Tags: [screenshot-api, use-cases, pdf, invoices, reports, tutorials]

Generating PDFs from HTML is notoriously difficult. Libraries like wkhtmltopdf, Puppeteer PDF, and WeasyPrint each have their own quirks with CSS rendering, font loading, and page breaks. For many document use cases — invoices, receipts, reports, certificates — a high-resolution screenshot is a simpler, more reliable alternative: what the browser renders is exactly what gets captured.

This approach trades multi-page reflow for single-page fidelity. It works best for documents that fit on one screen at high resolution, or where you control the page layout to stay within a defined height.

The Core Pattern: A4 Dimensions

Standard A4 paper at 96 DPI is 794 × 1123 pixels. At 2x (for retina/print quality):

import requests

def capture_document(url: str, api_key: str, output_path: str):
    """Capture a URL as a document-quality full-page screenshot."""
    resp = requests.get(
        "https://hermesforge.dev/api/screenshot",
        params={
            "url": url,
            "width": 794,           # A4 width at 96 DPI
            "format": "png",
            "full_page": "true",    # Capture complete document height
            "wait_for": "networkidle",
            "key": api_key
        },
        timeout=30
    )
    resp.raise_for_status()
    with open(output_path, "wb") as f:
        f.write(resp.content)
    return output_path

For letter-size (US): use width=816 (8.5 inches × 96 DPI).

Use Case 1: Invoice Capture

If your invoices are already rendered as web pages (common in Rails, Django, Laravel billing stacks), capture them directly:

import os

def capture_invoice(invoice_id: str, invoice_url: str, api_key: str, output_dir: str) -> str:
    """Screenshot an invoice page and save as a portable image."""
    os.makedirs(output_dir, exist_ok=True)
    output_path = os.path.join(output_dir, f"invoice-{invoice_id}.png")

    if os.path.exists(output_path):
        return output_path  # Already captured — skip

    resp = requests.get(
        "https://hermesforge.dev/api/screenshot",
        params={
            "url": invoice_url,
            "width": 794,
            "format": "png",
            "full_page": "true",
            "wait_for": "networkidle",
            "key": api_key
        },
        timeout=30
    )

    if resp.status_code == 200:
        with open(output_path, "wb") as f:
            f.write(resp.content)
        return output_path

    raise RuntimeError(f"Screenshot failed: HTTP {resp.status_code}")


# Example: batch capture a list of invoice URLs
invoices = [
    {"id": "INV-001", "url": "https://yourapp.com/invoices/INV-001/print"},
    {"id": "INV-002", "url": "https://yourapp.com/invoices/INV-002/print"},
]

for invoice in invoices:
    path = capture_invoice(invoice["id"], invoice["url"], "YOUR_API_KEY", "invoices/")
    print(f"Captured: {path}")

Most web frameworks have a "print view" route that strips navigation and renders clean document layout — use that URL, not the regular invoice view.

Use Case 2: Report Generation Pipeline

For scheduled reports (weekly summaries, monthly analytics, compliance snapshots):

import time
from datetime import date

REPORT_PAGES = [
    {"name": "weekly-summary",    "url": "https://app.example.com/reports/weekly?export=true"},
    {"name": "revenue-dashboard", "url": "https://app.example.com/reports/revenue?export=true"},
    {"name": "user-growth",       "url": "https://app.example.com/reports/growth?export=true"},
]

def generate_report_captures(pages: list, api_key: str) -> list[dict]:
    today = date.today().isoformat()
    output_dir = f"reports/{today}"
    os.makedirs(output_dir, exist_ok=True)

    results = []
    for page in pages:
        output_path = os.path.join(output_dir, f"{page['name']}.png")

        resp = requests.get(
            "https://hermesforge.dev/api/screenshot",
            params={
                "url": page["url"],
                "width": 1200,          # Wider for dashboard-style reports
                "format": "png",
                "full_page": "true",
                "wait_for": "networkidle",
                "key": api_key
            },
            timeout=45
        )

        if resp.status_code == 200:
            with open(output_path, "wb") as f:
                f.write(resp.content)
            results.append({"name": page["name"], "path": output_path, "status": "ok"})
            print(f"OK: {page['name']}")
        else:
            results.append({"name": page["name"], "status": "failed"})
            print(f"FAILED: {page['name']}")

        time.sleep(1)

    return results

Use Case 3: Certificate and Receipt Generation

For completion certificates, event tickets, or payment receipts where you need a shareable image:

def capture_certificate(
    template_url: str,
    recipient_name: str,
    course_name: str,
    completion_date: str,
    api_key: str
) -> bytes:
    """
    Capture a certificate template page populated with recipient data.
    Assumes the template accepts query parameters.
    """
    import urllib.parse

    params = {
        "name": recipient_name,
        "course": course_name,
        "date": completion_date,
        "print": "true"
    }
    full_url = f"{template_url}?{urllib.parse.urlencode(params)}"

    resp = requests.get(
        "https://hermesforge.dev/api/screenshot",
        params={
            "url": full_url,
            "width": 1122,      # A4 landscape width (297mm at 96DPI)
            "format": "png",
            "full_page": "false",
            "wait_for": "networkidle",
            "key": api_key
        },
        timeout=30
    )
    resp.raise_for_status()
    return resp.content


# Example
image_bytes = capture_certificate(
    template_url="https://yourapp.com/certificates/template",
    recipient_name="Jane Smith",
    course_name="Python for Data Science",
    completion_date="2026-05-24",
    api_key="YOUR_API_KEY"
)

with open("certificate-jane-smith.png", "wb") as f:
    f.write(image_bytes)

Designing Pages for Document Capture

For best results, the page being captured should:

Example CSS for a print-optimized layout:

/* Applied when ?print=true is set */
body.print-mode {
    width: 794px;
    margin: 0 auto;
    padding: 40px;
    font-family: 'Inter', sans-serif;
    background: white;
}

body.print-mode nav,
body.print-mode footer,
body.print-mode .sidebar {
    display: none;
}

When to Use Screenshots vs. Real PDFs

Scenario Screenshot Real PDF
Single-page document with fixed layout Ideal Overkill
Multi-page document with page breaks Not suitable Required
Need text selection/search in output Not suitable Required
Need exact CMYK color for print Not suitable Required
Need to embed fonts in output file Not needed Complex
Fastest implementation Yes No
Consistent rendering across viewers Yes Varies by viewer

For invoices, receipts, certificates, and single-page reports: screenshot approach works well. For multi-page legal documents, technical manuals, or print-production assets: use a proper PDF library.


Hermesforge Screenshot API: Get an API key. 50 captures/day free — enough for small-batch invoice and report generation with no signup required.