Getting started
Sign in, copy a key, post some Typst, get a PDF. Five minutes, no SDK.
-
Get a key
Enter your email on the start page and open the link we send. Your first API key is created on the spot: it appears once in the dashboard and arrives by email. Free accounts get 300 pages a month.
Keys look like
fl_live_…. Export it for the examples below:export FOLIANT_API_KEY=fl_live_…No account yet and just want to see it work? Skip this step and use
/v1/demo/renderinstead of/v1/renderwith no header. Three renders a day, five pages each. -
First render
curl -X POST https://api.foliant.dev/v1/render \ -H "Authorization: Bearer $FOLIANT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"source": "#set page(paper: \"a4\")\n= Hello\nRendered on #datetime.today().display()."}' \ -o hello.pdf -D -The body is the PDF. The
-D -prints the headers, which include the page count and your usage:content-type: application/pdf x-foliant-pages: 1 x-foliant-tier: free x-foliant-pages-used: 1 x-foliant-pages-limit: 300 x-ratelimit-remaining: 29 -
Pass data
Do not build Typst by string concatenation. Put values in
inputsand read them assys.inputs:curl -X POST https://api.foliant.dev/v1/render \ -H "Authorization: Bearer $FOLIANT_API_KEY" -H "Content-Type: application/json" -o out.pdf \ -d @- <<'JSON' { "source": "#let input(k) = sys.inputs.at(k, default: \"\")\n= Order #input(\"no\")\nFor #input(\"customer\"), total #input(\"total\") EUR.", "inputs": { "no": "4711", "customer": "Meridian Studio", "total": "8496.60" } } JSONInputs are strings; convert in Typst with
int()orfloat()when you need arithmetic. Once a document is used more than once, store it as a template and send only the inputs. -
Add an image
Attach files by path. Base64 inline, or a URL we fetch, or a stored asset:
{ "source": "#image(\"logo.png\", width: 40mm)", "files": { "logo.png": { "url": "https://example.com/logo.png" } } }For a logo you use everywhere, upload it once (
PUT /v1/assets/logo.pngor the dashboard) and write#image("/assets/logo.png"); nofilesentry needed. -
Handle errors
Every error is JSON with a stable
code. The one you will see most iscompile_error, which includes the line:HTTP 422 { "error": { "code": "compile_error", "message": "Typst could not compile the document", "diagnostics": [ { "severity": "error", "line": 2, "message": "unknown variable: totl", "hints": [] } ] } }Failed renders are not charged. To validate without producing output at all, add
"check": true; that returns{ ok, pages, warnings }and is always free.When the free tier is used up you get
429 quota_exceededwithresets_atand anupgrade_url. Add credit and the same key keeps working.
Node
import { writeFile } from "node:fs/promises";
const res = await fetch("https://api.foliant.dev/v1/render", {
method: "POST",
headers: { authorization: `Bearer ${process.env.FOLIANT_API_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ template: "invoice.typ", inputs: { no: "2026-0917", total: "8496.60" } }),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}` + (error.diagnostics ? "\n" + error.diagnostics.map(d => `line ${d.line}: ${d.message}`).join("\n") : ""));
}
await writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));
console.log("pages:", res.headers.get("x-foliant-pages"));
Python
import os, requests
r = requests.post(
"https://api.foliant.dev/v1/render",
headers={"Authorization": f"Bearer {os.environ['FOLIANT_API_KEY']}"},
json={"source": "= Hello from Python", "format": "pdf"},
timeout=60,
)
if r.status_code != 200:
err = r.json()["error"]
raise SystemExit(f"{err['code']}: {err['message']}\n" + "\n".join(f"line {d.get('line')}: {d['message']}" for d in err.get("diagnostics", [])))
open("hello.pdf", "wb").write(r.content)
print("pages", r.headers["x-foliant-pages"])
Where next
- Templates: store layouts, render with inputs, compose with includes, three complete examples.
- Agents and MCP: let Claude, Cursor or your own agent render documents.
- API reference: every field, header, error code and limit.
- Typst documentation: the language itself. Start with the tutorial; it takes an hour and pays for itself the first time you need a table.