The API

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.

POST/api/v1/render(exposed in production as /v1/render)
Synchronous by default
The response to the request is the binary itself. There is no polling in the common case: you send the body and get the file back — in ~10ms on the native engine (templates) or up to ~1s on Chromium (raw HTML). Set a 60s timeout on the client for heavy documents.

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.

FieldTypeDefaultDescription
templatestringSlug of a template saved in your organization. Compiles the document model with the data (resolves tables, QR, charts and variables).
htmlstringRaw HTML. Gets scalar substitution of {{var}} variables (including fallback {{x|default}}) and is rendered via Chromium.
dataobject{}Object with the data to inject. Nested paths are accessible via dot notation: {{client.name}}.
format"pdf" | "png""pdf"Output file format.
pdfobjectPDF-specific options (see below). Ignored when format is png.
pngobjectPNG-specific options (see below). Ignored when format is pdf.
watermarkbooleantrueAccepted 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.

template OR html
You need exactly one content path. With neither, the API responds 422 invalid_body. If you send both, template takes precedence.

The “template” path vs. the “html” path

Aspecttemplate (slug)html (raw)
What it rendersThe DocumentModel saved in the editorThe HTML string you sent
VariablesResolved in the model (scalars + tables/QR/charts)Scalar substitution of {{var}} and {{x|fallback}}
EngineNative when possible (deterministic), otherwise ChromiumAlways Chromium
When to useRecurring, structured documentsOne-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.

FieldTypeDefaultDescription
pageFormatenum"A4"Sheet size: A4, Letter, Legal, A3 or A5.
landscapebooleanfalseLandscape orientation when true.
printBackgroundbooleanfalsePrints CSS background colors and images. Turn it on for colored backgrounds.
marginobjectMargins per side: { top, right, bottom, left }, each one a CSS string (e.g. "20mm", "1in", "24px"). You can set only the sides you want.
scalenumber1Content scale factor, between 0.1 and 2.
Backgrounds not showing up?
By default Chromium does not print backgrounds. If your document has background colors or images covering the sheet, send "printBackground": true.

PNG options — png object

They apply when format is png. Useful for thumbnails, social posts and previews.

FieldTypeDefaultDescription
fullPagebooleanfalseCaptures the full page (the entire content height) instead of the viewport.
widthintegerViewport width in pixels (positive integer).
heightintegerViewport height in pixels (positive integer).

Full body (all fields)

A visual reference with every field present — in practice you only send what you need:

application/json
{
  "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:

HeaderTypeDescription
Content-Typestringapplication/pdf or image/png.
X-Render-MsnumberRender duration in milliseconds.
X-Render-BytesnumberSize of the generated file in bytes.
X-Render-EnginestringEngine used: native or chromium.
X-Renderly-Document-HashstringSHA-256 (hex) of the delivered bytes. Basis of the verification.
X-Renderly-Verify-UrlstringPublic URL to verify the document (/verify/<id>).
X-Renderly-Verify-IdstringIdentifier of the recorded issuance (UUID).
Verify-Url / Verify-Id may be missing
The verification headers only appear when the issuance was recorded successfully. The X-Renderly-Document-Hash always comes back. See Verifiable documents.

Complete examples

Raw HTML → A4 PDF with margins

curl
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.pdf

Template → 1080×1350 PNG

curl
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.png

JavaScript — download and save the binary

node
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

python
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:

ClientHow to read the binarySave
curl--output file.pdf
fetch / Nodeawait res.arrayBuffer()fs.writeFile(...)
Python requestsres.contentopen(path, "wb")
Browser (fetch)await res.blob()URL.createObjectURL + <a download>
Don’t use res.json() on success
On 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:

proxy → download
// 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

HTTPcodeWhen it happens
401missing_api_keyNo authentication header.
401invalid_api_keyInvalid or revoked key.
402quota_exceededMonthly organization quota exhausted.
404template_not_foundThe template slug does not exist in your organization.
422invalid_bodyInvalid body (missing template/html, wrong type, etc.).
422template_emptyThe template exists but has no content.
500render_failedFailed to render (invalid HTML, unreachable resource, etc.).

Details of each code and handling strategies in Errors.

Next
Templates
Create in the editor, publish and use via slug with variables.