POST /render

POST/renderrequiere x-api-key
Genera un documento .docx o .pdf a partir del slug y los datos.

Request

JSON body, máx 1 MB. Header content-type: application/json.

CampoTipoRequeridoNotas
slugstringSlug de la plantilla.
dataobjectValores que se inyectan en la plantilla.
format"docx" | "pdf"noDefault docx.
filenamestringnoNombre sugerido del archivo en el header Content-Disposition. Si se omite, default <slug>-<timestamp>.<ext>.

Ejemplo: docx con loop, condicional y nested

curl -X POST http://localhost:3000/render \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  -o factura.docx \
  -d '{
    "slug": "factura-demo",
    "data": {
      "folio": "F-001",
      "cliente": { "nombre": "Acme S.A. de C.V." },
      "items": [
        { "descripcion": "Servicio mensual", "cantidad": 12 },
        { "descripcion": "Soporte premium",  "cantidad": 1  }
      ],
      "descuento": 10
    }
  }'

Ejemplo: PDF

curl -X POST http://localhost:3000/render \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  -o factura.pdf \
  -d '{
    "slug": "factura-demo",
    "format": "pdf",
    "filename": "factura-001.pdf",
    "data": { ... }
  }'
Requiere LibreOffice
El render PDF se hace pasando el .docx por LibreOffice headless. Si no está instalado, devuelve 503 LIBREOFFICE_UNAVAILABLE. La imagen Docker ya lo trae; en local instala con brew install --cask libreoffice (macOS) o apt-get install -y libreoffice (Debian/Ubuntu).

Ejemplo: con imagen

Si tu plantilla tiene {%logo} (en una celda 1×1, ver sintaxis), pasa la imagen como data URI base64, URL HTTP/HTTPS o ruta relativa a IMAGES_DIR.

# Como data URI (caso más común)
PNG_B64=$(base64 -i ./logo.png)
curl -X POST http://localhost:3000/render \
  -H "x-api-key: $API_KEY" \
  -H "content-type: application/json" \
  -o factura.docx \
  -d "{
    \"slug\": \"factura-demo\",
    \"data\": {
      \"folio\": \"001\",
      \"cliente\": { \"nombre\": \"Acme\" },
      \"logo\": \"data:image/png;base64,$PNG_B64\"
    }
  }"
// Como URL pública (con SSRF guard activo)
{
  "slug": "factura-demo",
  "data": {
    "logo": "https://cdn.clickbalance.com/assets/logo.png"
  }
}

// Como path local (tienes que montar IMAGES_DIR fuera de banda)
{
  "slug": "factura-demo",
  "data": {
    "logo": "logos/empresa-acme.png"
  }
}

Ejemplo: Node.js

const fs = require('node:fs');

const res = await fetch('http://localhost:3000/render', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'x-api-key': process.env.API_KEY
  },
  body: JSON.stringify({
    slug: 'factura-demo',
    data: {
      folio: 'F-001',
      cliente: { nombre: 'Acme' },
      items: [{ descripcion: 'Servicio', cantidad: 1 }]
    }
  })
});

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

const buffer = Buffer.from(await res.arrayBuffer());
fs.writeFileSync('factura.docx', buffer);

Validación contra schema

Si la plantilla tiene un <slug>.schema.json asociado, el data es validado con ajv antes de renderizar. Datos inválidos → 422 INVALID_DATA:

{
  "error": {
    "code": "INVALID_DATA",
    "message": "Data does not conform to template schema",
    "requestId": "ed1b29da-...",
    "details": {
      "slug": "factura-demo",
      "errors": [
        {
          "instancePath": "",
          "schemaPath": "#/required",
          "keyword": "required",
          "params": { "missingProperty": "cliente" },
          "message": "must have required property 'cliente'"
        }
      ]
    }
  }
}

Códigos de error específicos

  • 400 INVALID_INPUT — body no es JSON válido, falta slug/data, o slug malformado.
  • 404 TEMPLATE_NOT_FOUND — el slug no existe.
  • 422 INVALID_DATA — los datos no cumplen el JSON Schema de la plantilla.
  • 500 RENDER_ERROR — docxtemplater falló (sintaxis de plantilla incorrecta, etiqueta de imagen fuera de tabla, etc).
  • 500 CONVERSION_ERROR — LibreOffice falló al convertir a PDF.
  • 502 IMAGE_FETCH_ERROR — imagen URL inalcanzable, IP privada bloqueada (SSRF), o demasiado grande.
  • 503 LIBREOFFICE_UNAVAILABLE — solicitaste PDF y LibreOffice no está instalado.