Skip to content

PdfBroker.io is a cloud-based PDF generation REST API that converts HTML and CSS templates into professional PDF documents. This tutorial shows you how to generate PDF invoices by sending an HTML template to the PdfBroker.io WeasyPrint endpoint, which returns a ready-to-send PDF — complete with proper fonts, layout, and optional PDF/A compliance for archiving.

Prerequisites

Before you begin, you need:

Step 1: Design Your HTML Invoice Template

Start with a clean HTML template. PdfBroker.io's WeasyPrint service supports modern CSS including flexbox, grid, @page rules, and print-specific styles. Here is a complete invoice template you can use as a starting point:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
  @page {
    size: A4;
    margin: 20mm;
    @bottom-center {
      content: "Page " counter(page) " of " counter(pages);
      font-size: 9px;
      color: #888;
    }
  }
  body {
    font-family: 'Helvetica Neue', Arial, sans-serif;
    color: #333;
    line-height: 1.5;
  }
  .header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 30px;
  }
  .company-name {
    font-size: 24px;
    font-weight: bold;
    color: #6462a8;
  }
  .invoice-title {
    font-size: 28px;
    text-align: right;
    color: #6462a8;
  }
  .invoice-meta {
    text-align: right;
    font-size: 13px;
    color: #666;
  }
  .addresses {
    display: flex;
    justify-content: space-between;
    margin-bottom: 30px;
  }
  .addresses div { width: 45%; }
  .label {
    font-weight: bold;
    font-size: 12px;
    text-transform: uppercase;
    color: #888;
    margin-bottom: 5px;
  }
  table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 30px;
  }
  thead th {
    background: #6462a8;
    color: white;
    padding: 10px 12px;
    text-align: left;
    font-size: 13px;
  }
  thead th:last-child,
  tbody td:last-child { text-align: right; }
  tbody td {
    padding: 10px 12px;
    border-bottom: 1px solid #eee;
    font-size: 13px;
  }
  tbody tr:nth-child(even) { background: #f9f9f9; }
  .totals {
    width: 300px;
    margin-left: auto;
  }
  .totals table { margin-bottom: 0; }
  .totals td {
    padding: 6px 12px;
    border-bottom: none;
  }
  .totals .grand-total td {
    font-size: 16px;
    font-weight: bold;
    border-top: 2px solid #6462a8;
    color: #6462a8;
  }
  .footer {
    margin-top: 40px;
    font-size: 11px;
    color: #999;
    text-align: center;
  }
</style>
</head>
<body>
  <div class="header">
    <div>
      <div class="company-name">Acme Corp</div>
      <div>123 Business Street<br>Stockholm, Sweden</div>
    </div>
    <div>
      <div class="invoice-title">INVOICE</div>
      <div class="invoice-meta">
        Invoice #: INV-2026-001<br>
        Date: 2026-02-17<br>
        Due: 2026-03-17
      </div>
    </div>
  </div>

  <div class="addresses">
    <div>
      <div class="label">Bill To</div>
      <strong>Customer AB</strong><br>
      456 Client Avenue<br>
      Gothenburg, Sweden<br>
      VAT: SE123456789001
    </div>
    <div>
      <div class="label">Payment Details</div>
      Bank: Nordea<br>
      IBAN: SE12 3456 7890 1234<br>
      BIC: NDEASESS<br>
      Reference: INV-2026-001
    </div>
  </div>

  <table>
    <thead>
      <tr>
        <th>Description</th>
        <th>Qty</th>
        <th>Unit Price</th>
        <th>Amount</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>PDF Generation API — Professional Plan (Feb 2026)</td>
        <td>1</td>
        <td>€29.99</td>
        <td>€29.99</td>
      </tr>
      <tr>
        <td>Custom template development (4 hours)</td>
        <td>4</td>
        <td>€95.00</td>
        <td>€380.00</td>
      </tr>
      <tr>
        <td>Additional API requests (5,000 overage)</td>
        <td>5,000</td>
        <td>€0.01</td>
        <td>€50.00</td>
      </tr>
    </tbody>
  </table>

  <div class="totals">
    <table>
      <tr><td>Subtotal</td><td>€459.99</td></tr>
      <tr><td>VAT (25%)</td><td>€115.00</td></tr>
      <tr class="grand-total"><td>Total</td><td>€574.99</td></tr>
    </table>
  </div>

  <div class="footer">
    Thank you for your business. Payment is due within 30 days.
  </div>
</body>
</html>

This template uses CSS @page rules for print-specific layout including automatic page numbering — a feature fully supported by PdfBroker.io's WeasyPrint service.

Step 2: Authenticate with the API

PdfBroker.io uses OAuth2 Client Credentials. Exchange your credentials for an access token before making API calls. See the Authentication documentation for full details.

curl -X POST https://login.pdfbroker.io/connect/token \
  -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET"

The response includes an access_token valid for a limited time. The PdfBroker.Client NuGet package handles token management automatically in .NET.

Step 3: Generate the Invoice PDF

Using C# with PdfBroker.Client

The simplest approach for .NET developers. The NuGet package handles authentication and request formatting:

using PdfBroker.Client;
using PdfBroker.Common.RequestObjects;

var client = new PdfBrokerClientService("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");

// Load your HTML template and inject dynamic invoice data
var html = File.ReadAllText("invoice-template.html")
    .Replace("INV-2026-001", invoiceNumber)
    .Replace("€574.99", totalAmount);

var request = new WeasyPrintRequestDto
{
    HtmlBase64String = Convert.ToBase64String(
        System.Text.Encoding.UTF8.GetBytes(html)),
    WeasyPrintToPdfArguments = new Dictionary<string, string>
    {
        { "pdf-variant", "pdf/a-3b" }  // Archive-ready PDF
    }
};

byte[] pdfBytes = await client.WeasyPrintAsByteArrayAsync(request);
await File.WriteAllBytesAsync($"invoices/{invoiceNumber}.pdf", pdfBytes);

Using C# with HttpClient

If you prefer raw HTTP calls without the NuGet package:

using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

// Authenticate
using var tokenClient = new HttpClient();
var tokenResponse = await tokenClient.PostAsync(
    "https://login.pdfbroker.io/connect/token",
    new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "client_credentials",
        ["client_id"] = "YOUR_CLIENT_ID",
        ["client_secret"] = "YOUR_CLIENT_SECRET"
    }));

var tokenJson = await tokenResponse.Content.ReadAsStringAsync();
var accessToken = JsonDocument.Parse(tokenJson)
    .RootElement.GetProperty("access_token").GetString();

// Build the request
var html = await File.ReadAllTextAsync("invoice-template.html");
var payload = new
{
    htmlBase64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(html)),
    weasyPrintToPdfArguments = new Dictionary<string, string>
    {
        { "pdf-variant", "pdf/a-3b" }
    }
};

using var apiClient = new HttpClient();
apiClient.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", accessToken);

var response = await apiClient.PostAsJsonAsync(
    "https://api.pdfbroker.io/api/pdf/weasyprint", payload);

response.EnsureSuccessStatusCode();
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("invoice.pdf", pdfBytes);

Using cURL

Useful for quick testing. Encode your HTML to base64 first:

# Encode the HTML template
HTML_BASE64=$(base64 -w 0 invoice-template.html)

# Get access token
ACCESS_TOKEN=$(curl -s -X POST https://login.pdfbroker.io/connect/token \
  -d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET" \
  | jq -r '.access_token')

# Generate the PDF
curl -X POST https://api.pdfbroker.io/api/pdf/weasyprint \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"htmlBase64String\": \"$HTML_BASE64\",
    \"weasyPrintToPdfArguments\": {
      \"pdf-variant\": \"pdf/a-3b\"
    }
  }" \
  --output invoice.pdf

Using Python

import requests
import base64

# Authenticate
token_response = requests.post(
    "https://login.pdfbroker.io/connect/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
    },
)
access_token = token_response.json()["access_token"]

# Load and encode the HTML template
with open("invoice-template.html", "r", encoding="utf-8") as f:
    html_base64 = base64.b64encode(f.read().encode("utf-8")).decode("ascii")

# Generate the PDF
response = requests.post(
    "https://api.pdfbroker.io/api/pdf/weasyprint",
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "htmlBase64String": html_base64,
        "weasyPrintToPdfArguments": {"pdf-variant": "pdf/a-3b"},
    },
)

with open("invoice.pdf", "wb") as f:
    f.write(response.content)

Step 4: Making It Dynamic

In a real application, you would populate the template with data from your database or billing system. A clean approach is to use string replacement on a template file, or a templating engine like Razor or Jinja2 on the server side before sending HTML to the API.

For .NET applications, a reusable invoice service might look like this:

public class InvoiceService
{
    private readonly PdfBrokerClientService _pdfClient;
    private readonly string _templateHtml;

    public InvoiceService(PdfBrokerClientService pdfClient)
    {
        _pdfClient = pdfClient;
        _templateHtml = File.ReadAllText("invoice-template.html");
    }

    public async Task<byte[]> GenerateInvoicePdfAsync(Invoice invoice)
    {
        var html = _templateHtml
            .Replace("{{InvoiceNumber}}", invoice.Number)
            .Replace("{{Date}}", invoice.Date.ToString("yyyy-MM-dd"))
            .Replace("{{DueDate}}", invoice.DueDate.ToString("yyyy-MM-dd"))
            .Replace("{{CustomerName}}", invoice.CustomerName)
            .Replace("{{LineItems}}", RenderLineItems(invoice.Items))
            .Replace("{{Total}}", invoice.Total.ToString("C", new CultureInfo("sv-SE")));

        var request = new WeasyPrintRequestDto
        {
            HtmlBase64String = Convert.ToBase64String(
                Encoding.UTF8.GetBytes(html)),
            WeasyPrintToPdfArguments = new Dictionary<string, string>
            {
                { "pdf-variant", "pdf/a-3b" }
            }
        };

        return await _pdfClient.WeasyPrintAsByteArrayAsync(request);
    }

    private static string RenderLineItems(IEnumerable<InvoiceItem> items)
    {
        var sb = new StringBuilder();
        foreach (var item in items)
        {
            sb.Append($"""
                <tr>
                    <td>{item.Description}</td>
                    <td>{item.Quantity}</td>
                    <td>€{item.UnitPrice:F2}</td>
                    <td>€{item.Quantity * item.UnitPrice:F2}</td>
                </tr>
                """);
        }
        return sb.ToString();
    }
}

You can also use the PdfBroker Template Tool to design your invoice visually and export the HTML directly.

Tips for Production Invoice Generation

Use PDF/A for archiving. If you need to store invoices long-term for tax or compliance purposes, generate them as PDF/A by adding "pdf-variant": "pdf/a-3b" to the WeasyPrint arguments. PdfBroker.io's WeasyPrint service supports PDF/A-1b, PDF/A-2b, PDF/A-3b, and PDF/A-4b variants.

Include embedded fonts. If your template uses custom fonts, embed them as base64 in the CSS @font-face rule or provide them in the resources object. See the Working with Fonts documentation.

Handle the response correctly. PdfBroker.io returns the PDF as a binary response by default. If you need a JSON response containing the PDF as a base64 string, set the Accept header to application/json. See Content Negotiation for details.

Secure your data. PdfBroker.io processes your HTML in memory and does not store your document data after the response is returned. Your invoice data stays private.

Summary

Generating PDF invoices with PdfBroker.io follows a straightforward workflow: design an HTML template, inject your data, and POST it to the WeasyPrint API endpoint. The service handles rendering, font embedding, and PDF compliance standards so you can focus on your application logic.