POST /render
POST
/renderrequiere x-api-keyGenera un documento .docx o .pdf a partir del slug y los datos.
Request
JSON body, máx 1 MB. Header content-type: application/json.
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
slug | string | sí | Slug de la plantilla. |
data | object | sí | Valores que se inyectan en la plantilla. |
format | "docx" | "pdf" | no | Default docx. |
filename | string | no | Nombre 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, faltaslug/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.