Documentation
The cloud API, then the self-hosted engine. Both take the same HTML, CSS and options.
Cloud API
Subscribe, then create a key in your dashboard. Send it as a bearer token. Each successful render is billed €0.005 per document on top of the €5 monthly base: a PDF of any length, a PNG, WebP or SVG each counts as one document. Failed renders are free. A render may take up to 5 minutes.
To try the API before subscribing, create a test key (pal_test_…, one per account): it is free and never
billed, and limited to 50 requests per UTC day and 10 per minute (429 beyond, with Retry-After).
Every request appears in your dashboard with its status and generation time.
POST https://api.palatine.page/v1/render
Authorization: Bearer pal_live_…
Content-Type: application/json | Field | Type | Description |
|---|---|---|
html | string | Required. The document. |
css | string | string[] | Extra stylesheets, applied after the document's own. |
format | "pdf" | "png" | "webp" | "svg" | Default pdf. |
width, height | integer (CSS px) | Viewport. Default width 800; height is automatic unless paginating. |
baseURL | string | Resolves relative URLs of images, fonts and stylesheets. |
options | object | Rendering options, below. |
Response
200 with the file bytes and its content type. Headers: x-palatine-pages (output page count, informational), x-palatine-documents (billed documents: 1 with a live key, 0 with a test key), x-palatine-duration-ms (generation time), x-palatine-mode (live or test), x-palatine-render-id, and x-palatine-warnings (URL-encoded JSON list of degraded-rendering
warnings, if any).
const key = process.env.PALATINE_API_KEY;
const url = "https://api.palatine.page/v1/render";
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: "Bearer " + key,
"Content-Type": "application/json",
},
body: JSON.stringify({
html: "<h1>Hi!</h1>", format: "pdf",
}),
});
if (!response.ok) throw Error(response.statusText);
const pdf = await response.arrayBuffer();Fonts and resources
Noto Sans is always available as a fallback. Other fonts, images and stylesheets are loaded from the http(s) URLs in your document (@font-face, <img>, <link>);
private network addresses are refused. Requests are limited to 10 MB, each resource to 20 MB.
Runtime limits and timeouts
The hosted API renders inside a Cloudflare Worker, with the same WebAssembly build you can run yourself: up to 5 minutes of CPU and 128 MB of memory per render. Very large documents (books well over a hundred pages, long tables) can exceed that memory today and fail; render them with the WebAssembly or native build.
For a WASM build you deploy in your own paid Worker, the default CPU limit is 30 seconds, configurable up to 300 seconds, with the same 128 MB of memory. HTTP requests have no hard wall-clock duration limit while the client stays connected. CPU time and elapsed time are different limits.
A hosted render can take up to 5 minutes. Each external resource fetch also has a 15-second timeout. Keep the client connected and set its timeout slightly above 5 minutes, as the Python example does. The WebAssembly and native builds you run yourself have no render time limit.
Options
| Option | Description |
|---|---|
pagination | Paged PDF: @page sizes and margins, breaks, margin boxes. Needs height. |
linkedFootnotes | Turns doc-noteref links into footnotes at their reference. |
typography.justification.algorithm | "greedy" or "knuth-plass". |
typography.justification.opticalMargins | Hanging punctuation in justified paragraphs. |
typography.hyphenation | Minimum word length, letters before/after, penalties, maximum consecutive hyphens. |
mediaType | "print" or "screen", for @media rules. |
backgroundColor, pdfTitle, pdfAuthor | Output metadata. |
missingResources | "error" (default) or "ignore". |
Errors
Errors are JSON: { "error": "message" }.
| Status | Meaning |
|---|---|
| 400 | Invalid JSON or fields. |
| 401 | Missing, invalid or revoked API key. |
| 402 | No active API subscription. |
| 413 | Body larger than 10 MB. |
| 422 | The document could not be rendered (missing resource, invalid option…). |
Self-hosted engine
Once your license is issued, download the build from the emailed link or your dashboard
and save the license file as palatine-license.txt. The license is checked offline; nothing is sent
anywhere.
Choose where rendering happens
One Palatine license per build covers your whole company and server fleet. Use the native renderer on your Linux servers, or the WASM build to render offline directly on your customers’ machines, inside a browser, desktop or smartphone app. Keep API secrets on your backend when using the hosted API. Embedding the distributed engine in a customer-facing app is subject to the distribution terms.
We strongly recommend against per-machine Prince licensing for a growing server fleet on cost grounds. One Prince license covers one machine: $3,800 for one license, with volume discounts down to $2,660 per license for five or more. This is a licensing-cost recommendation, not a technical restriction. A Palatine license is priced by volume and covers every machine; see licenses.
Native (Linux x86-64)
tar xf palatine-native-linux-x64.tar.gz
./palatine --license palatine-license.txt \
--input report.html --font Body.ttf --font-family Body \
--paginate --format pdf --width 794 --height 1123 --output report.pdf Or set PALATINE_LICENSE to the license file path or its PAL1-… token.
WebAssembly
npm install ./palatine-renderer.tgz import { createRenderer } from "@palatine/renderer";
import { readFile } from "node:fs/promises";
const renderer = await createRenderer({
license: await readFile("palatine-license.txt", "utf8"),
});
const pdf = await renderer.render({
html, css, format: "pdf", width: 794, height: 1123,
fonts: [{ family: "Body", data: await readFile("Body.ttf") }],
options: { pagination: true },
}); License lifecycle
- Every build released before your license's expiry date works with it.
- After expiry, each render prints a
license-expiredwarning for 30 days, then rendering stops. - Renewing gives you a new license file valid one more year; replace the old one.