Render endpoint
This is the heart of Renderly. A single endpoint takes your HTML or template, injects the data and returns the rendered document. Here is every body parameter, all the PDF and PNG options, the response headers and complete download examples.
/api/v1/render(exposed in production as /v1/render)Authentication
Send your API key in Authorization: Bearer rndr_live_... or X-API-Key: rndr_live_.... Details in Authentication.
Request body
Content-Type application/json. You must provide one of template or html. All other fields are optional.
| Field | Type | Default | Description |
|---|---|---|---|
| template | string | — | Slug of a template saved in your organization. Compiles the document model with the data (resolves tables, QR, charts and variables). |
| html | string | — | Raw HTML. Gets scalar substitution of {{var}} variables (including fallback {{x|default}}) and is rendered via Chromium. |
| data | object | {} | Object with the data to inject. Nested paths are accessible via dot notation: {{client.name}}. |
| format | "pdf" | "png" | "pdf" | Output file format. |
| object | — | PDF-specific options (see below). Ignored when format is png. | |
| png | object | — | PNG-specific options (see below). Ignored when format is pdf. |
| watermark | boolean | true | Accepted for compatibility, but ignored: the verification seal (QR → /verify) is standard and mandatory on every PDF. Co-branding is Enterprise-only. |
* Marked fields are conditionally required — template or html.
422 invalid_body. If you send both, template takes precedence.The “template” path vs. the “html” path
| Aspect | template (slug) | html (raw) |
|---|---|---|
| What it renders | The DocumentModel saved in the editor | The HTML string you sent |
| Variables | Resolved in the model (scalars + tables/QR/charts) | Scalar substitution of {{var}} and {{x|fallback}} |
| Engine | Native when possible (deterministic), otherwise Chromium | Always Chromium |
| When to use | Recurring, structured documents | One-off layouts, prototypes, HTML you already generate |
The difference between the two variable paths is detailed in Data & variables.
PDF options — pdf object
Control page size, orientation, margins and scale. They apply when format is pdf.
| Field | Type | Default | Description |
|---|---|---|---|
| pageFormat | enum | "A4" | Sheet size: A4, Letter, Legal, A3 or A5. |
| landscape | boolean | false | Landscape orientation when true. |
| printBackground | boolean | false | Prints CSS background colors and images. Turn it on for colored backgrounds. |
| margin | object | — | Margins per side: { top, right, bottom, left }, each one a CSS string (e.g. "20mm", "1in", "24px"). You can set only the sides you want. |
| scale | number | 1 | Content scale factor, between 0.1 and 2. |
"printBackground": true.PNG options — png object
They apply when format is png. Useful for thumbnails, social posts and previews.
| Field | Type | Default | Description |
|---|---|---|---|
| fullPage | boolean | false | Captures the full page (the entire content height) instead of the viewport. |
| width | integer | — | Viewport width in pixels (positive integer). |
| height | integer | — | Viewport height in pixels (positive integer). |
Full body (all fields)
A visual reference with every field present — in practice you only send what you need:
{
"template": "invoice",
"html": null,
"data": { "client": "Acme Ltd", "total": "$1,480.00" },
"format": "pdf",
"pdf": {
"pageFormat": "A4",
"landscape": false,
"printBackground": true,
"margin": { "top": "20mm", "right": "16mm", "bottom": "20mm", "left": "16mm" },
"scale": 1
},
"png": {
"fullPage": true,
"width": 1080,
"height": 1350
},
"watermark": true
}Response
On success (200), the body is the binary of the file — not JSON. The metadata comes in the headers:
| Header | Type | Description |
|---|---|---|
Content-Type | string | application/pdf or image/png. |
X-Render-Ms | number | Render duration in milliseconds. |
X-Render-Bytes | number | Size of the generated file in bytes. |
X-Render-Engine | string | Engine used: native or chromium. |
X-Renderly-Document-Hash | string | SHA-256 (hex) of the delivered bytes. Basis of the verification. |
X-Renderly-Verify-Url | string | Public URL to verify the document (/verify/<id>). |
X-Renderly-Verify-Id | string | Identifier of the recorded issuance (UUID). |
X-Renderly-Document-Hash always comes back. See Verifiable documents.Complete examples
Raw HTML → A4 PDF with margins
curl -X POST https://api.renderly.dev/v1/render \
-H "Authorization: Bearer rndr_live_SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Contract</h1><p>Between {{party_a}} and {{party_b}}.</p>",
"data": { "party_a": "Acme", "party_b": "Beta" },
"format": "pdf",
"pdf": { "pageFormat": "A4", "printBackground": true, "margin": { "top": "25mm", "bottom": "25mm" } }
}' --output contract.pdfTemplate → 1080×1350 PNG
curl -X POST https://api.renderly.dev/v1/render \
-H "X-API-Key: rndr_live_SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"template": "post-instagram",
"data": { "title": "Winter sale" },
"format": "png",
"png": { "width": 1080, "height": 1350, "fullPage": false }
}' --output post.pngJavaScript — download and save the binary
import { writeFile } from "node:fs/promises"
const res = await fetch("https://api.renderly.dev/v1/render", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.RENDERLY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
template: "invoice",
data: { client: "Acme", total: "$1,480.00" },
format: "pdf",
pdf: { pageFormat: "A4", landscape: false },
}),
})
// On error, the body is JSON; on success, it's the binary.
if (!res.ok) {
const { error } = await res.json()
throw new Error(`render failed: ${error.code} — ${error.message}`)
}
const bytes = Buffer.from(await res.arrayBuffer())
await writeFile("invoice.pdf", bytes)
console.log({
ms: res.headers.get("X-Render-Ms"),
engine: res.headers.get("X-Render-Engine"),
hash: res.headers.get("X-Renderly-Document-Hash"),
verify: res.headers.get("X-Renderly-Verify-Url"),
})Python — download and save the binary
import os
import requests
res = requests.post(
"https://api.renderly.dev/v1/render",
headers={"Authorization": f"Bearer {os.environ['RENDERLY_API_KEY']}"},
json={
"template": "invoice",
"data": {"client": "Acme", "total": "$1,480.00"},
"format": "pdf",
"pdf": {"pageFormat": "A4", "landscape": False},
},
timeout=60,
)
# On error, the body is JSON; on success, it's the binary.
if res.status_code != 200:
err = res.json()["error"]
raise RuntimeError(f"render failed: {err['code']} — {err['message']}")
with open("invoice.pdf", "wb") as f:
f.write(res.content)
print({
"ms": res.headers["X-Render-Ms"],
"engine": res.headers["X-Render-Engine"],
"hash": res.headers["X-Renderly-Document-Hash"],
"verify": res.headers.get("X-Renderly-Verify-Url"),
})Downloading the binary
Since the response is the file itself, downloading is just reading the body as bytes and writing it. Things to watch out for per client:
| Client | How to read the binary | Save |
|---|---|---|
curl | — | --output file.pdf |
| fetch / Node | await res.arrayBuffer() | fs.writeFile(...) |
| Python requests | res.content | open(path, "wb") |
| Browser (fetch) | await res.blob() | URL.createObjectURL + <a download> |
200 the body is binary bytes. Calling res.json() will break. Handle JSON only when res.ok is false (error).Forward the PDF straight to the browser
If your server only brokers the download, you can forward the body without buffering everything:
// In an endpoint on YOUR server that forwards the PDF to the browser:
const upstream = await fetch("https://api.renderly.dev/v1/render", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.RENDERLY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ template: "invoice", data }),
})
return new Response(upstream.body, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": 'attachment; filename="invoice.pdf"',
},
})Endpoint errors
| HTTP | code | When it happens |
|---|---|---|
| 401 | missing_api_key | No authentication header. |
| 401 | invalid_api_key | Invalid or revoked key. |
| 402 | quota_exceeded | Monthly organization quota exhausted. |
| 404 | template_not_found | The template slug does not exist in your organization. |
| 422 | invalid_body | Invalid body (missing template/html, wrong type, etc.). |
| 422 | template_empty | The template exists but has no content. |
| 500 | render_failed | Failed to render (invalid HTML, unreachable resource, etc.). |
Details of each code and handling strategies in Errors.
Renderly