How to Generate PDFs from HTML with a REST API
PdfBroker.io is a cloud-based REST API that generates PDF documents from HTML and CSS. You send an HTML string to the API and receive a rendered PDF in return — no SDK installation, no local rendering engine, no server-side dependencies. This guide walks through the complete workflow: writing an HTML template, authenticating, calling the API, and handling the response, with working code examples in C#, Python, and cURL.
Why Use a REST API for PDF Generation?
Most developers who need to generate PDFs in their applications face a choice: install a local library or use a cloud service. Local libraries like wkhtmltopdf, Puppeteer, or iTextSharp work, but they come with operational baggage — binary dependencies that complicate Docker images, version conflicts across environments, memory issues under load, and platform-specific rendering differences.
A REST API eliminates all of that. Your application sends HTML over HTTPS and gets back a PDF. The rendering engine runs on someone else's infrastructure, scales independently, and produces consistent output regardless of your deployment environment. The tradeoff is a network round-trip and a dependency on an external service — but for most applications, the operational simplicity is worth it.
PdfBroker.io's Two Rendering Engines
PdfBroker.io offers two HTML-to-PDF rendering engines, each with different strengths:
WeasyPrint (Premium)
WeasyPrint is a CSS Paged Media renderer. It excels at producing print-quality documents with precise layout control — think invoices, reports, contracts, and certificates. Key features:
- Full CSS Paged Media support (
@pagerules, named pages, page margins, headers/footers) - PDF/A compliance (archival documents)
- PDF/UA compliance (accessible documents)
- Embedded font support via the
resourcesobject - No JavaScript execution (purely CSS-driven rendering)
WeasyPrint requires a Basic plan (€8.95/mo) or above.
wkhtmltopdf (All Plans)
wkhtmltopdf uses a WebKit browser engine to render HTML. It handles JavaScript execution, making it suitable for pages that depend on client-side rendering. Key features:
- JavaScript execution (charts, dynamic content)
- Extensive command-line arguments for controlling output
- Available on the free tier (200 requests/month)
- Good for "screenshot-style" PDF captures of web pages
Step 1: Sign Up and Get Credentials
Create a free account at pdfbroker.io/signup. After confirming your email, navigate to the members area to find your API credentials:
- Client ID — identifies your application
- Client Secret — used to authenticate
These credentials are used with OAuth2 client credentials flow to obtain an access token.
Step 2: Write Your HTML Template
Your HTML template is a complete HTML document, exactly as you'd write for a web page. CSS controls the visual layout, and CSS Paged Media properties control the print-specific behavior (page size, margins, headers, page breaks).
Here's a simple template for a document with a header, content, and footer:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
@page {
size: A4;
margin: 2cm;
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 9pt;
color: #999;
}
}
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #333;
}
h1 {
color: #1a1a2e;
border-bottom: 2px solid #e2e8f0;
padding-bottom: 0.5em;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1em 0;
}
th, td {
border: 1px solid #e2e8f0;
padding: 8px 12px;
text-align: left;
}
th {
background-color: #f8f9fa;
font-weight: bold;
}
.highlight {
background-color: #fff3cd;
padding: 1em;
border-left: 4px solid #ffc107;
margin: 1em 0;
}
</style>
</head>
<body>
<h1>Quarterly Report — Q1 2026</h1>
<p>This report summarizes key metrics for the first quarter of 2026.</p>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Q4 2025</th>
<th>Q1 2026</th>
<th>Change</th>
</tr>
</thead>
<tbody>
<tr>
<td>Revenue</td>
<td>€142,000</td>
<td>€158,500</td>
<td>+11.6%</td>
</tr>
<tr>
<td>Active Users</td>
<td>3,240</td>
<td>3,870</td>
<td>+19.4%</td>
</tr>
</tbody>
</table>
<div class="highlight">
<strong>Note:</strong> PDF/A-compliant version available on request.
</div>
</body>
</html>
The @page rule with @bottom-center adds automatic page numbering — this works with WeasyPrint's CSS Paged Media support. For wkhtmltopdf, page numbering is handled differently via command-line arguments.
Step 3: Authenticate
PdfBroker.io uses OAuth2 client credentials for authentication. Exchange your Client ID and Client Secret for an access token:
C#
using System.Net.Http;
var httpClient = new HttpClient();
var tokenResponse = await httpClient.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.ReadFromJsonAsync<JsonDocument>();
var accessToken = tokenJson.RootElement.GetProperty("access_token").GetString();
If you're using the PdfBroker.Client NuGet package, authentication is handled automatically:
using PdfBroker.Client;
var client = new PdfBrokerClientService("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
// Token management is handled internally
cURL
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')
Python
import requests
token_resp = 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_resp.json()["access_token"]
Step 4: Generate the PDF
Encode your HTML as base64 and POST it to the API endpoint. The default response is the raw PDF binary — save it directly to a file or stream it to your user.
Using WeasyPrint (Recommended)
C# with PdfBroker.Client
using PdfBroker.Client;
using PdfBroker.Common.RequestObjects;
var client = new PdfBrokerClientService("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
var html = await File.ReadAllTextAsync("report-template.html");
var request = new WeasyPrintRequestDto
{
HtmlBase64String = Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(html))
};
byte[] pdfBytes = await client.WeasyPrintAsByteArrayAsync(request);
await File.WriteAllBytesAsync("quarterly-report.pdf", pdfBytes);
C# with HttpClient (No SDK)
var html = await File.ReadAllTextAsync("report-template.html");
var htmlBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(html));
var request = new HttpRequestMessage(HttpMethod.Post,
"https://api.pdfbroker.io/api/pdf/weasyprint");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Content = JsonContent.Create(new
{
htmlBase64String = htmlBase64
});
var response = await httpClient.SendAsync(request);
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
await File.WriteAllBytesAsync("quarterly-report.pdf", pdfBytes);
cURL
HTML_BASE64=$(base64 -w 0 report-template.html)
curl -X POST https://api.pdfbroker.io/api/pdf/weasyprint \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"htmlBase64String\": \"$HTML_BASE64\"}" \
--output quarterly-report.pdf
Python
import base64
with open("report-template.html", "r", encoding="utf-8") as f:
html_b64 = base64.b64encode(f.read().encode("utf-8")).decode("ascii")
response = requests.post(
"https://api.pdfbroker.io/api/pdf/weasyprint",
headers={"Authorization": f"Bearer {access_token}"},
json={"htmlBase64String": html_b64},
)
with open("quarterly-report.pdf", "wb") as f:
f.write(response.content)
Using wkhtmltopdf
The wkhtmltopdf endpoint accepts the same base64-encoded HTML but uses a different URL and supports additional rendering arguments for JavaScript execution and browser-specific features.
C# with PdfBroker.Client
using PdfBroker.Client;
using PdfBroker.Common.RequestObjects;
var client = new PdfBrokerClientService("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
var html = await File.ReadAllTextAsync("report-template.html");
var request = new WkHtmlToPdfRequestDto
{
HtmlBase64String = Convert.ToBase64String(
System.Text.Encoding.UTF8.GetBytes(html)),
WkHtmlToPdfArguments = new Dictionary<string, string>
{
{ "--page-size", "A4" },
{ "--margin-top", "20mm" },
{ "--margin-bottom", "20mm" },
{ "--enable-javascript", "" }
}
};
byte[] pdfBytes = await client.WkHtmlToPdfAsByteArrayAsync(request);
await File.WriteAllBytesAsync("quarterly-report.pdf", pdfBytes);
cURL
HTML_BASE64=$(base64 -w 0 report-template.html)
curl -X POST https://api.pdfbroker.io/api/pdf/wkhtmltopdf \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"htmlBase64String\": \"$HTML_BASE64\",
\"wkHtmlToPdfArguments\": {
\"--page-size\": \"A4\",
\"--margin-top\": \"20mm\",
\"--margin-bottom\": \"20mm\"
}
}" \
--output quarterly-report.pdf
Python
response = requests.post(
"https://api.pdfbroker.io/api/pdf/wkhtmltopdf",
headers={"Authorization": f"Bearer {access_token}"},
json={
"htmlBase64String": html_b64,
"wkHtmlToPdfArguments": {
"--page-size": "A4",
"--margin-top": "20mm",
"--margin-bottom": "20mm",
},
},
)
with open("quarterly-report.pdf", "wb") as f:
f.write(response.content)
Handling Responses
PdfBroker.io supports two response formats controlled by the Accept header:
Binary response (default) — the API returns the raw PDF bytes. This is the most efficient option for saving to disk or streaming to a client. Use this when the PDF is the final output.
JSON response — set Accept: application/json and the API returns a JSON object with the PDF as a base64-encoded string. This is useful when you need to pass the PDF to another service or embed it in a larger response payload.
// JSON response example
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.SendAsync(request);
var json = await response.Content.ReadFromJsonAsync<JsonDocument>();
var pdfBase64 = json.RootElement.GetProperty("pdfFileBase64String").GetString();
byte[] pdfBytes = Convert.FromBase64String(pdfBase64);
See the Content Negotiation documentation for full details.
Making Templates Dynamic
In a production application, you'll populate templates with data from your database, billing system, or API. There are several approaches:
String interpolation — for simple templates, use string replacement or interpolation to insert values directly into the HTML before sending it to the API.
var template = await File.ReadAllTextAsync("invoice-template.html");
var html = template
.Replace("{{CustomerName}}", invoice.CustomerName)
.Replace("{{InvoiceNumber}}", invoice.Number)
.Replace("{{Total}}", invoice.Total.ToString("C"));
Server-side templating — for complex templates with loops, conditionals, and layouts, use a templating engine like Razor (C#) or Jinja2 (Python) to render the HTML before sending it to PdfBroker.io.
PdfBroker Template Tool — PdfBroker.io includes a visual Template Tool for designing and previewing HTML templates. Build your layout visually, then use the generated HTML in your API calls.
Including Images and Custom Fonts
With WeasyPrint
WeasyPrint supports a resources object for passing binary assets alongside your HTML. Reference files by name in your HTML, and include them as base64-encoded strings:
{
"htmlBase64String": "<base64 HTML with <img src='logo.png'>>",
"resources": {
"logo.png": "<base64-encoded image data>"
}
}
See Images in HTML Templates for details.
With wkhtmltopdf
wkhtmltopdf fetches resources via URL, just like a browser. Reference images using absolute URLs (https://...) or embed them as data URIs directly in the HTML.
Adding PDF/A or PDF/UA Compliance
If your documents need archival compliance or accessibility tagging, add the pdf-variant parameter to WeasyPrint requests:
{
"htmlBase64String": "<base64 HTML>",
"weasyPrintToPdfArguments": {
"pdf-variant": "pdf/a-3b"
}
}
Supported values: pdf/a-1b, pdf/a-2b, pdf/a-3b, pdf/ua-1. See PDF/A vs PDF: Understanding the Difference for guidance on choosing the right variant.
Which Engine Should You Choose?
| Requirement | WeasyPrint | wkhtmltopdf |
|---|---|---|
| Print-quality documents (invoices, reports) | ✓ Best choice | Adequate |
| JavaScript-dependent content | ✗ No JS support | ✓ Full JS execution |
| PDF/A or PDF/UA compliance | ✓ | ✗ |
| CSS Paged Media (page numbers, headers) | ✓ Full support | Partial |
| Available on free tier | ✗ Basic plan+ | ✓ |
| Embedded resources via API | ✓ resources object | ✗ URL-based only |
For a detailed comparison, see WeasyPrint vs wkhtmltopdf: When to Use Which.
Summary
Generating PDFs from HTML with a REST API is a four-step process: write your HTML template, authenticate with OAuth2, POST the base64-encoded HTML to the WeasyPrint or wkhtmltopdf endpoint, and handle the binary or JSON response. PdfBroker.io handles the rendering infrastructure so your application stays free of PDF library dependencies.
Related Resources
- WeasyPrint as a Service — Full WeasyPrint API reference
- wkhtmltopdf as a Service — Full wkhtmltopdf API reference
- How to Generate PDF Invoices from HTML with a REST API — Invoice-specific tutorial
- Getting Started with PDF/A and PDF/UA Compliance via API — Compliance variants
- Content Negotiation — Binary vs JSON response formats
- PdfBroker Template Tool — Visual template editor
- Pricing — Plans starting from free