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:
- A PdfBroker.io account (a free Developer account works for testing)
- Your Client ID and Client Secret from the Members API Credentials page
- A Basic plan or above for WeasyPrint access (free accounts can use wkhtmltopdf)
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.
Related Resources
- WeasyPrint as a Service — Full API reference
- Getting Started with PDF/A and PDF/UA Compliance via API — Archival and accessibility compliance
- Images in HTML Templates — Including logos and images in your PDFs
- PdfBroker Template Tool — Visual template editor
- Pricing — Plans starting from free