Build an Automated Invoice Generator using HTML & Python

If you are building an e-commerce platform, a SaaS app, or just managing your own freelance work, you eventually hit the "Invoice Problem." You have data in a database, but your customers need a clean, professional PDF invoice.

Trying to build PDFs from scratch using low-level Python libraries like ReportLab is painful. It involves calculating x/y coordinates for every line of text.

A much smarter approach is to design your invoice using standard HTML/CSS, and then use an API to convert that HTML into a PDF.

In this tutorial, we will build a Python script that takes raw invoice data, merges it into an HTML template, and generates a professional PDF using the free aPDF.io API.

The Workflow

Here is the architecture of what we are building:
  1. Data Source: A simple Python dictionary (mimicking a database).
  2. Template Engine: We use Jinja2 to inject data into HTML.
  3. PDF Engine: We send the rendered HTML to aPDF.io.
  4. Result: We get a download link for the PDF.

Step 1: The Setup

You will need your API Token.
  1. Go to aPDF.io.
  2. Sign up (it's free).
  3. Copy your API Token from the dashboard.

Next, install the requests and jinja2 library if you haven't already:

pip install requests jinja2

Step 2: Create the HTML Invoice Template

Create a file named invoice_template.html. This is standard HTML. Notice the {{ double_curly_braces }} ? That is where Python will inject our data.
<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: 'Helvetica', sans-serif; color: #333; }
        .box { max-width: 800px; margin: auto; padding: 30px; border: 1px solid #eee; }
        .header { display: flex; justify-content: space-between; margin-bottom: 20px; }
        .title { font-size: 24px; font-weight: bold; color: #2c3e50; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
        .total { text-align: right; margin-top: 20px; font-size: 18px; font-weight: bold; }
    </style>
</head>
<body>
    <div class="box">
        <div class="header">
            <div>
                <div class="title">INVOICE</div>
                <p>Invoice #: {{ invoice_id }}<br>Date: {{ date }}</p>
            </div>
            <div style="text-align: right;">
                <strong>{{ company_name }}</strong><br>
                {{ company_email }}
            </div>
        </div>

        <p><strong>Bill To:</strong><br>
        {{ client_name }}<br>
        {{ client_address }}</p>

        <table>
            <thead>
                <tr>
                    <th>Item</th>
                    <th>Quantity</th>
                    <th>Price</th>
                    <th>Total</th>
                </tr>
            </thead>
            <tbody>
                {% for item in items %}
                <tr>
                    <td>{{ item.name }}</td>
                    <td>{{ item.qty }}</td>
                    <td>USD {{ item.price }}</td>
                    <td>USD {{ item.total }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>

        <div class="total">
            Grand Total: USD {{ grand_total }}
        </div>
    </div>
</body>
</html>

Step 3: The Python Generator Script

Create a file named generate_invoice.py. This script reads the template, fills it with data, and sends it to the API.
import requests
from jinja2 import Template

# 1. Your API Token
API_TOKEN = "YOUR_API_TOKEN_HERE"
API_URL = "https://apdf.io/api/pdf/file/create"

# 2. Mock Data (In a real app, this comes from your DB)
invoice_data = {
    "invoice_id": "INV-2025-001",
    "date": "November 21, 2025",
    "company_name": "Tech Solutions Inc.",
    "company_email": "billing@techsolutions.com",
    "client_name": "John Doe Enterprises",
    "client_address": "123 Market St, San Francisco, CA",
    "items": [
        {"name": "Web Development Service", "qty": 1, "price": 500.00, "total": 500.00},
        {"name": "Server Maintenance", "qty": 2, "price": 100.00, "total": 200.00},
        {"name": "SSL Certificate", "qty": 1, "price": 50.00, "total": 50.00},
    ],
    "grand_total": 750.00
}

# 3. Load and Render the HTML Template
with open("invoice_template.html", "r") as file:
    template_content = file.read()

jinja_template = Template(template_content)
rendered_html = jinja_template.render(invoice_data)

# 4. Send to API
print("Generating PDF...")

try:
    response = requests.post(
        API_URL,
        headers={
            'Authorization': f'Bearer {API_TOKEN}'
        },
        data={
            'html': rendered_html,
            # Optional Layout Parameters
            'format': 'a4',
            'margin_top': '20',
            'margin_bottom': '20',
            'margin_left': '20',
            'margin_right': '20'
        }
    )

    # 5. Handle the Response
    if response.status_code == 200:
        result = response.json()
        print("Success!")
        print(f"Download your invoice here: {result['file']}")

        # Optional: Print file expiration or size
        print(f"Pages: {result['pages']}")
        print(f"Size: {result['size']} bytes")
    else:
        print(f"Error {response.status_code}: {response.text}")

except Exception as e:
    print(f"An error occurred: {e}")

Run the script

python generate_invoice.py

Output

Generating PDF...
Success!
Download your invoice here: https://apdf-files.s3.eu-central-1.amazonaws.com/6f1674e2703953aa.pdf
Pages: 1
Size: 12405 bytes
That's it. You have a URL you can email directly to your customer, or download and save to your database.

Next Steps

Now that you have automated invoice generation, here are a few things you can try next:
  • Secure the Invoice: Use the Password Protection endpoint to lock the PDF with the client's ID.
  • Store Efficiently: If your invoices have high-resolution logos, use the Compress endpoint to shrink the file size before archiving.
Ready to build?
Get your free API token here
Most APIs charge you per document. aPDF.io is built to be a developer-friendly, free alternative that handles the heavy lifting without the monthly subscription.