Skip to main content
Cada entrada lista lo que cambia para un integrador y marca con Requiere accion lo que puede romper una integracion existente si no se revisa.

2026-10-28 (9)

  • Configurar SIAT v3 valida el ambiente. PUT /api/v1/admin/onboarding/companies/{id}/siat-version y la tool configurarVersionSiat con v3 responden 422 SIAT_V3_NO_DISPONIBLE al momento de configurar si el ambiente no habla v3 (antes la company quedaba en v3 y fallaba al emitir). verificationUrl ahora cae a la consulta oficial del SIN cuando la factura no guardó su QR. Sin Requiere accion.

2026-10-28 (8)

  • Nuevo POST /api/v1/invoices/preview y tool MCP previsualizarFactura: el mismo body que emitir, pero sin emitir (no numera, no guarda, no toca el SIN). Devuelve montos v2 o v3 según tu company (neto, IVA, monto a pagar, diferencia contra el precio final pedido), el XML que se mandaría y validaciones contra el XSD oficial del SIN y las fórmulas. Ver Previsualizar factura. Sin Requiere accion.

2026-10-28 (7)

  • SIAT v3 para todos los sectores en sandbox, sin cambiar tus precios. Si tu company pasa a v3, priceUnit sigue siendo el precio final que cobras (priceBasis=GROSS, default): cucu deriva el neto y el IVA con el redondeo del SIN y arma el XML como pide la norma. La respuesta (REST, MCP y webhooks) agrega siatVersion, priceBasis, amountNet, amountIva, amountToPay, amountRequested y amountDifference: cuando un precio final no se puede representar en v3 (ej. Bs 100,00 → 99,99) la diferencia viene explícita, nunca se cobra distinto en silencio. Quien prefiera mandar netos usa priceBasis=NET (por company o por request). Campos nuevos opcionales para v3: incotermScope (3, 20), contractNumber (20, 21), productDescription, locality, originDepartment, originMunicipality (21), beneficiaryComplement (13). El PDF/ticket muestra TOTAL PRECIO NETO e IVA (13 %) en v3. Producción no cambia (RND 102100000011 vigente).

2026-10-28 (6)

  • SIAT versión 3 (IVA transparente) en sandbox, por company. El SIN publicó el Anexo Técnico Ley 1733 (RND 102600000034) para pruebas en el piloto; en producción sigue vigente la RND 102100000011 (v2) y nada cambia. En sandbox, soporte puede pasar una company a v3 (tool admin configurarVersionSiat o PUT /api/v1/admin/onboarding/companies/{id}/siat-version), con lo que el XML lleva subTotalNeto, totalPrecioNeto, iva y montoaPagar. Cómo se interpreta priceUnit y qué sectores cubre: ver la entrada (7), que reemplaza a esta. Códigos nuevos en errores: SIAT_V3_NO_DISPONIBLE, SECTOR_NOT_SUPPORTED_V3. Sin Requiere accion hasta que el SIN fije la fecha de producción; lo anunciaremos aquí.

2026-10-28 (5)

  • Entrega por WhatsApp y Telegram, decidida por cada empresa. Apagada por defecto. Se activa por el canal de soporte (tool admin configurarEntregaChat o PUT /api/v1/admin/onboarding/tenants/{code}/entrega-chat), eligiendo canales (clientPhone → WhatsApp, clientTelegramUserId → Telegram) y remitente: el número/bot de cucu o el propio de la empresa. Mientras no se active, enviar clientPhone no genera ningún mensaje.
  • /f/{cuf} se elimina (404): cucu no tiene página de facturas. verificationUrl y publicPageUrl traen la consulta oficial del SIN (la URL del QR). Requiere accion si guardabas links /f/. Vigente en sandbox cuando el despliegue de dev termine; en producción, con la fecha de cierre de los links públicos.
  • GET /api/v1/public/invoice/{cuf} sin datos del comprador: ya no trae clientName ni clientDocument (el CUF es derivable y la ruta no pide credencial). Requiere accion si los leías de ahí: usa GET /api/v1/invoices/cuf/{cuf} con tu API Key. Sandbox ahora; producción con el próximo despliegue.
  • Correo al comprador: el botón “Verificar en Impuestos” lleva a la consulta del SIN (verificationUrl); si la factura no tiene QR, no hay botón.
  • Sandbox: los links públicos /api/v1/public/invoice/{cuf}/* responden 410 PUBLIC_DOCUMENTS_GONE. Prueba ahí la migración a documents.

2026-10-28 (4)

Documentos privados (fase 1)

Requiere accion: migrar de pdfUrl / links publicos a documents antes de la fecha que se anunciara. MCP descargarDocumento devuelve download_url.
  • Nuevos GET /api/v1/invoices/{id}/documents/{tipo} y GET /api/v1/invoices/cuf/{cuf}/documents/{tipo} (a4, ticket, ticketsiat, chititicket, xml, zip), con API Key. Ver Descargar documentos.
  • Nuevo GET|HEAD /api/v1/documents/t/{token}: link temporal (300 s, 3 usos por defecto) para que agentes y apps descarguen el archivo.
  • InvoiceResponse (REST y MCP) agrega documents y verificationUrl. pdfUrl, ticketUrl, xmlUrl y publicPageUrl quedan deprecados y se eliminaran al cierre de los links publicos. Los webhooks invoice.* agregan el bloque documents; pdf_url no cambia por ahora.
  • MCP descargarDocumento devuelve download_url, expires_at y max_uses en lugar de contentBase64 / sizeBytes, y suma los formatos ticketsiat, chititicket y zip.
  • Links publicos: sin cambios por ahora, salvo Cache-Control: public, no-cache + ETag tambien en el XML (antes un año) y 409 en facturas sin documento. Al cerrarse responderan 410 PUBLIC_DOCUMENTS_GONE y /f/{cuf} quedara solo para verificacion. Ver Acceso publico.
  • Codigos nuevos en errores: DOCUMENT_NOT_AVAILABLE, DOCUMENT_LINK_GONE, PUBLIC_DOCUMENTS_GONE.

2026-10-28 (3)

  • Se eliminaron POST /api/v1/ai/normativas/load y POST /api/v1/ai/normativas/reload. No estaban documentados y cualquier API Key podía dispararlos; la carga de normativa pasa a ser una operación de soporte (indexarNormativa, admin). GET /api/v1/ai/normativas/search y /status siguen igual.

2026-10-28

Segunda entrega del dia: endurecimiento de seguridad en MCP, webhooks, sincronizacion de catalogos y logo del PDF.

MCP: todas las herramientas exigen X-API-Key, utilidades incluidas

Requiere accion si algun cliente MCP llamaba a buscarNormativa, verificarFormatoNit, consultarDatosNit, erroresComunesSiat, buscarContingencias o calcularMontoLiteral sin llave: ahora se autentican como el resto de las 49 herramientas y, sin X-API-Key (o con una invalida), el resultado de la herramienta es Error: API Key requerido. Envie el header X-API-Key con su clave (sk_live_xxx o sk_test_xxx). Cada llamada cuenta ademas para el limite de requests por minuto de la llave, el mismo que en REST. buscarContingencias devuelve solo las contingencias del tenant autenticado. Ver Utilidades SIAT y Autenticacion MCP.

Webhooks: politica de destinos en cada entrega, sin credenciales en la URL, lastError generico

Requiere accion si la URL de algun endpoint lleva credenciales embebidas (https://usuario:clave@host/...): al registrar o actualizar se rechaza con 400 La URL del webhook no admite credenciales embebidas. Pasa la autenticacion de tu receptor por la firma webhook-signature o por un token en el path/query, y actualiza la URL con PUT /api/v1/webhooks/endpoints/{id}.
  • La URL se valida con una unica politica al registrar/actualizar y en cada entrega: se resuelve el host, se valida cada IP y la conexion va a la IP validada. Un host que pasa a resolver a una direccion interna deja de recibir entregas (lastError = "Destino del webhook no permitido").
  • Rangos bloqueados ampliados: ademas de loopback y privadas, CGNAT (100.64.0.0/10), link-local y metadata (169.254.0.0/16), 0.0.0.0/8, multicast, ULA (fc00::/7) e IPv4 embebida en IPv6 (::ffff:, NAT64, 6to4) y Teredo. Lista completa en Webhooks → Seguridad.
  • lastError de una entrega ya no trae el detalle de la excepcion: toda falla de red es No se pudo conectar con el endpoint o no respondió a tiempo; un host que no resuelve o que apunta a una IP bloqueada es Destino del webhook no permitido; una respuesta no 2xx es HTTP <codigo>. Si tu integracion parseaba lastError para distinguir timeout de conexion rechazada, ya no es posible.
  • Las entregas no siguen redirecciones (3xx = HTTP 3xx, fallo) y no leen el cuerpo de tu respuesta.

Sincronizacion de catalogos: POS de otro tenant responde 404

Requiere accion si tu integracion esperaba 403. En POST /api/v1/siat/sync/all y POST /api/v1/siat/sync/{catalog}, un posId de otro tenant responde 404 POS_NOT_FOUND (Punto de venta no encontrado), igual que uno inexistente. Antes era 403 FORBIDDEN. Ver Catalogos.

Logo del PDF: solo el subido por storage

El PDF embebe unicamente el logo guardado por POST /api/v1/storage/brand/logo (bytes de la empresa o el objeto bajo brands/{tenantId}/). Nunca se descarga desde una URL: si tu empresa tenia asociada una URL externa como logo, el PDF sale sin logo hasta que subas el archivo. Ver Storage.

2026-10-28

Emision mas rapida: la respuesta sale apenas el SIN acepta

POST /api/v1/invoices responde en cuanto el SIAT valida la factura (o queda en contingencia). El PDF (A4 y ticket), la subida a storage, el email al cliente y la notificacion por chat pasan a entregarse despues, por una cola propia con reintentos. Antes la respuesta esperaba a que todo eso terminara (varios segundos).
  • pdfUrl, ticketUrl y xmlUrl siguen siendo validas desde la respuesta. Si descargas el PDF antes de que la entrega lo haya guardado, se genera en el momento: la primera descarga tarda mas, nunca 404. Si otro proceso ya lo esta generando: 503 DOCUMENT_IN_PREPARATION + Retry-After.
  • El webhook invoice.emitted puede llegar antes que el email al cliente.
  • Los PDF publicos ya no se cachean un ano: Cache-Control: public, no-cache + ETag por version (la misma URL sirve la vigente y la anulada). El XML sigue inmutable.
  • La reversion de una anulacion reenvia la factura vigente por correo, con asunto [ANULACIÓN REVERTIDA] ....
Revisa: si tu integracion asumia que al recibir el 201 (o el webhook) el cliente ya tenia el correo, o que el PDF estaba listo al instante, ajustala. Ver Crear factura.

idempotencyKey en la factura; clave de una factura anulada → 422

  • La factura devuelve data.idempotencyKey (la Idempotency-Key con la que se emitio, null si no se envio) en la emision y en GET /invoices/{id}, /cuf/{cuf} y /by-idempotency-key/{key}.
  • Requiere accion. Reusar la Idempotency-Key de una factura anulada responde 422 con error.code = IDEMPOTENCY_KEY_CANCELLED: no se devuelve la factura anulada como replay ni se emite otra con esa clave. Para facturar de nuevo, usa una clave nueva.

429 por credenciales invalidas repetidas desde una IP

Requiere accion si tu integracion rota llaves o reintenta un 401 en bucle. Mas de 20 credenciales invalidas por minuto desde una misma IP (API Keys en rutas protegidas, credenciales admin, API Keys en /mcp) → esa IP recibe 429 con Retry-After presente la llave que presente, hasta la proxima ventana. El trafico con una llave valida no lo activa, y una request sin credencial no cuenta. Detalle en Autenticacion.

Reenvio de factura por correo: destino validado y cupo por hora

Requiere accion si reenvias correos de forma automatica. POST /api/v1/invoices/{id}/resend-email:
  • ?email= tiene que ser una sola direccion valida; si no, 400.
  • Cupo de 5 reenvios por factura por hora y 60 por tenant por hora. Superado cualquiera: 429 RATE_LIMITED + Retry-After.

Paginacion: tope de 100 por pagina

Requiere accion si pedias size mayor a 100. En GET /invoices y GET /invoices/search, size > 100 no es error: se sirven 100 y data.size informa el tamano efectivo. size <= 0 → 20; page < 0 → 0. En MCP, listarFacturas sirve como maximo 50 (antes la documentacion REST decia “max 50”: el tope REST es 100).

Los 500 devuelven una referencia, no el mensaje interno

error.code = INTERNAL_ERROR, message = "Error interno del servidor" y error.details = "ref=<uuid>". El detalle real queda solo en los logs de cucu bajo esa referencia: mandala a soporte. Los 4xx y los errores de dominio conservan su mensaje en message. Ademas, la pagina de Errores se corrigio para reflejar el sobre real: el objeto error tiene code y, solo en INTERNAL_ERROR, details; no trae field, siatCode ni siatDescription. El texto legible siempre va en message.

Recursos de otro tenant responden 404

Requiere accion si tu integracion esperaba 400 para “POS no encontrado”. En /api/v1/events/* y /api/v1/massive/*, un punto de venta, evento o lote inexistente o de otro tenant responde 404 NOT_FOUND con el mismo mensaje. Antes era 400. Lo mismo aplica a la empresa en la subida de logo.

Logo de empresa: PNG/JPEG/WebP verificados por contenido, 2 MiB

POST /api/v1/storage/brand/logo: el formato se detecta por los bytes del archivo (PNG, JPEG o WebP), no por el Content-Type; el body va como application/octet-stream. Limite 2 MiB. Errores nuevos: 400 LOGO_INVALIDO, 413 LOGO_DEMASIADO_GRANDE, 404 si la empresa no es tuya. La clave en storage es brands/{tenantId}/logo.{png|jpg|webp}.

Cabeceras de seguridad en las respuestas

Todas las respuestas traen X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer y Strict-Transport-Security. Las respuestas JSON traen ademas X-Frame-Options: DENY y una CSP restrictiva; los documentos publicos y /f/{cuf} quedan fuera de esas dos para poder embeberse. No requiere accion salvo que embebieras respuestas JSON de la API en un iframe.