renderpaper

API reference

One base URL, JSON in, application/pdf out. Every endpoint below is live on https://renderpaper.com.

Authentication

Create a key under API keys in your account. Send it as either header — both work identically:

Headers
X-API-Key: rs_live_…
— or —
Authorization: Bearer rs_live_…

A key is shown once, when it is created. Store it somewhere your application can read it and treat it like a password — anyone holding it can render documents billed to your account. Revoking a key takes effect immediately.

Your first render

The shortest path: post HTML and data together, get a PDF back in the response body. Nothing is stored.

curl
$ curl -X POST https://renderpaper.com/v1/render \
    -H "X-API-Key: $RENDERPAPER_KEY" \
    -H "Content-Type: application/json" \
    -d '{"template":"<h1>Invoice {{.Number}}</h1>","data":{"Number":"2026-0142"}}' \
    -o invoice.pdf

# invoice.pdf — 1 page, A4

Template syntax

Templates are Go html/template. Every field of data is available by name, and values are HTML-escaped as they are injected, so customer-supplied text cannot break your layout or inject markup.

SyntaxDoes
{{.Field}}Insert a value by name.
{{.A.B}}Reach into nested objects.
{{range .Lines}}…{{end}}Repeat a block over an array. Inside, . is the current element.
{{if .Paid}}…{{else}}…{{end}}Conditional block.
{{printf "%.2f" .Total}}Format a number.

A template that fails to parse or execute returns 400 with the actual error message, so it is debuggable from the response rather than by guesswork.

Render inline HTML

POST/v1/render

Returns application/pdf. Use this when the template lives in your codebase.

FieldTypeNotes
templatestringrequired HTML, with optional placeholders.
dataany JSONValues injected into the template — object, array or scalar.
optionsobjectSee options.

Render a stored template

POST/v1/templates/{id}/render

Returns application/pdf. Use this when someone other than a developer owns the design — you send only the data, and edits made in the editor apply to the next render with no code change.

Request body
{
  "data": { "Number": "2026-0142", "Customer": "Northwind Ltd" },
  "options": { "paper": "a4" }
}

Options

All optional, on every render endpoint.

FieldTypeDefaultNotes
format"pdf" | "png" | "jpeg" | "webp""pdf"What comes back. See image output.
paper"a4" | "letter""a4"PDF only. Case-insensitive.
landscapebooleanfalse
print_backgroundbooleantrueSet false to drop background colours and images.
margin_innumber0.4Inches, applied to all four sides. Maximum 5.

Your CSS wins. An @page rule in the template takes precedence over paper, landscape and margin_in. That is deliberate — a template carrying its own page geometry renders identically wherever it is sent from. If the paper option appears to be ignored, look for @page in your CSS.

Image output

Set format to png, jpeg or webp on any render endpoint and the response is that image instead of a PDF. Same template, same data, same metering — an image counts as one document, however tall it is.

curl
$ curl -X POST https://renderpaper.com/v1/render \
    -H "X-API-Key: $RENDERPAPER_KEY" \
    -H "Content-Type: application/json" \
    -d '{"template":"<h1>{{.Title}}</h1>","data":{"Title":"Launch"},
         "options":{"format":"png","width":1200,"height":630}}' \
    -o card.png

# card.png — 1200x630 CSS pixels at 2x, ready for an og:image tag
FieldTypeDefaultNotes
widthnumber800CSS pixels the page is laid out at. Max 4000.
heightnumberfull pageOmit it and the image extends to the laid-out height of the page. Max 12000.
scale1 | 2 | 32Device pixels per CSS pixel. 2 is crisp on ordinary screens.
quality1–10090jpeg and webp only; png is lossless.

A screenshot has a viewport, not a page. The paper, landscape and margin_in options apply to PDFs only, and an @page rule in your CSS is ignored for images. One hard bound: width or height multiplied by scale may not exceed 16,000 device pixels — a request past that is refused with an explanation, and a measured full-page height beyond it is truncated rather than failed.

Templates are self-contained

The renderer fetches nothing over the network — not images, not stylesheets, not fonts. That is a deliberate property: a render cannot leak data to a third host, cannot slow down on someone else's CDN, and cannot change because a remote file did.

For images, upload them in the editor — references to uploaded assets (/a/…) are inlined into the document automatically at render time — or embed a data: URI directly. For type, use the built-in font set (Liberation and Noto cover ordinary documents) or embed a font as @font-face with a data: source.

List templates

GET/v1/templates
200 response
[
  {
    "id": "inv_8f2c1a",
    "name": "Invoice",
    "updated_at": "2026-08-04T09:12:44Z",
    "render_url": "https://renderpaper.com/v1/templates/inv_8f2c1a/render"
  }
]

Create a template

POST/v1/templates
FieldTypeNotes
namestringTruncated at 80 characters.
htmlstringrequired Up to 512 KiB.
sample_datastringJSON, as a string. Drives the editor preview. Up to 64 KiB.

Returns 201 and the same object shape as the list endpoint. Templates created through the API are editable in the browser editor, and vice versa — there is one set of templates, not two.

Update a template

PUT/v1/templates/{id}

Same body as create. Renders that begin after the update use the new HTML; documents already accepted in a batch are unaffected, because a batch snapshots the template when it is accepted.

Delete a template

DELETE/v1/templates/{id}

Returns 204. Deleting a template does not affect PDFs already rendered from it.

Submit a batch

POST/v1/batches

For invoice and statement runs: one request instead of hundreds, with retries handled for you and no HTTP timeout hanging over the whole job. Returns 202 Accepted — the documents are queued, not finished.

Paid plans only. A free account gets 402 here and should render documents one at a time with POST /v1/render instead. Queue position also follows the plan: higher tiers are claimed first, and within a tier the queue is first-in-first-out, so a large run is never starved by a trickle behind it.

FieldTypeNotes
template_idstringUse a stored template. One of this or html is required.
htmlstringInline HTML instead of a stored template.
optionsobjectApplied to every document in the batch, including format — a batch can produce images.
itemsarrayrequired Up to 500 objects, each {"data": …}.
webhook_urlstringOptional. https URL that receives a signed POST on completion. See webhooks.
202 response
{
  "batch_id": "bat_7d1e04",
  "count": 3,
  "status_url": "https://renderpaper.com/v1/batches/bat_7d1e04",
  "items": [
    { "id": "job_a1", "position": 0,
      "pdf_url": "https://renderpaper.com/v1/jobs/job_a1/pdf" }
  ]
}

The template is snapshotted at acceptance. Editing it while the batch runs cannot change documents already accepted, so a long run stays internally consistent. A template that fails to parse fails the whole batch immediately with 400, rather than recording hundreds of identical failures.

Batch status

GET/v1/batches/{id}
200 response
{
  "batch_id": "bat_7d1e04",
  "complete": false,
  "counts": { "total": 3, "queued": 1, "running": 1, "done": 1, "failed": 0 },
  "created_at": "2026-08-04T09:14:02Z",
  "items": [
    { "id": "job_a1", "position": 0, "status": "done",
      "pdf_bytes": 24518,
      "pdf_url": "https://renderpaper.com/v1/jobs/job_a1/pdf" }
  ]
}

Poll this until complete is true. An item that failed every attempt carries an error field explaining why.

Fetch a document

GET/v1/jobs/{id}/pdf

Returns application/pdf once the document is rendered.

While the job is still queued or running this returns 409 with a Retry-After header — not 404. The job exists; it simply has no result yet. Treat 409 as "poll again" and 404 as "wrong id".

Batch results are deleted automatically 24 hours after they are created, so fetch what you need within that window.

Completion webhooks

Add webhook_url to a batch and we POST the outcome to it once every document has settled — no polling. The URL must be https on a public host.

Payload
{
  "event": "batch.completed",
  "batch_id": "bat_7d1e04",
  "counts": { "total": 3, "done": 3, "failed": 0 },
  "items": [ { "id": "job_a1", "status": "done", "pdf_url": "…" } ],
  "timestamp": "2026-08-05T18:40:12Z"
}

Verifying the signature

Every delivery carries X-Renderpaper-Signature: hex(HMAC-SHA256(body, key)) where the key is hex(SHA256(your API key)) — derived from something you already hold, so there is no extra secret to store.

Node
const key = crypto.createHash('sha256').update(process.env.RENDERPAPER_KEY).digest('hex');
const sig = crypto.createHmac('sha256', key).update(rawBody).digest('hex');
// compare sig with the X-Renderpaper-Signature header, constant-time

Delivery is at-least-once. Two documents finishing simultaneously can both observe a completed batch, so deduplicate on batch_id. Three attempts over ~15 seconds; after that the failure is logged on our side and batch status remains the source of truth.

Zapier, Make and n8n

There is no marketplace app to install — the API is plain HTTP, which every automation tool already speaks. Three working recipes:

Zapier

Add a Webhooks by Zapier → Custom Request step: method POST, URL https://renderpaper.com/v1/render, headers X-API-Key and Content-Type: application/json, and the JSON body from your first render with fields mapped from earlier steps. The response is the PDF; feed it to any step that accepts a file.

Make

Use the HTTP → Make a request module with the same method, URL and headers. Body type Raw / JSON. Tick Parse response off — the response is binary — and map the output to Google Drive, email, or wherever the document goes.

n8n

An HTTP Request node, response format File. For batches, pair it with a Webhook node and pass that node's URL as webhook_url — n8n resumes when the batch completes, no polling loop.

These are recipes against the generic HTTP modules, not published marketplace apps; the field names above are exact.

Errors

Every error is JSON: {"error": "…"}.

StatusMeaning
400Invalid template, data or options. The parse or execution error is returned verbatim.
401Missing, malformed or revoked API key.
404No such template, batch or job — or it belongs to another account.
409The document is not rendered yet. Retry after the interval in Retry-After.
413Request body over the size cap.
429Per-minute rate limit, or the monthly document allowance on a free plan. Retry-After is set — for an allowance it points at the start of next month.
503Renderer at capacity. Retry shortly; Retry-After is set.
500Render failure on our side.

Limits

LimitValue
Request body, render endpoints1 MiB
Request body, batch endpoint8 MiB
Documents per batch500
Template HTML512 KiB
Sample data per template64 KiB
Templates per account200
Batch result retention24 hours

Requests are also rate limited per account, and each plan includes a monthly document allowance — see pricing. On a paid plan, going over the allowance is billed rather than blocked, because a hard stop mid-run is the worse failure. On the free plan rendering stops at the allowance, since there is no card on file to bill the extra to. A failed render is never counted against the allowance.