Node.js SDK (@renduo/sdk)
Generate PDFs from TypeScript or Node.js with the official SDK — typed, zero runtime dependencies, and no base64 to decode by hand.
If your backend runs on Node.js, the official SDK is the fastest way to call
Renduo: it is a thin, typed client over native fetch with zero runtime
dependencies, and it returns the PDF already decoded as a Buffer — no
manual base64 handling, no hand-rolled error parsing.
Not required. The SDK is a convenience layer over the same HTTP API the quickstart calls with
curl. If you prefer raw requests (Node, Python, Go, …) or you only need a one-off script, skip it — see the backend examples.
Install
npm install @renduo/sdkRequires Node ≥ 20 (native fetch). ESM only — see
Module format below.
Quickstart
import { Renduo } from '@renduo/sdk'
import { writeFile } from 'node:fs/promises'
const renduo = new Renduo({ apiKey: process.env.RENDUO_API_KEY! })
const { pdf } = await renduo.generate({
templateId: '5f0c2e3a-…', // Dashboard → Templates, or renduo templates list
payload: {
title: 'Invoice 2026-001',
customer: { name: 'Acme Corp' },
items: [
{ description: 'Starter Plan', amount: 19000 },
{ description: 'Tax (19%)', amount: 3610 },
],
total: 22610,
},
})
await writeFile('invoice.pdf', pdf)pdf is a Buffer you can write to disk, stream into an HTTP response, or
attach to an email — no base64 step. sync is the default mode: the call
waits for the PDF and returns once it's ready.
Async generation
For high volume or background jobs, use mode: 'async' and either poll with
waitForGeneration or listen for the generation.completed webhook
registered in Dashboard → Settings → Webhooks.
const job = await renduo.generate({ templateId, payload, mode: 'async' })
// job: { jobId, status: 'queued' }
// Polls GET /v1/generate/:jobId until ready, then downloads the PDF.
const result = await renduo.waitForGeneration(job.jobId)
// result.pdf is a Buffer — same as sync mode.waitForGeneration polls every 1.5s with a 60s client-side timeout (both
configurable via { pollIntervalMs, timeoutMs }). It throws
RenduoGenerationError if the job ends in failed or timeout, with the
job's errorMessage and status attached:
import { RenduoGenerationError } from '@renduo/sdk'
try {
const result = await renduo.waitForGeneration(job.jobId)
} catch (err) {
if (err instanceof RenduoGenerationError) {
console.error(`${err.status}: ${err.message}`) // e.g. "failed: …"
}
}For an event-driven pipeline instead of polling, register a webhook endpoint
that subscribes to generation.completed / generation.failed and verify the
incoming signature with the SDK (below).
Templates
const { templates, total } = await renduo.templates.list()
const template = await renduo.templates.get(templates[0].id)
// template.versions: [{ version, status: 'active' | 'disabled', createdAt }, …], newest firsttemplates.get(id) throws RenduoApiError with code: 'TEMPLATE_NOT_FOUND'
if the template doesn't exist.
Rolling back a version
templates.activate(id, version) makes a previously published version the
active one again, disabling the current one in a single transaction:
const { activeVersion, previousActiveVersion } = await renduo.templates.activate(templateId, 2)
// → activeVersion: 2, previousActiveVersion: 3It is idempotent — activating the version that is already active resolves
without changing anything, so a retried deploy step is safe. It throws
RenduoApiError with code: 'VERSION_NOT_ACTIVATABLE' (409) if that version
never finished publishing, and 'TEMPLATE_NOT_FOUND' (404) if the template or
version doesn't exist. Requires a key with the templates:write scope.
See Templates & versions for what a rollback does and does not change.
Verifying webhooks
import { verifyWebhookSignature } from '@renduo/sdk'
// Inside your webhook route handler:
const signature = request.headers.get('renduo-signature')
const rawBody = await request.text() // raw body — never a re-serialized JSON.parse/stringify round trip
if (!verifyWebhookSignature(process.env.RENDUO_WEBHOOK_SECRET!, rawBody, signature!)) {
return new Response('Invalid signature', { status: 401 })
}
const event = JSON.parse(rawBody) // { event: 'generation.completed', generationId, pdfUrl, … }The function returns false — never throws — for a malformed header, a
signature mismatch, or a timestamp older than 5 minutes (replay protection).
Error handling
Every non-2xx response throws RenduoApiError with the API's typed error
code, message, and request id:
import { RenduoApiError } from '@renduo/sdk'
try {
await renduo.generate({ templateId, payload })
} catch (err) {
if (err instanceof RenduoApiError && err.code === 'QUOTA_EXCEEDED') {
// Free plan limit reached — paid plans accumulate overage instead.
}
throw err
}See the errors reference for the full list of codes.
Module format
ESM only. A CommonJS require('@renduo/sdk') won't work — run your project
as ESM ("type": "module" in package.json, or .mjs) or use dynamic
import('@renduo/sdk') from CommonJS code.
See also
- Backend examples — the same flows with raw
fetch/ Python, no SDK. - First PDF in 5 minutes — register a template with the CLI first, if you haven't.
- API reference — every endpoint the SDK wraps.