Pay-per-use · No subscriptions

HTML to PDF in Python — without WeasyPrint or a headless browser

One POST request. No binary install. Pay per page.

Generate pixel-perfect PDFs from HTML in any Python application — FastAPI, Django, Flask, Celery workers, Lambda functions, CLI scripts — by calling a REST API. Skip the WeasyPrint dependency hell, the wkhtmltopdf Docker layer, and the headless Chrome memory bill.

Why developers choose HTML2DocHub

Works with any Python framework: FastAPI, Django, Flask, Starlette, Celery
Sync (requests, httpx) and async (httpx.AsyncClient, aiohttp) clients
No system dependencies — no wkhtmltopdf, no Chromium, no Cairo / Pango
Serverless-friendly — AWS Lambda and GCP Cloud Functions work out of the box
Full Chromium rendering — same engine as Chrome, unlike WeasyPrint's subset
Idempotency keys for safe retries inside Celery tasks
Async mode + webhook callback for long-running renders
Pay ₹0.08 per page — no monthly commitment

Code Examples

requests (synchronous)python
import requests

API_KEY = "sk_live_YOUR_KEY"

def render_pdf(html: str) -> bytes:
    resp = requests.post(
        "https://api.html2dochub.com/v1/render",
        headers={"X-API-Key": API_KEY},
        json={
            "type": "pdf",
            "html": html,
            "options": {"format": "A4", "margin": "18mm"},
        },
        timeout=60,
    )
    resp.raise_for_status()
    job = resp.json()
    return requests.get(job["download_url"], timeout=60).content

pdf_bytes = render_pdf("<h1>Invoice #INV-0042</h1><p>Total: ₹1,200</p>")
open("invoice.pdf", "wb").write(pdf_bytes)
httpx (async)python
import httpx

async def render_pdf(html: str) -> bytes:
    async with httpx.AsyncClient(timeout=60) as client:
        resp = await client.post(
            "https://api.html2dochub.com/v1/render",
            headers={"X-API-Key": API_KEY},
            json={
                "type": "pdf",
                "html": html,
                "options": {"format": "A4"},
            },
        )
        resp.raise_for_status()
        job = resp.json()

        pdf_resp = await client.get(job["download_url"])
        return pdf_resp.content
Django — FileResponse from a viewpython
from django.http import FileResponse
from django.template.loader import render_to_string
import requests, io

def invoice_pdf(request, invoice_id: int):
    html = render_to_string("invoices/detail.html", {"invoice": Invoice.objects.get(pk=invoice_id)})

    resp = requests.post(
        "https://api.html2dochub.com/v1/render",
        headers={"X-API-Key": settings.HTML2DOCHUB_API_KEY},
        json={"type": "pdf", "html": html, "options": {"format": "A4"}},
        timeout=60,
    )
    resp.raise_for_status()
    pdf = requests.get(resp.json()["download_url"], timeout=60).content

    return FileResponse(io.BytesIO(pdf), filename=f"invoice-{invoice_id}.pdf",
                        content_type="application/pdf")
Celery — async render with idempotencypython
from celery import shared_task
import requests, uuid

@shared_task(bind=True, max_retries=3, default_retry_backoff=True)
def generate_report_pdf(self, report_id: int):
    report = Report.objects.get(pk=report_id)
    idempotency_key = f"report-{report_id}-v{report.version}"

    try:
        resp = requests.post(
            "https://api.html2dochub.com/v1/render",
            headers={"X-API-Key": settings.HTML2DOCHUB_API_KEY},
            json={
                "type": "pdf",
                "mode": "async",
                "html": report.render_html(),
                "webhook_url": f"{settings.BASE_URL}/webhooks/pdf-ready",
                "tag": f"report:{report_id}",
                "idempotency_key": idempotency_key,
            },
            timeout=30,
        )
        resp.raise_for_status()
    except requests.RequestException as exc:
        raise self.retry(exc=exc)

    report.job_id = resp.json()["id"]
    report.save()

Simple, transparent pricing

Pay only for pages rendered. No subscriptions. No minimum monthly fee.

1 page PDF:~₹0.10
10 page PDF:~₹0.80
100 pages/day:~₹8/day
See full pricing details

Frequently Asked Questions

How is this different from WeasyPrint?+
WeasyPrint is a pure-Python HTML renderer that only supports a subset of CSS (no flexbox gaps, limited grid, no shadow DOM). HTML2DocHub uses real Chromium, so anything that renders in Chrome renders identically in your PDF. You also skip the Cairo, Pango, and GDK-PixBuf system dependencies that make WeasyPrint painful in Docker.
What about xhtml2pdf or pdfkit?+
xhtml2pdf is based on ReportLab and handles only very basic HTML. pdfkit wraps wkhtmltopdf, which is unmaintained since 2023. Both fail on modern CSS (grid, flexbox gap, custom properties). If your templates use any CSS written in the last 5 years, a Chromium-backed API is the safer choice.
Can I use this in AWS Lambda?+
Yes. Because HTML2DocHub is an HTTP API, your Lambda has zero binary dependencies — just install requests or httpx in your layer. No more fighting with Chromium ARM builds or 256 MB deployment limits.
Do I need to worry about rate limits from Celery workers?+
Each API key has a per-minute rate limit (default 60 req/min, configurable). Celery retries with exponential backoff will respect it naturally. For high-throughput pipelines, create a dedicated API key and request a rate-limit bump.
Is there an official Python SDK?+
Not yet — the API is so small that requests + a 10-line wrapper covers 99% of use cases. An official python-html2dochub package is planned for 2026 Q3.

Start rendering PDFs today

Free account. No credit card required. API ready in minutes.

Get your free API key