API documentation
DocForge turns an HTML template and JSON data into a PDF or PNG. One endpoint, one key, no SDK needed.
curl https://api.docforge.dev/v1/render \
-H "Authorization: Bearer $DOCFORGE_KEY" \
-H "Accept: application/pdf" \
-d '{"html":"<h1>Hello {{name}}</h1>","data":{"name":"World"}}' \
-o document.pdfAuthentication
Send your key as Authorization: Bearer df_live_… or X-API-Key: df_live_…. Keys are shown once; revoke and recreate them in the dashboard. Rate limit: 10 requests per second per key, bursts up to 30.
Render a document
POST /v1/render with a JSON body:
| Field | Type | Description |
|---|---|---|
template_id | string | A saved template. Use this or html. |
html, css | string | Inline template, up to 5 MB. A fragment is fine; we add the document wrapper. |
data | object | Values for the placeholders. |
output | "pdf" | "png" | Default pdf. |
options | object | Overrides the template's page options, see below. |
async | boolean | Queue the job and return 202 immediately. |
webhook_url | https URL | With async: where we POST the result. |
Getting the file. With Accept: application/pdf (or image/png) the response body is the file, and the headers X-Render-Id, X-Pages, X-Credits-Used, X-Credits-Remaining describe it. Without it you get JSON with a link valid for 24 hours:
{
"id": "rnd_lt6vl7zp2nx3h6ygtooy",
"status": "succeeded",
"output": "pdf",
"pages": 1,
"credits_used": 1,
"credits_remaining": 99,
"url": "https://api.docforge.dev/files/r/rnd_lt6vl7zp2nx3h6ygtooy.pdf?exp=…&sig=…",
"expires_at": "2026-10-05T10:53:21Z"
}Other endpoints: GET /v1/renders/:id (status and a fresh link), GET /v1/templates, GET /v1/templates/:id, GET /v1/account (balance).
Page options
| Option | Applies to | Default |
|---|---|---|
format | A4. Also A3, A5, Letter, Legal | |
landscape | false | |
margin_mm | 10 | |
width | PNG | 1200 px |
height | PNG | 0 = full page |
scale | PNG | 1. Use 2 for retina |
CSS @page rules in the template win over these. Images and fonts load from public URLs (Google Fonts works); private and internal addresses are blocked. Rendering times out after 30 seconds; documents are limited to 200 pages.
Templates & helpers
Templates use Handlebars. {{x}} is HTML-escaped, {{{x}}} inserts raw HTML. Loop with {{#each items}}…{{/each}} and use ../field to reach outside the loop.
{{formatMoney total "EUR"}} → €1,234.50
{{formatNumber 1234.5 2}} → 1,234.50
{{formatDate issued_at "Jan 2, 2006"}} → Oct 4, 2026 (Go date layout)
{{multiply quantity unit_price}} {{plus a b}}
{{sumBy items "amount"}} {{upper s}} {{lower s}}
{{orDefault note "—"}} {{#ifEq status "paid"}}PAID{{/ifEq}}Page breaks: <div style="page-break-after: always"></div>. Repeat table headers on every page with thead { display: table-header-group }.
Async & webhooks
Send "async": true and an optional webhook_url. We POST {"type": "render.succeeded" | "render.failed", "data": {…}} and retry with backoff for about 3 hours until your endpoint answers 2xx. Verify the X-DocForge-Signature header with the webhook secret from the API keys page:
import crypto from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay protection
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Errors
Every error looks like {"error": {"code": "…", "message": "…"}}. Failed renders never cost credits.
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_request | Invalid JSON or fields |
| 401 | unauthorized | Missing, wrong or revoked key |
| 402 | insufficient_credits | Top up in Billing |
| 404 | template_not_found | No such template on your account |
| 413 | too_large | HTML over 5 MB |
| 422 | template_error, too_many_pages | Handlebars syntax error, or over 200 pages |
| 429 | rate_limited | Slow down; see Retry-After |
| 504 | render_timeout | Took over 30 s, usually a slow external image |
AI agents (MCP)
DocForge is an MCP server at https://api.docforge.dev/mcp (Streamable HTTP) with tools render_document, list_templates and get_balance.
claude mcp add --transport http docforge https://api.docforge.dev/mcp \
--header "Authorization: Bearer df_live_…"Then ask your agent: "Make an invoice PDF for Globex: 3 hours of consulting at $120."
No-code: n8n, Zapier, Make, Activepieces
Every integration has the same idea: pick a template, map your data onto its fields, and get the PDF back as a file the next step can attach to an email or upload to Drive.
| Tool | How to add it |
|---|---|
| n8n | Settings → Community nodes → install n8n-nodes-docforge. Also works as a tool for n8n AI agents. |
| Zapier | Search for DocForge when adding an action, then choose Render Document. |
| Make | Add the DocForge app to a scenario and use Render a document. |
| Activepieces | Add the DocForge piece and use Render Document. |
Anything else (Pipedream, Power Automate, Retool, Bubble): use an HTTP request step with POST https://api.docforge.dev/v1/render, the header Authorization: Bearer df_live_… (or X-API-Key) and the JSON body above. Leave out Accept to get a link, or set Accept: application/pdf to get the file.
ChatGPT & OpenAPI
The API is described in OpenAPI 3.1 at https://api.docforge.dev/openapi.json. Import it into Postman, Dify, Flowise, Langflow or any code generator.
Custom GPT: in the GPT editor open Configure → Actions → Create new action, choose Import from URL and paste the link above. Set authentication to API key → Bearer with your key. Your GPT can now list your templates and hand users a download link to a finished PDF.
Credits
1 credit = 1 PDF page or 1 PNG. Free credits (100 a month) are used first and refill monthly; purchased credits never expire. Previews in the dashboard editor are free and watermarked.