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
FieldTypeDescription
htmlstringRequired. The document.
cssstring | string[]Extra stylesheets, applied after the document's own.
format"pdf" | "png" | "webp" | "svg"Default pdf.
width, heightinteger (CSS px)Viewport. Default width 800; height is automatic unless paginating.
baseURLstringResolves relative URLs of images, fonts and stylesheets.
optionsobjectRendering 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).

render.ts
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.

See Cloudflare Worker limits and Worker CPU billing.

Options

OptionDescription
paginationPaged PDF: @page sizes and margins, breaks, margin boxes. Needs height.
linkedFootnotesTurns doc-noteref links into footnotes at their reference.
typography.justification.algorithm"greedy" or "knuth-plass".
typography.justification.opticalMarginsHanging punctuation in justified paragraphs.
typography.hyphenationMinimum word length, letters before/after, penalties, maximum consecutive hyphens.
mediaType"print" or "screen", for @media rules.
backgroundColor, pdfTitle, pdfAuthorOutput metadata.
missingResources"error" (default) or "ignore".

Errors

Errors are JSON: { "error": "message" }.

StatusMeaning
400Invalid JSON or fields.
401Missing, invalid or revoked API key.
402No active API subscription.
413Body larger than 10 MB.
422The 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-expired warning for 30 days, then rendering stops.
  • Renewing gives you a new license file valid one more year; replace the old one.