Códigos de error

Toda respuesta de error usa la envoltura { error: { code, message, requestId, details? } }. La columna code es estable; usa eso para branching, no el mensaje.

Tabla maestra

HTTPcodeCuándo
400INVALID_INPUTJSON malformado, slug malformado, falta un campo requerido (zod), multipart sin file, etc.
401UNAUTHORIZEDFalta header x-api-key o el valor no coincide.
404TEMPLATE_NOT_FOUNDGET/PUT/DELETE/POST /render con slug que no existe.
404NOT_FOUNDRuta HTTP que no existe (catch-all).
409TEMPLATE_CONFLICTPOST /templates con un slug que ya está registrado. Usa PUT para reemplazar.
413PAYLOAD_TOO_LARGEUpload de plantilla mayor a MAX_UPLOAD_MB; o body JSON mayor a 1 MB.
422INVALID_DATAEl data de POST /render no cumple el JSON Schema de la plantilla. details.errors trae los errores ajv.
500RENDER_ERRORdocxtemplater falló: sintaxis de plantilla rota, etiqueta de imagen fuera de tabla 1×1, etc.
500CONVERSION_ERRORLibreOffice falló al convertir a PDF (timeout, archivo dañado, fuente faltante).
500INTERNAL_ERRORError no tipificado. Revisa logs con el requestId.
502IMAGE_FETCH_ERRORImagen URL no alcanzable, content-type no-imagen, tamaño excedido, o IP bloqueada por SSRF guard.
503LIBREOFFICE_UNAVAILABLEformat=pdf y LibreOffice no está instalado o no responde.

Diagnóstico

Cada respuesta incluye requestId. Para encontrar el log:

# Si los logs van a stdout (NODE_ENV=production):
docker compose logs converter-api | grep "<requestId>"

# En desarrollo (pino-pretty):
# El reqId aparece en cada línea de log; los errores 5xx se loggean con stack trace

Detalles ajv (422 INVALID_DATA)

El campo details.errors es el array crudo de ajv:

{
  "error": {
    "code": "INVALID_DATA",
    "details": {
      "slug": "factura-demo",
      "errors": [
        {
          "instancePath": "/items/0",
          "schemaPath": "#/properties/items/items/required",
          "keyword": "required",
          "params": { "missingProperty": "cantidad" },
          "message": "must have required property 'cantidad'"
        }
      ]
    }
  }
}

Para mostrar errores legibles al usuario final: lee error.details.errors[].message.

Detalles zod (400 INVALID_INPUT)

Para errores de validación del body o params del API:

{
  "error": {
    "code": "INVALID_INPUT",
    "details": {
      "issues": [
        { "path": ["slug"], "message": "Invalid slug format" }
      ]
    }
  }
}