How to Convert URLs and HTML to PDF with Python

Published April 2, 2026 · 6 min read · URLSnap Team

Generating PDFs from web pages or HTML templates is a common task — invoices, reports, receipts, documentation snapshots. This tutorial shows you how to do it cleanly in Python using the URLSnap API, no wkhtmltopdf binary, no WeasyPrint C dependencies, no Selenium setup.

Prerequisites

pip install requests

That's it. No system-level dependencies needed.

Get a Free API Key

import requests

resp = requests.post(
    'https://urlsnap.dev/api/register',
    json={'email': 'you@example.com'}
)
print(resp.json())
# {'key': 'us_abc123...', 'message': 'Free tier: 20 requests/day...'}

Convert a URL to PDF

import requests

API_KEY = 'us_your_key_here'

def url_to_pdf(url: str, output_path: str, **kwargs):
    """
    Download a URL as a PDF file.
    
    kwargs: format (A4/Letter/Legal), landscape (bool), margin (css string)
    """
    params = {'url': url, **kwargs}
    response = requests.get(
        'https://urlsnap.dev/api/pdf',
        params=params,
        headers={'x-api-key': API_KEY},
    )
    response.raise_for_status()
    with open(output_path, 'wb') as f:
        f.write(response.content)
    print(f'Saved PDF to {output_path}')

# Basic usage
url_to_pdf('https://example.com', 'page.pdf')

# Letter-size landscape
url_to_pdf(
    'https://en.wikipedia.org/wiki/Python_(programming_language)',
    'python-wiki.pdf',
    format='Letter',
    landscape='true',
    margin='15px',
)
📄 Supported formats: A4, Letter, Legal, A3, Tabloid.

Convert HTML to PDF

Got a Jinja2 template or a dynamic HTML string? Post it directly:

import requests
from jinja2 import Template

API_KEY = 'us_your_key_here'

def html_to_pdf(html: str, output_path: str):
    response = requests.post(
        'https://urlsnap.dev/api/pdf',
        json={'html': html, 'format': 'A4'},
        headers={'x-api-key': API_KEY},
    )
    response.raise_for_status()
    with open(output_path, 'wb') as f:
        f.write(response.content)

# Example: render an invoice template
INVOICE_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: sans-serif; padding: 40px; color: #111; }
    h1 { color: #6366f1; }
    table { width: 100%; border-collapse: collapse; margin-top: 20px; }
    th, td { padding: 10px; border-bottom: 1px solid #eee; text-align: left; }
    .total { font-weight: bold; font-size: 1.1em; }
  </style>
</head>
<body>
  <h1>Invoice #{{ invoice_id }}</h1>
  <p>Bill to: {{ customer }}</p>
  <table>
    <tr><th>Item</th><th>Amount</th></tr>
    {% for item in items %}
    <tr><td>{{ item.name }}</td><td>${{ item.amount }}</td></tr>
    {% endfor %}
    <tr class="total"><td>Total</td><td>${{ total }}</td></tr>
  </table>
</body>
</html>
"""

invoice_html = Template(INVOICE_TEMPLATE).render(
    invoice_id='INV-2026-042',
    customer='Acme Corp',
    items=[
        {'name': 'API Starter Plan', 'amount': '9.00'},
        {'name': 'Setup fee', 'amount': '0.00'},
    ],
    total='9.00',
)

html_to_pdf(invoice_html, 'invoice.pdf')

Async Usage with httpx

For high-throughput use in async Python applications (FastAPI, etc.):

import httpx
import asyncio

API_KEY = 'us_your_key_here'

async def url_to_pdf_async(url: str) -> bytes:
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            'https://urlsnap.dev/api/pdf',
            params={'url': url, 'format': 'A4'},
            headers={'x-api-key': API_KEY},
            timeout=60.0,
        )
        resp.raise_for_status()
        return resp.content

async def main():
    urls = [
        'https://example.com',
        'https://httpbin.org/html',
    ]
    pdfs = await asyncio.gather(*[url_to_pdf_async(u) for u in urls])
    for i, pdf in enumerate(pdfs):
        with open(f'output_{i}.pdf', 'wb') as f:
            f.write(pdf)
        print(f'Saved {len(pdf)//1024}KB PDF')

asyncio.run(main())

Check Your Quota

import requests

API_KEY = 'us_your_key_here'

info = requests.get(
    'https://urlsnap.dev/api/me',
    headers={'x-api-key': API_KEY}
).json()

print(f"Plan: {info['plan']}")
print(f"Used today: {info['requests_today']}/{info['daily_limit']}")
print(f"Total all time: {info['requests_total']}")

Error Handling

import requests

def safe_url_to_pdf(url: str, output_path: str, api_key: str):
    try:
        resp = requests.get(
            'https://urlsnap.dev/api/pdf',
            params={'url': url},
            headers={'x-api-key': api_key},
            timeout=60,
        )
        if resp.status_code == 429:
            print("Daily limit reached. Upgrade at urlsnap.dev/pricing")
            return False
        if resp.status_code == 401:
            print("Invalid API key.")
            return False
        resp.raise_for_status()
        with open(output_path, 'wb') as f:
            f.write(resp.content)
        return True
    except requests.exceptions.Timeout:
        print(f"Timeout generating PDF for {url}")
        return False
    except requests.exceptions.RequestException as e:
        print(f"Error: {e}")
        return False

Start generating PDFs today

Free tier: 20 PDFs/day. No credit card, no install, no config.

Get your free API key →