Create PDF from Template
Send a POST request to this endpoint to render one of your templates into a PDF document, or to
build one PDF from many documents with pages.
You will need to authenticate to use this API. See authentication.
This endpoint consumes 1 credit per 20 pages, rounded up: 1 to 20 pages cost 1 credit, 21 to 40 cost 2, up to 5 credits for 100 pages. A PDF longer than 100 pages is refused and costs nothing.
Request
POST https://api.ojo.so/v1/pdf/templateRequest Body
| Parameter | Type | Description |
|---|---|---|
| templateId | string | The ID of the template to use. With pages, the default template, optional when every entry names its own or gives html |
| modify | object | (Optional) An object containing variables to modify in the template. With pages, shared by every template entry |
| pages | array | (Optional) The documents of one PDF, in order. See Documents from pages |
Page size comes from a CSS @page rule in the template first, as described in
Create PDF from HTML, then from the template’s
saved size, then A4. A template variable works in @page too, so one template can serve both
sizes:
@page {
size: {{pageSize}};
}cURL
curl --request POST \
--url https://api.ojo.so/v1/pdf/template \
--header 'Authorization: Bearer <API Key>' \
--header 'Content-Type: application/json' \
--data '{"templateId": "<Template ID>", "modify": {"customer": "Acme Inc.", "pageSize": "letter"}}'Response
200
Everything went as planned and your PDF is generated.
{
"id": "pdf-id.pdf",
"pdfUrl": "<content domain>/path/to/document.pdf",
"pages": 1,
"credits": 1,
"createdAt": "2026-09-28T12:34:56Z"
}pdfUrl has no scheme. Prefix it with https:// to fetch the file.
Documents from pages
pages builds one PDF from up to 100 documents, merged in order. Each entry is a document that
starts on a new page and prints at least one: a template with its own values, or HTML.
| Field | Type | Description |
|---|---|---|
| templateId | string | (Optional) The template for this entry. Defaults to the top-level templateId |
| html | string | (Optional) HTML printed as is, with no variables. Give templateId or html, not both |
| modify | object | (Optional) Values for this entry |
A template entry takes the template’s saved variables, then the top-level modify, then its own
modify, each over the one before. A document longer than a page prints more pages. Each document
has its own page size, by the rule above.
One certificate per attendee, in one file:
{
"templateId": "<Certificate template ID>",
"modify": { "course": "TypeScript 101" },
"pages": [
{ "modify": { "name": "Ada Lovelace" } },
{ "modify": { "name": "Grace Hopper" } }
]
}A report with a cover, a body and terms from different templates:
{
"modify": { "customer": "Acme Inc." },
"pages": [
{ "templateId": "<Cover template ID>" },
{ "templateId": "<Report template ID>", "modify": { "quarter": "Q3" } },
{ "html": "<h1>Terms</h1><p>...</p>" }
]
}The response adds the pages each entry printed:
{
"id": "pdf-id.pdf",
"pdfUrl": "<content domain>/path/to/document.pdf",
"pages": 3,
"credits": 1,
"entries": [
{ "index": 0, "pages": 1 },
{ "index": 1, "pages": 1 },
{ "index": 2, "pages": 1 }
],
"createdAt": "2026-09-28T12:34:56Z"
}pages makes one file. For one file per recipient, send one request each.
Limits
| Limit | Value | When exceeded |
|---|---|---|
Entries in pages | 100 | 422 before rendering, nothing charged |
| Pages in the PDF | 100 | 422, nothing charged |
| Rendered HTML of all entries | 5 MB | 422 before rendering, nothing charged |
| Rendering time | 60 s | 422, nothing charged |
| Size of the PDF | 50 MB | 422, nothing charged |
pages requests in flight | 1 per account | 429 with Retry-After |
The whole request is checked before anything renders, and a 422 lists every problem found.
A request with pages can take about a minute, plus any time spent waiting behind other renders.
If your client gives up before the answer arrives, the PDF may still be made and charged. Find it
with GET /v1/pdf or on the dashboard’s Storage page.