PDFPipe

Guide

HTML to PDF in Flask

Generating PDFs inside a Flask app usually leads to either WeasyPrint (limited CSS support, no JavaScript) or Playwright (a 150 MB Chromium binary in your Docker image that leaks memory under load). PDFPipe is a single HTTP call that returns a production-quality PDF, with nothing extra to install.

Basic example

Install requests, set your API key as an environment variable, and add a route:

python
# pip install flask requests
import os, requests
from flask import Flask, Response, abort

app = Flask(__name__)
PDFPIPE_KEY = os.environ["PDFPIPE_API_KEY"]

@app.route("/invoice/<order_id>.pdf")
def invoice(order_id):
    html = f"""
    <html>
    <body style="font-family: system-ui; padding: 40px">
      <h1>Invoice #{order_id}</h1>
      <p>Thank you for your order.</p>
    </body>
    </html>
    """
    r = requests.post(
        "https://api.pdfpipe.xyz/v1/pdf",
        headers={"Authorization": f"Bearer {PDFPIPE_KEY}"},
        json={"html": html, "options": {"format": "A4"}},
        timeout=60,
    )
    if not r.ok:
        abort(502, r.json().get("detail", "render failed"))
    return Response(
        r.content,
        mimetype="application/pdf",
        headers={"Content-Disposition": f'attachment; filename="invoice-{order_id}.pdf"'},
    )

The PDF streams directly from the API response to the browser. No temp files, no disk writes.

Using Jinja2 templates

Flask ships with Jinja2. You can render your HTML template server-side (filling in order data, customer names, line items) and send the result to PDFPipe:

python
# render_template_string fills in your Jinja2 variables,
# then pass the rendered HTML straight to PDFPipe.
from flask import render_template_string

INVOICE_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<style>
  body { font-family: system-ui; margin: 40px; color: #1d1812 }
  table { width: 100%; border-collapse: collapse; margin-top: 24px }
  td, th { padding: 10px; border-bottom: 1px solid #e5e7eb }
  .total { text-align: right; font-size: 1.5rem; font-weight: bold }
</style>
</head>
<body>
  <h1>Invoice #{{ order.id }}</h1>
  <p>{{ order.customer_name }} · {{ order.date }}</p>
  <table>
    {% for item in order.items %}
    <tr><td>{{ item.name }}</td><td style="text-align:right">{{ item.amount }}</td></tr>
    {% endfor %}
  </table>
  <p class="total">Total: {{ order.total }}</p>
</body>
</html>
"""

@app.route("/invoice/<order_id>.pdf")
def invoice(order_id):
    order = get_order(order_id)  # your data source
    html = render_template_string(INVOICE_TEMPLATE, order=order)
    r = requests.post(
        "https://api.pdfpipe.xyz/v1/pdf",
        headers={"Authorization": f"Bearer {PDFPIPE_KEY}"},
        json={"html": html, "options": {"format": "A4"}},
        timeout=60,
    )
    if not r.ok:
        abort(502, r.json().get("detail", "render failed"))
    return Response(r.content, mimetype="application/pdf")

Store and retrieve later

Pass store: true to keep the PDF on the CDN and get a retrieval URL back in the response headers. Useful when you want to email a link rather than attach a file:

python
# Store the PDF and return a retrieval URL
r = requests.post(
    "https://api.pdfpipe.xyz/v1/pdf",
    headers={"Authorization": f"Bearer {PDFPIPE_KEY}"},
    json={
        "html": html,
        "options": {"format": "A4"},
        "store": True,
        "filename": f"invoice-{order_id}.pdf",
    },
    timeout=60,
)
doc_id  = r.headers.get("X-PDFPipe-Document-Id")
doc_url = r.headers.get("X-PDFPipe-Document-Url")
# doc_url is a direct download link valid for your plan's retention window

Getting an API key

The Hobby plan gives you 500 free documents a month with no credit card. Paid plans start at $19 and include email support, a longer archive window, and higher monthly limits.

Full API reference on the docs page. Try it now in the live playground with no signup.

Get an API key →