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

422 Unprocessable Entity


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)