API Reference
Base URL: https://pdfcreator.tools.systaro.de (or your deployed host)
POST /render
Renders an HTML document to PDF.
Request
Content-Type: application/json
{
"html": "<h1>Document</h1>",
"assets": {
"logo.png": "<base64-encoded bytes>",
"style.css": "<base64-encoded bytes>"
},
"options": {
"base_url": "https://example.com",
"pdf_variant": "pdf/a-3b",
"optimize_images": false
}
}
Fields
html (string, required)
The full HTML to render. Can include <style> blocks, <link> tags pointing to public URLs, inline images, and asset:// references.
assets (object, optional)
Map of filename to base64-encoded file content. Files referenced in HTML as asset://filename are resolved from this map.
"assets": {
"logo.png": "iVBORw0KGgoAAAANS...",
"report.css": "Ym9keSB7IGZvbnQ..."
}
Asset references in HTML:
<img src="asset://logo.png">
<link rel="stylesheet" href="asset://report.css">
Public URLs work without the assets map — WeasyPrint fetches them directly:
<img src="https://cdn.example.com/logo.png">
options (object, optional)
| Field | Type | Default | Description |
|---|---|---|---|
base_url |
string | null |
Base URL for resolving relative links in the HTML |
pdf_variant |
string | null |
PDF standard variant. See PDF Variants |
optimize_images |
boolean | false |
Lossless image compression to reduce file size |
jpeg_quality |
integer | null |
JPEG quality for embedded images, 0–95. Lower = smaller file. Not set by default (lossless). |
dpi |
integer | null |
Maximum image resolution in DPI. Use 150 for screen, 300 for print quality. Also sets the resolution used for any element the renderer has to rasterize, such as a flattened gradient cover, so it is the lever for the file size of those pages. |
Response
200 OK
- Content-Type:
application/pdf - Body: raw PDF bytes
422 Unprocessable Entity
- Body:
{ "detail": "<error message>" } - Returned when WeasyPrint fails to render (e.g. missing asset, malformed HTML)
Text Layer
Rendered PDFs are post-processed so the text they show is the text you get when you select and copy it.
Typographic ligatures are the reason this is needed. A renderer draws fi,
fl and ff as single glyphs, and records them in the PDF as a ligature
codepoint. Copying then yields Ausflug rather than Ausflug, which looks
right but does not match a search for the word. Worse, fonts that use the
legacy Apple Private Use codepoints (macOS system fonts such as Iowan Old
Style do) produce characters that most readers cannot decode at all, so
Ausflug copies as Ausug — the letters are simply gone.
Every ligature is therefore decomposed back to its plain characters. Only the text layer is rewritten; glyphs and layout are untouched and the rendered pages are pixel-identical. A document with nothing to repair is returned byte for byte, so PDF/A output is never restructured.
Text that the renderer has to rasterize, such as a gradient heading or a
flattened cover page, also stays copyable. The bitmap is laid over the original
text instead of replacing it, and a flattened cover keeps its own text live.
Flattening trades file size for rendering speed, roughly ten to fifteen times
the bytes for that page, so lower dpi when the size matters more.
GET /health
Returns service status. Use for liveness probes.
Response
{ "status": "ok" }
PDF Variants
| Value | Standard | Use Case |
|---|---|---|
null (default) |
Plain PDF | General purpose |
"pdf/a-1b" |
PDF/A-1b | Basic long-term archiving |
"pdf/a-2b" |
PDF/A-2b | Archiving with transparency support |
"pdf/a-3b" |
PDF/A-3b | Archiving with embedded file attachments |
"pdf/a-4" |
PDF/A-4 | Latest archival standard |
Examples
Minimal
curl -X POST https://pdfcreator.tools.systaro.de/render \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello</h1>"}' \
--output out.pdf
With inline asset
curl -X POST https://pdfcreator.tools.systaro.de/render \
-H "Content-Type: application/json" \
-d '{
"html": "<img src=\"asset://logo.png\"><p>Report</p>",
"assets": {
"logo.png": "'$(base64 -i logo.png)'"
}
}' \
--output out.pdf
PDF/A with base URL
curl -X POST https://pdfcreator.tools.systaro.de/render \
-H "Content-Type: application/json" \
-d '{
"html": "<link rel=\"stylesheet\" href=\"/styles/report.css\"><h1>Doc</h1>",
"options": {
"base_url": "https://cdn.example.com",
"pdf_variant": "pdf/a-3b"
}
}' \
--output archive.pdf
Python client
import base64
import httpx
def render_pdf(html: str, assets: dict[str, bytes] = {}, **options) -> bytes:
payload = {
"html": html,
"assets": {k: base64.b64encode(v).decode() for k, v in assets.items()},
"options": options,
}
resp = httpx.post("https://pdfcreator.tools.systaro.de/render", json=payload, timeout=60)
resp.raise_for_status()
return resp.content
# Usage
pdf = render_pdf(
html="<h1>Hello</h1>",
assets={"logo.png": open("logo.png", "rb").read()},
pdf_variant="pdf/a-3b",
)
with open("out.pdf", "wb") as f:
f.write(pdf)