PDF

Turn HTML into a PDF the way a browser prints it, because a browser prints it.

POST /v1/pdf lays your HTML out in headless Chromium and returns the printed PDF. Not a PDF-drawing library approximating a page — the actual layout engine, so line breaking, the cascade, and bidi text all work the way they do in a browser window. That last part is why this endpoint exists: Persian and Arabic script is cursive and right-to-left with embedded Latin runs, and reproducing that correctly outside a real browser is a project, not a setting.

POST/v1/pdf

No network

Every reference must be inline
The browser that renders your HTML has no network access at all — it is launched with --host-resolver-rules=MAP * 0.0.0.0, so a file:// path, an internal address, and a public URL all fail identically. This is deliberate: the renderer is an attacker's easiest way to make your server fetch something it should not (a cloud metadata endpoint, a service that is only reachable from inside the network). Any src=, href=, content= or CSS url() that starts with http:, https:, //, file: or ftp: is refused with 400 before rendering starts. Images and fonts go in as data: URIs; styles go in a <style> block in the document itself, never a linked stylesheet.

Parameters

ParameterTypeDescription
html
Required
stringThe document body, up to 4,000,000 characters. If it does not already contain <html, it is wrapped for you in a minimal page with the base stylesheet shown below.
filename
Optional
stringSets Content-Disposition. Only the base name is kept.
Default: document.pdf
direction
Optional
stringauto, ltr or rtl. See Right-to-left below — pass it explicitly for anything that matters.
Default: auto
format
Optional
stringPage size: A4, Letter, Legal, and the other sizes Chromium's print engine accepts.
Default: A4
margin
Optional
stringSpace-separated CSS lengths. One value sets every side; two set block then inline, the way the default does. Give one or two — this is not a full four-value shorthand.
Default: 20mm 18mm
header
Optional
stringAn HTML template printed at the top of every page. Setting either header or footer turns both on; see Headers and footers below.
footer
Optional
stringAn HTML template printed at the bottom of every page. Defaults to a page x / y counter when header is set but footer is not.
landscape
Optional
booleanRotate the page.
Default: false

Right-to-left

direction: "auto" counts Persian-script characters in the body and switches to rtl once they pass 15% of it — good enough for a document that is entirely one script or the other, wrong for the common case of a short Persian cover note quoting a long English contract. Pass direction explicitly whenever the outcome matters. Vazirmatn, Noto Naskh Arabic, Noto Sans and DejaVu Sans Mono are installed on the renderer and used automatically; anything else — a house typeface, a logo font — has to be embedded the same way an image does, as a data: URI in an @font-face rule, because the renderer cannot fetch it.

Headers and footers

header and footer are Chromium's own print templates: small HTML fragments that may use the classes date, title, url, pageNumber and totalPages, which Chromium fills in itself. Keep the font small — the template renders inside the page margin, so a tall header needs a taller margin to avoid overlapping the body.

A page-number footer
<div style="width:100%;font-size:8px;text-align:center;color:#8b93a3;padding:0 12mm">
  <span class="pageNumber"></span> / <span class="totalPages"></span>
</div>

A complete template

Everything this document references — the logo, the custom font — is inlined. There is nothing outside the request for the renderer to fail to reach.

invoice.html
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<style>
  @font-face {
    font-family: "Brand Sans";
    src: url(data:font/woff2;base64,d09GMgABAAAAAAaw...) format("woff2");
  }
  body { font-family: "Brand Sans", "Noto Sans", sans-serif; }
  .letterhead { display: flex; align-items: center; gap: 12px; margin-bottom: 24px; }
  .letterhead img { width: 40px; height: 40px; }
  table { width: 100%; border-collapse: collapse; }
  th, td { border: 1px solid #d3d8e0; padding: 6px 10px; }
</style>
</head>
<body>
  <div class="letterhead">
    <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAg...">
    <h1>Invoice #1042</h1>
  </div>
  <table>
    <thead><tr><th>Item</th><th>Amount</th></tr></thead>
    <tbody><tr><td>Legal research, 3.5h</td><td>$210.00</td></tr></tbody>
  </table>
</body>
</html>

Calling it

curl https://api.console.larsa.larsima.com/v1/pdf \
  -H "Authorization: Bearer $LARSA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Hello</h1><p>Rendered by a real browser.</p>",
    "filename": "hello.pdf",
    "direction": "ltr"
  }' \
  --output hello.pdf

The response

HeaderMeaning
Content-Typeapplication/pdf.
Content-Dispositionattachment; filename="…", from your filename.
X-Lardad-ElapsedRender time in seconds.
X-Lardad-BytesSize of the PDF in bytes — check this before trusting a short response.
This is a pass-through endpoint: an error comes back as {"detail": "…"}, not the {"error": {…}} shape the chat and audio endpoints use. See Errors.
Navigate Open esc Close