Documentation
Getting started
Three steps: authenticate, upload a template, send data to /generate. This page covers the whole request shape, including tables, images, and debug mode.
1. Authentication
Send your API key on every request:
X-API-Key: YOUR_API_KEY
That's the only credential you need for generating documents, managing templates, and checking usage. Get your key from your dashboard after signing up and verifying your email - keep it out of client-side code and version control, and rotate it any time if it's ever exposed.
Billing, key rotation, and account deletion require being logged into the website itself - not just an API key.
2. Design and upload a template
A template has two kinds of markers showing where your data goes:
<placeholder_name>Replaced with a single text value or an image.
{table_name}Marks a row to repeat once per item you send.
Upload it from the dashboard, or POST /templates as multipart form data with name and file fields.
3. Placeholder types
text
Replaces a placeholder with a string.
{
"placeholder": "<client_name>",
"type": "text",
"value": "Acme Corp",
"textFormatting": {
"bold": true,
"colour": "#7A2E2A"
}
}placeholderrequiredThe tag in your template this replaces, e.g. <client_name>.
typerequiredEither "text" or "image".
valuerequiredThe replacement string - up to 10,000 characters. Send an empty string to blank the placeholder out. Tabs and line breaks are fine; other control characters aren't.
textFormattingoptionalOverrides font, size (1–96pt), bold/italic/underline, text colour, and highlight colour for just this value, as #RRGGBB hex.
image
Replaces a placeholder with an embedded image. Width and height are in whole millimetres.
{
"placeholder": "<logo>",
"type": "image",
"value": "iVBORw0KGgoAAAANSUhEUgAA...",
"imageType": "png",
"width": 40,
"height": 20
}placeholderrequiredSame as the text type - the tag this replaces.
typerequired"image".
valuerequiredBase64-encoded PNG or JPEG data, up to ~5MB decoded.
widthrequiredWidth in whole millimetres - 40 is 40mm (about 1.57in).
heightrequiredHeight in whole millimetres.
imageTyperequiredOne of "png", "jpeg", or "jpg" - must match the actual bytes you send.
An image inside a text box can't be replaced - keep it in the main body or a table cell.
4. Table tags
A placeholder swaps in a single value - but a table needs an entire row repeated once per item you send, and a placeholder has no way to say "repeat this". A tag on a row marks it as the one to duplicate, so the engine knows exactly which row to expand and how many times.
Each entry in tableRows becomes one repeated row. Every row has tableColumns, and each column can hold its own placeholders - matched to the template's columns left to right.
{
"tag": "{items}",
"tableRows": [
{
"tableColumns": [
{ "placeholders": [
{ "placeholder": "<item_name>", "type": "text", "value": "Widget" }
]},
{ "placeholders": [
{ "placeholder": "<item_price>", "type": "text", "value": "$10.00" }
]}
]
}
]
}A column can also hold its own nested tablePlaceholders, for a sub-table with its own tag inside a cell:
{
"tag": "{items}",
"tableRows": [
{
"tableColumns": [
{ "placeholders": [
{ "placeholder": "<item_name>", "type": "text", "value": "Widget" }
]},
{
"tablePlaceholders": [
{
"tag": "{sub_items}",
"tableRows": [
{ "tableColumns": [
{ "placeholders": [
{ "placeholder": "<part_name>", "type": "text", "value": "Bolt" }
]}
]}
]
}
]
}
]
}
]
}tableRows can also be an empty array. That's a normal, valid way to say "nothing to list this time" - the table renders with no rows, not as an error.
5. Debug mode & warnings
A missing or unusable placeholder or table tag never fails the request - you still get a 200 and a document back, with the literal text left in place wherever it couldn't be applied. The X-Docgen-Warnings header on every response is a quick signal something needs a closer look - it's a count of how many things were skipped, not a description of what happened.
For the actual detail, use debug mode: add ?debug=true to any /generate call and you get back a JSON report instead of the document - exactly which placeholders and table tags matched, which didn't, and the rendered document itself, base64-encoded, all in one response. It's the fastest way to debug a template that isn't behaving.
{
"report": {
"globalPlaceholders": [
{ "placeholder": "<client_name>", "replaced": 1 },
{ "placeholder": "<optional_note>", "replaced": 0 }
],
"tablePlaceholders": [
{ "tag": "{items}", "replaced": 1, "tableRows": [ ... ] }
]
},
"warnings": [
"Global placeholder <optional_note> was never replaced in the document"
],
"document": "JVBERi0xLjQK..."
}6. Account & usage
You've already seen POST /generate above - it's the core of the API. A few more endpoints round out template and account management, all callable with your API key. Click through them below to see exactly what each one sends back:
Request
GET https://undefined/usage
X-API-Key: YOUR_API_KEY
Response
200 OK
Content-Type: application/json
{
"docs_used": 42,
"docs_per_month": 500,
"period_start": "2026-07-10T00:00:00Z",
"period_end": "2026-08-09T00:00:00Z",
"rate_limit_per_minute": 10
}A complete request
One request combining a text value, an image, and a table with two rows - switch between scenarios below to see exactly what goes out and what comes back.
Request
POST https://undefined/generate
X-API-Key: YOUR_API_KEY Content-Type: application/json
{
"odtTemplate": "invoice",
"outputType": "pdf",
"globalPlaceholders": [
{
"placeholder": "<client_name>",
"type": "text",
"value": "Acme Corp"
},
{
"placeholder": "<logo>",
"type": "image",
"value": "iVBORw0KGgoAAAANSUhEUgAA...",
"imageType": "png",
"width": 40,
"height": 20
}
],
"tablePlaceholders": [
{
"tag": "{items}",
"tableRows": [
{
"tableColumns": [
{
"placeholders": [
{
"placeholder": "<item_name>",
"type": "text",
"value": "Widget"
}
]
},
{
"placeholders": [
{
"placeholder": "<item_price>",
"type": "text",
"value": "$10.00"
}
]
}
]
},
{
"tableColumns": [
{
"placeholders": [
{
"placeholder": "<item_name>",
"type": "text",
"value": "Gadget"
}
]
},
{
"placeholders": [
{
"placeholder": "<item_price>",
"type": "text",
"value": "$25.00"
}
]
}
]
}
]
}
]
}Response
200 OK
Content-Type: application/pdf Content-Disposition: attachment; filename="document.pdf"
The response body here is the finished PDF itself, as binary bytes - not shown.