Most documents your product sends are the same document. The May invoice and the June invoice differ in five values. The onboarding contract for client A and client B differ in a name, an address, and a date. The weekly report differs in the numbers. The layout, the typography, the logo placement: identical, every single time.
Yet most teams regenerate the whole document from scratch each time. If an LLM is in the loop, that means re-prompting, re-paying for the tokens, and hoping the model formats the table the same way it did last month. It usually does not. Customers notice when the invoice they file every month keeps shifting by a few pixels.
The Render API exists for exactly this case: the layout is fixed, the data changes. You save the layout once as a template with named placeholders. After that, producing a document is a single call that carries nothing but the values.
Data In, PDF Out
$ curl -X POST https://api.pressa.dev/api/v2/templates/monthly-invoice/render \
-H "Authorization: Bearer pressa_..." \
-H "Content-Type: application/json" \
-d '{"data": {"client": "Acme GmbH", "invoice_number": "2026-041", "amount": 4200}}'The response carries a signed PDF URL, the page count, render timing, and the template version that produced the document. No markup in the request. No layout decisions made at request time. The same template plus the same data produces the same PDF, byte-for-byte deterministic in the ways that matter: fonts, spacing, table widths, page breaks.
Templates are addressed by ID or by name, so the call above reads the way you would say it: render monthly-invoice for Acme.
The Schema Is Derived, Not Declared
When you save a template, Pressa parses it and derives a JSON schema from the placeholders it finds: which fields exist, which are required, what type each one expects. You never write the schema by hand, and it cannot drift out of sync with the template because it is regenerated on every template change.
Every render request is validated against that schema before anything compiles. Send a request with a missing field and you get a 422 that names it:
{
"error": "schema_validation_failed",
"missing_fields": ["invoice_number"],
"type_errors": []
}This matters twice as much when the caller is an AI agent. An LLM that receives "something went wrong" retries blindly. An LLM that receives a field path patches the request and succeeds on the second attempt, without a human in the loop. Every error the Render API returns is structured this way: a stable error code plus the context needed to fix the call.
Safe by Default
User data flows into a typesetting language, which is historically where injection bugs live. The Render API closes that door at the template boundary instead of trusting every caller.
Every interpolated value is escaped automatically. If a customer is named Acme & Sons, 100% Ltd. the ampersand and the percent sign arrive in the PDF as literal characters, not as LaTeX control sequences. Template authors who genuinely need to inject markup opt out per value with the raw filter; everything else is safe without anyone thinking about it.
Formatting filters cover the recurring cases: currency renders 4200 as $4,200.00 with the right symbol, date parses and reformats dates, and asset resolves an uploaded logo or signature by name. The template itself is screened by a 14-rule security validator at save time, and the compile runs in an isolated sandbox with no shell access and no network.
Fonts and Logos Just Work
Two details that usually break template systems are handled server-side. If a template uses a custom font, the render detects it and selects the right compilation engine automatically; you do not pass a compiler flag, and a template author switching fonts cannot silently break every integration that calls it. And if a template references an image from your asset library, the file is resolved and embedded automatically on every render. Upload the logo once; every document that mentions it gets it.
Where Templates Come From
Two paths. If you like a document you already compiled, open it in Pressa Studio and click the values that should become variables; the placeholder, the schema, and the sample data are generated for you. Or create the template programmatically with a single API call, which is how AI agents do it: generate a document once, perfect it, then save it as the canonical layout for everything that follows.
From the CLI and From Your Agent
CLI. pressa render monthly-invoice --data april.json --output invoice.pdf renders from a data file. Pass inline JSON, or --data - to read from stdin, and --output - to stream the PDF to stdout for piping into the next step of a script.
MCP. The Pressa MCP server exposes a render tool. Claude, Cursor, or any MCP-aware agent fills a saved template by name with fresh values, without ever seeing or re-sending the underlying markup. The agent spends its tokens on your data, not on regenerating a layout it already got right last month.
Try It
The Render API is available on every paid plan, and renders count toward the same monthly quota as compiles. The full endpoint reference, including every error code and filter, is in the API documentation . If you want the two-minute version: save a template, send it data, ship the PDF.