Last updated: . Every code sample on this page was run against the live API with the sandbox key on that date.
Short answer: POST your HTML as JSON to https://rendlet.com/v1/pdf with an Authorization: Bearer header and write the response body to a file. Node 18+ has fetch built in, so there is nothing to install. Rendlet renders with headless Chromium on its side, so you don't ship a browser in your app or container.
The public sandbox key needs no signup (5 renders a day per network, 1 page, up to 50 KB, faint watermark).
# Works as-is with the public sandbox key. With your own key: export RENDLET_API_KEY=YOUR_API_KEY
curl -X POST https://rendlet.com/v1/pdf \
-H "Authorization: Bearer ${RENDLET_API_KEY:-sandbox}" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Hello</h1><p>Rendered by Rendlet.</p>","options":{"format":"A4","margin":"15mm"}}' \
-o hello.pdf
htmlToPdf() helperSave as render.mjs and run node render.mjs. It builds HTML from data with a template literal, renders it, and saves order.pdf. Output when we ran it: saved order.pdf (1 page, 15513 bytes); 4 renders left.
// Node 18+ (built-in fetch). Save as render.mjs and run: node render.mjs
// Uses the public sandbox key unless you set RENDLET_API_KEY=YOUR_API_KEY
import { writeFile } from "node:fs/promises";
async function htmlToPdf(html, options = { format: "A4", margin: "15mm" }) {
const res = await fetch("https://rendlet.com/v1/pdf", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RENDLET_API_KEY ?? "sandbox"}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ html, options }),
});
if (!res.ok) {
// Errors are JSON, e.g. {"error":"quota_exceeded", ...}
throw new Error(`Rendlet ${res.status}: ${await res.text()}`);
}
return {
pdf: Buffer.from(await res.arrayBuffer()),
pages: res.headers.get("x-pages"),
remaining: res.headers.get("x-quota-remaining"),
};
}
const order = { id: "A-1042", customer: "Globex Corp", total: "$2,035.00" };
const html = `<!doctype html>
<html><body style="font-family:sans-serif;padding:24px">
<h1 style="color:#1f4e79">Order ${order.id}</h1>
<p>Customer: ${order.customer}</p>
<p><b>Total: ${order.total}</b></p>
</body></html>`;
const { pdf, pages, remaining } = await htmlToPdf(html);
await writeFile("order.pdf", pdf);
console.log(`saved order.pdf (${pages} page, ${pdf.length} bytes); ${remaining} renders left`);
In a real app, escape user-supplied values before putting them into HTML (or use your usual template engine, e.g. the one that already renders your emails), then pass the string to htmlToPdf().
| Field | Values |
|---|---|
options.format | A4, Letter, Legal, A3, A5 |
options.landscape | true / false |
options.margin | CSS length, e.g. "15mm" or "0" |
options.printBackground | true / false |
filename | Name used in the Content-Disposition header |
response | "binary" (default) or "base64": JSON {filename, pages, bytes, content_type, base64}, handy for serverless functions and AI agents |
For HTML input, JavaScript in the page is not run, and only data: URIs and public https:// assets (images, fonts, CSS) load. Inline your CSS or link it over https. Simplified Chinese works out of the box (Noto Sans SC is bundled).
Successful renders return X-Pages, X-Plan, X-Quota-Limit and X-Quota-Remaining. Errors are JSON like {"error":"...","message":"..."}: 400 bad request, 401 missing key, 403 invalid key, 413 HTML too large or too many pages, 429 quota exceeded, 500 render failed.
Once you have the Buffer, send it with Content-Type: application/pdf, or upload it to your storage. Keep your API key in an environment variable on the server, never in browser code.
| Sandbox | Free | Starter | Pro | Scale | |
|---|---|---|---|---|---|
| Price | $0 | $0 | $7/mo | $19/mo | $59/mo |
| Documents | 5 / day / network | 100 / month | 1,000 / month | 5,000 / month | 25,000 / month |
| Requests per minute | 3 | 10 | 30 | 60 | 120 |
| Pages per document | 1 | 10 | 50 | 200 | 500 |
| HTML size | 50 KB | 1 MB | 2 MB | 2 MB | 2 MB |
Every plan has a hard monthly cap: at the cap the API returns 429 with an upgrade_url, and you are never billed for overage. Failed renders don't count. Source: docs.
If your page needs JavaScript to render (client-side charts, SPAs), a login session, or you want to print a live URL, run a browser yourself. See Rendlet vs self-hosted Puppeteer for the trade-offs.