Renduo
Guides

Backend examples — Node, Next.js, Python

Working code for calling the Renduo API from the three most common backends.

Ready-to-copy snippets for calling Renduo from common backends. All of them authenticate with a Bearer token (RENDUO_API_KEY), generate a PDF in sync mode, and save it. For background volume, switch mode to async and receive a webhook — see the async + webhooks guide.

Node.js

If your backend runs on Node.js, use the official @renduo/sdk — it returns the PDF already decoded as a Buffer (no base64 step) and handles errors and async polling for you:

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: process.env.TEMPLATE_ID!,
  payload: { title: 'Invoice 2026-001', customer: 'Acme Corp' },
})

await writeFile('output.pdf', pdf)

Prefer the raw API over an extra dependency? Here's the same thing with plain fetch:

// generate.mjs
import { writeFileSync } from 'node:fs'

const API_URL = 'https://api.renduo.dev'
const API_KEY = process.env.RENDUO_API_KEY
const TEMPLATE_ID = process.env.TEMPLATE_ID

const res = await fetch(`${API_URL}/v1/generate`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    templateId: TEMPLATE_ID,
    mode: 'sync',
    payload: { title: 'Invoice 2026-001', customer: 'Acme Corp' },
  }),
})

if (!res.ok) {
  const err = await res.json()
  throw new Error(`${err.error}: ${err.message}`)
}

const data = await res.json()
writeFileSync('output.pdf', Buffer.from(data.pdf, 'base64'))
console.log(`Saved output.pdf (${data.durationMs}ms)`)

Next.js (Route Handler / Server Action)

// app/api/pdf/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const body = await req.json()

  const res = await fetch('https://api.renduo.dev/v1/generate', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.RENDUO_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      templateId: body.templateId,
      mode: 'sync',
      payload: body.payload,
    }),
    // Keep server actions / route handlers from timing out the render window.
    // Renduo's own timeout is 10s; give this request a little more.
    signal: AbortSignal.timeout(12_000),
  })

  if (!res.ok) {
    const err = await res.json()
    return NextResponse.json({ error: err.error, message: err.message }, { status: res.status })
  }

  const data = await res.json()
  // Return the PDF as a binary download; use base64 only if you need to embed
  // it in a JSON response.
  return new NextResponse(Buffer.from(data.pdf, 'base64'), {
    headers: { 'Content-Type': 'application/pdf' },
  })
}

Important: always call Renduo from the server (Route Handler or Server Action) — never from client components. Your API key must never reach the browser.

Python

# generate.py
import base64
import json
import os
import urllib.request

API_URL = "https://api.renduo.dev"
API_KEY = os.environ["RENDUO_API_KEY"]
TEMPLATE_ID = os.environ["TEMPLATE_ID"]

body = json.dumps({
    "templateId": TEMPLATE_ID,
    "mode": "sync",
    "payload": {"title": "Invoice 2026-001", "customer": "Acme Corp"},
}).encode()

req = urllib.request.Request(
    f"{API_URL}/v1/generate",
    data=body,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
)

with urllib.request.urlopen(req) as res:
    data = json.loads(res.read())

with open("output.pdf", "wb") as f:
    f.write(base64.b64decode(data["pdf"]))

print(f"Saved output.pdf ({data['durationMs']}ms)")

Or with requests:

import base64, os, requests

res = requests.post(
    "https://api.renduo.dev/v1/generate",
    headers={
        "Authorization": f"Bearer {os.environ['RENDUO_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "templateId": os.environ["TEMPLATE_ID"],
        "mode": "sync",
        "payload": {"title": "Invoice 2026-001", "customer": "Acme Corp"},
    },
    timeout=15,
)
res.raise_for_status()

with open("output.pdf", "wb") as f:
    f.write(base64.b64decode(res.json()["pdf"]))

Handling errors

Every backend should handle the structured error shape:

{
  "error": "VALIDATION_ERROR",
  "message": "Invalid data.",
  "details": { "": "…" },
  "requestId": "…"
}

Common codes: VALIDATION_ERROR (400), TEMPLATE_NOT_FOUND (404), QUOTA_EXCEEDED (402), GENERATION_TIMEOUT (504), RENDER_ERROR (500). Check the status code first, then read error for the machine-readable code and log requestId for support.

See also

On this page