1. Qué es, qué no es y dónde está
1.1 En una frase
- Qué es: una API REST (JSON sobre HTTPS) con la que tu software emite y consulta facturas de venta, notas crédito/débito y facturas del sector salud ante la DIAN, usando a KIAI S.A.S (NIT 901337751) como proveedor tecnológico, que genera el XML UBL, calcula el CUFE/CUDE, firma, transmite a la DIAN, produce el PDF y el AttachedDocument y los entrega al comprador.
- Qué no es: no calcula tus impuestos ni tus totales (tú los envías y la API los verifica al centavo), no emite tokens ni gestiona usuarios (eso lo hace el servicio de identidad de Kiai), no hace por ti los trámites ante la DIAN (habilitación, resoluciones de numeración) y no cubre nómina electrónica, RIPS ni el documento equivalente POS.
1.2 Ambientes y URLs
| Qué | URL | Autenticación |
|---|---|---|
| API v1 | https://documentos.kiai.co/api/v1 | Token Bearer |
| Endpoint de token | https://usuarios.kiai.co/api/oauth/token | client_id + client_secret |
| Descubrimiento del servicio de identidad | https://usuarios.kiai.co/.well-known/openid-configuration | Pública |
| Versión desplegada | https://documentos.kiai.co/api/buildinfo | Pública |
| Salud del proceso / de la base | https://documentos.kiai.co/health/live · /health/ready | Pública |
| Estado de Kiai y del canal DIAN | https://documentos.kiai.co/api/v1/estado-servicios | Pública |
- La API tiene un solo ambiente público, el de producción. El ambiente DIAN (habilitación o producción)
no depende de la URL: es una propiedad de tu emisor (sección 3). Cada documento dice en qué ambiente se
emitió (campo
ambiente). - No hay Swagger en producción. El contrato es el archivo
openapi.yaml(OpenAPI 3.0.3), que describe cada campo; su listaserversya incluyehttps://documentos.kiai.co/api/v1.
curl -s https://documentos.kiai.co/api/buildinfo
# {"version":"1.0.0+4478.644b40e","commitSha":"644b40ed0d2df63142e72ef7713bc22a6c07db50","buildId":"4478","buildTime":"2026-09-24T20:12:03Z","environment":"prod"}
1.3 Convenciones
Tipo, longitud y obligatoriedad de cada campo que envías y recibes: Anexo C — Diccionario de campos.
| Tema | Regla |
|---|---|
| Formato | JSON UTF-8, nombres en camelCase, enumeraciones como texto ("emitida", "credito"). En las respuestas los campos opcionales vienen presentes con null: la forma del JSON es estable. |
| Contrato estricto | Una propiedad que no existe en el esquema de entrada responde 400 con la ruta del campo como clave (p. ej. lineas[0].tarifaIVA). Nada se descarta en silencio. |
| Compatibilidad | Kiai puede agregar campos nuevos a las respuestas, y códigos o estados nuevos en los campos que los llevan, sin cambiar de versión: tu integración debe ignorar los campos que no conozca y tratar un código o un estado desconocido como tal, sin fallar. Quitar o renombrar un campo, o cambiarle el tipo, exige una versión nueva (v2). |
| Tu empresa | Nunca viaja en el body, en la ruta ni en un encabezado: sale del token. Enviar emisorId responde 400 explicándolo. |
| Identificadores | UUID. |
| Fechas | Fechas AAAA-MM-DD; fechas-hora ISO 8601 con zona (p. ej. 2026-09-01T07:30:00-05:00). Las respuestas devuelven fechas-hora en UTC (Z). |
| Montos | Máximo 2 decimales en montos, tarifas y porcentajes; máximo 6 en cantidades. |
| Listados | Subconjunto OData: $top (1 a 100, por defecto 20), $skip, $count=true, $filter, $orderby. Respuesta { "value": [...], "count": n } (count solo con $count=true). Un filtro no soportado responde 400. |
| Correlación | Envía X-Correlation-Id (si no lo envías, se genera). Se devuelve en la respuesta y aparece como correlationId en los errores: dáselo a soporte. |
| Idempotencia | Todo POST acepta Idempotency-Key (texto opaco, p. ej. un UUID; el contrato admite hasta 200 caracteres). Ver abajo. |
| Límites | Por emisor. Superarlo responde 429 con Retry-After. |
| Errores | Problem Details (RFC 9457), application/problem+json, con el campo fuente (cliente, dian o kiai). Ver sección 9. |
Idempotencia (Idempotency-Key). Úsala en todo POST que crea o emite:
- Misma llave y mismo cuerpo ⇒ la misma respuesta, sin volver a procesar (no duplica documentos ni quema consecutivos).
- Misma llave con otro cuerpo, o en otra ruta ⇒ 422. Usa una llave nueva por operación.
- Si la primera petición todavía se está procesando ⇒ 409 con
Retry-After: 2. - Solo se recuerda una respuesta exitosa (2xx), por emisor. Si la petición falló, la llave queda libre para reintentar con el dato corregido.
- Trata cada llave como de un solo uso. Kiai la conserva al menos 48 horas.
- No aplica al alta del emisor (todavía no existe el emisor al que asociarla).
Límites de uso. Por defecto: 600 solicitudes por minuto por emisor, y 60 emisiones por minuto por emisor
(POST .../emision). Otro emisor no se ve afectado por tu consumo.
Son los valores vigentes en producción.
1.4 El recorrido completo
- Creación. Tu empresa tiene cuenta en
usuarios.kiai.cocon el producto Facturación Electrónica activo, y un usuario administrador crea una credencial de servicio con los permisos de Facturación (sección 2.1). - Autenticación. Tu software pide un token con
resource=facturacion(sección 2.2) y lo usa en cada llamada (sección 2.3). - Tu empresa en la API. Das de alta tu emisor en habilitación (sección 3.3): con el
testSetIdde la DIAN si es la primera vez que facturas con Kiai, o sin él si ya estás habilitado con Kiai (sección 3.2). Los cambios posteriores los hace Kiai (sección 3.8). - Consultas tus numeraciones: la de pruebas
SETTy las de notasNC/ND(sección 4). - Documentos. Si te toca, emites el set de pruebas: creas cada documento en borrador, lo corriges si hace falta y lo emites (sección 5). Sigues el resultado por webhook o consultando el documento (secciones 6 y 8).
- Cuando la DIAN aprueba tu set (o de una vez, si ya estabas habilitado), Kiai te marca como habilitado.
- Solicitas en la DIAN tu numeración de producción, Kiai pasa tu emisor a producción (sección 3.6) y operas.
¿Eres aliado de Kiai? Si registras a tus clientes desde tu sistema, el recorrido empieza antes: los das de alta y los habilitas ante la DIAN por API. Ver la sección 11.
2. Autenticación
La API no tiene usuarios ni emite tokens. Acepta solo tokens JWT firmados (RS256) por el servicio de identidad de
Kiai (usuarios.kiai.co), dirigidos a Facturación. La integración usa credenciales de servicio
(client_credentials).
2.1 Obtén tu credencial (client_id / client_secret)
La credencial se crea por autoservicio, con la sesión de un usuario de tu empresa en usuarios.kiai.co,
sección INTEGRACIONES:
- Elige un nombre (p. ej. “ERP producción”) y los permisos (scopes):
facturacion.emitir— crear, emitir, transmitir y notificar documentos.facturacion.consultar— consultar documentos, PDF, XML y numeraciones. Los catálogos los lee cualquier token válido de Facturación.
- Recibes el
client_id(empieza porkiai_live_) y elclient_secret(empieza porksc_). Elclient_secretse muestra una sola vez: guárdalo en tu bóveda de secretos.
Reglas:
- La credencial pertenece a tu empresa: todo token que obtengas con ella opera a nombre de esa empresa.
- Máximo 10 credenciales activas por empresa.
- Rotar el secreto invalida el anterior de inmediato; revocar la credencial impide pedir tokens nuevos. En los dos casos, los tokens ya emitidos siguen valiendo hasta que expiran (máximo 60 minutos). Las dos acciones están en la misma sección INTEGRACIONES.
- Recomendado: una credencial por sistema, con el mínimo de permisos. Un tablero que solo consulta documentos no
necesita
facturacion.emitir. Ojo: la descarga en lote (POST /descargas) sí lo exige, porque es unPOST. - Si integras a nombre de varias empresas (aliado de Kiai), cada empresa cliente necesita su propia credencial: un token opera siempre a nombre de una sola empresa. Para registrar y habilitar a esas empresas por API, ver la sección 11.
En INTEGRACIONES los permisos aparecen como Facturación · emitir y Facturación · consultar. Necesitas un
usuario administrador de tu empresa en usuarios.kiai.co; si aún no lo tienes, pídeselo a Kiai. Kiai nunca ve tu
client_secret: lo generas y lo guardas tú.
2.2 Pide el token
POST https://usuarios.kiai.co/api/oauth/token, con los datos en el cuerpo (client_secret_post, el único
método soportado; no uses autenticación HTTP Basic):
curl -s -X POST https://usuarios.kiai.co/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=<TU_CLIENT_ID>" \
--data-urlencode "client_secret=<TU_CLIENT_SECRET>" \
--data-urlencode "resource=facturacion"
También acepta JSON:
curl -s -X POST https://usuarios.kiai.co/api/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"client_credentials","client_id":"<TU_CLIENT_ID>","client_secret":"<TU_CLIENT_SECRET>","resource":"facturacion"}'
Respuesta:
{
"access_token": "<TOKEN_JWT>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "facturacion.emitir facturacion.consultar"
}
Dos reglas que evitan la mayoría de los 401:
resource=facturaciones obligatorio (RFC 8707). Sin él, el token sale con una audiencia genérica y la API lo rechaza con 401 aunque la credencial sea correcta.- No envíes
scope. Los permisos se fijan al crear la credencial; el endpoint de token ignora ese parámetro y el token trae todos los permisos de la credencial (los ves en el camposcopede la respuesta).
Si decodificas el token (su contenido no es secreto; su firma sí), debe traer aud: "facturacion",
token_type: "m2m", tu companyId y uno o más scope.
2.3 Usa el token
API=https://documentos.kiai.co/api/v1
TOKEN=<TU_ACCESS_TOKEN>
# Prueba de humo: no exige emisor ni permisos especiales
curl -s -G "$API/catalogos/tipos-documento" --data-urlencode '$top=2' -H "Authorization: Bearer $TOKEN"
Antes del alta del emisor, cualquier llamada sobre tus documentos o numeraciones responde 403 «La empresa del token no tiene la facturación activa»: es lo esperado hasta la sección 3.3.
- Vida del token: 60 minutos (
expires_in: 3600). Guárdalo en memoria y reutilízalo; pide uno nuevo unos minutos antes de que expire o cuando recibas un 401 por expiración. No pidas un token por cada llamada. - La API tolera hasta 1 minuto de diferencia de reloj.
- Un token de sesión del portal (el de un usuario que inició sesión) no sirve aquí: responde 401 porque fue emitido para otro destinatario.
2.4 Qué permiso exige cada operación
| Operación | Permiso requerido |
|---|---|
Lecturas (GET) | facturacion.consultar |
Escrituras (POST, PUT, PATCH, DELETE) | facturacion.emitir |
Catálogos (/catalogos/...) | Cualquier token válido de Facturación |
estado-servicios, buildinfo, health | Sin token |
Un ERP que emite y consulta lleva los dos permisos. Si falta el que exige la operación, la API responde 403 nombrando cuál falta.
2.5 Errores de autenticación
Endpoint de token (usuarios.kiai.co), formato { "error": "...", "error_description": "..." }:
| HTTP | error | Causa |
|---|---|---|
| 400 | unsupported_grant_type | grant_type distinto de client_credentials. |
| 400 | invalid_request | Falta client_id o client_secret. |
| 400 | invalid_target | El resource no existe o la credencial no tiene ningún permiso de ese recurso (p. ej. una credencial solo de Firma pidiendo facturacion). |
| 401 | invalid_client | client_id o client_secret inválidos, o credencial revocada. |
API v1 — el detail del 401 dice el motivo real:
| HTTP | detail (resumen) | Qué hacer |
|---|---|---|
| 401 | “El token es válido, pero fue emitido para otro destinatario…” | Pide el token con resource=facturacion. |
| 401 | “El token expiró…” | Pide un token nuevo. |
| 401 | “No se pudo verificar la firma del token…” | El token no viene de usuarios.kiai.co o se alteró. |
| 401 | “Falta el token Bearer o es inválido/expirado.” | Revisa el encabezado Authorization: Bearer <token>. |
| 403 | “La credencial de servicio no tiene el scope ’…’” | Usa una credencial con ese permiso. |
| 403 | “El token de Gestión de Usuarios no identifica a una empresa (claim companyId).” | La credencial no está asociada a una empresa: contacta a Kiai. |
| 403 | “La empresa del token no tiene la facturación activa.” | Aún no das de alta tu emisor (sección 3.3), o Kiai lo desactivó. |
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.2",
"title": "No autenticado.",
"status": 401,
"detail": "El token es válido, pero fue emitido para otro destinatario. Esta API solo acepta tokens cuya audiencia (aud) sea la del producto Facturación. Un token de sesión del portal —que vale para todos los productos— no opera aquí: usa una credencial de servicio del producto Facturación.",
"instance": "/api/v1/facturas",
"traceId": "00-84b1...",
"correlationId": "5952f26eedc741b2a2ce29648af4a222",
"fuente": "cliente"
}
3. Alta del emisor y habilitación ante la DIAN
3.1 El emisor es tu empresa
En la API, el emisor es tu empresa como facturador: su id es el companyId de tu empresa y sale del
token. No existe un encabezado ni un campo para elegir emisor; una credencial solo puede operar el emisor de su
propia empresa.
Solo el id viene del servicio de identidad. Los datos fiscales (NIT, razón social, régimen, dirección…) son
los que envías en el alta y viven en esta API: cambiar tu empresa en usuarios.kiai.co no cambia tus documentos.
Para corregirlos, ver 3.8.
Hoy no aportas certificado de firma ni PIN: la API no los recibe y la firma de los documentos la resuelve Kiai como proveedor tecnológico.
3.2 Antes de empezar, en la DIAN
Si ya facturas electrónicamente con Kiai como proveedor tecnológico (por ejemplo, desde el portal de Kiai), ya estás habilitado ante la DIAN con el mismo software que usa esta API: no repites el set de pruebas.
- Da de alta tu emisor sin
testSetId(3.3). - Avísale a Kiai: te marca como habilitado (3.6) y acuerdan el paso a producción.
- Si quieres probar antes, emite documentos en habilitación: sin set de pruebas, la DIAN los valida en el momento. Si ya emitiste documentos de prueba con Kiai, fija el consecutivo siguiente por encima de los que usaste (4.2): la DIAN rechaza un número repetido («Regla: 90, Rechazo: Documento procesado anteriormente»).
Si es la primera vez que facturas con Kiai:
- Entra al portal de habilitación de la DIAN: empresas en
https://catalogo-vpfe-hab.dian.gov.co/User/CompanyLogin, personas naturales enhttps://catalogo-vpfe-hab.dian.gov.co/User/PersonLogin. Abre el enlace que llega al correo de tu RUT. - Ve a «Registro y habilitación» › «Documentos electrónicos» › «Factura electrónica» › «Configurar modos de operación». Elige el modo «Software de un proveedor tecnológico», selecciona KIAI S.A.S. (NIT 901337751) y pulsa «Asociar».
- En «Listado de modos de operación asociados», en la fila del software de Kiai, abre «Detalle de set de pruebas»
y copia el
TestSetId. Lo envías en el alta del emisor (3.3).
3.3 Da de alta tu emisor
POST /api/v1/emisores con el token de tu credencial. La empresa sale del token: no la envíes.
El testSetId va solo aquí. No lo pegues también en el paso de habilitación de usuarios.kiai.co: ese paso
corre por su cuenta un set de pruebas automático contra el mismo identificador, y los dos se estorbarían. Si ya
lo hiciste por el portal, estás en el caso de 3.2 «ya habilitado»: da de alta sin testSetId y avísale a Kiai.
curl -s -X POST "$API/emisores" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tipoDocumento": "31",
"numeroDocumento": "<TU_NIT_SIN_DV>",
"dv": "<TU_DV>",
"tipoOrganizacion": "1",
"razonSocial": "<RAZON_SOCIAL>",
"regimen": "48",
"responsabilidadesFiscales": "O-13",
"direccion": "<DIRECCION>",
"codigoMunicipio": "11001",
"municipio": "Bogotá D.C.",
"departamento": "Bogotá",
"codigoPostal": "110111",
"codigoPais": "CO",
"correo": "<CORREO_DE_FACTURACION_DE_TU_EMPRESA>",
"telefono": "<TELEFONO>",
"testSetId": "<TU_TEST_SET_ID>",
"ambiente": "habilitacion"
}'
| Campo | Obligatorio | Regla |
|---|---|---|
tipoDocumento | Sí | Código DIAN numérico (31 = NIT). |
numeroDocumento | Sí | Hasta 20 caracteres: dígitos, letras y guiones. Sin DV. |
dv | Si tipoDocumento = 31 | Un dígito; se valida contra el NIT con el algoritmo mod-11 de la DIAN. |
tipoOrganizacion | Sí | 1 persona jurídica, 2 persona natural. |
razonSocial | Sí | Hasta 450 caracteres. nombres y apellidos son opcionales. |
regimen | Sí | Código DIAN (48 responsable de IVA, 49 no responsable). |
responsabilidadesFiscales | No | Códigos separados por ; (p. ej. O-13;O-15). |
direccion | Sí | Hasta 300 caracteres. |
codigoMunicipio | Sí | Código DANE de exactamente 5 dígitos. municipio y departamento son obligatorios. |
codigoPais | No | ISO 3166-1 de 2 letras; por defecto CO. |
correo | Sí | Correo de tu empresa. Kiai lo usa para avisarte cuando una numeración está por vencer o agotarse. |
testSetId | Para el set de pruebas | El que te asignó la DIAN. Sin él, tus documentos no van al set de pruebas. |
ambiente | No | Solo habilitacion (por defecto). produccion responde 400: el paso a producción lo hace Kiai (3.6). |
activo | No | Por defecto true. No lo envíes: con false el emisor nace dado de baja y todo responde 403 hasta que Kiai lo reactive. |
No envíes emisorId, empresaId, softwareId, softwarePin, certificadoSerial ni certificadoPin: responden
400 (los cuatro últimos son material de Kiai como proveedor tecnológico).
Respuesta 201 (con encabezado Location):
{
"id": "<ID_EMISOR = companyId de tu empresa>",
"tipoDocumento": "31",
"numeroDocumento": "<TU_NIT_SIN_DV>",
"dv": "<TU_DV>",
"tipoOrganizacion": "1",
"razonSocial": "<RAZON_SOCIAL>",
"nombres": null,
"apellidos": null,
"regimen": "48",
"responsabilidadesFiscales": "O-13",
"direccion": "<DIRECCION>",
"codigoMunicipio": "11001",
"municipio": "Bogotá D.C.",
"departamento": "Bogotá",
"codigoPostal": "110111",
"codigoPais": "CO",
"correo": "<CORREO_DE_FACTURACION_DE_TU_EMPRESA>",
"telefono": "<TELEFONO>",
"testSetId": "<TU_TEST_SET_ID>",
"ambiente": "habilitacion",
"habilitadoDian": false,
"activo": true,
"fechaCreacion": "2026-09-23T15:04:05.123456Z",
"fechaActualizacion": "2026-09-23T15:04:05.123456Z"
}
Errores propios del alta: 409 si tu empresa ya tiene emisor o si ya existe un emisor con ese documento;
403 si el token no identifica una empresa o si la credencial no tiene facturacion.emitir; 400 si envías
ambiente: "produccion" o un campo que no te corresponde.
Revisa los datos antes de enviarlos. Después del alta, editar el emisor (incluido el
testSetIdy el ambiente) es una operación que solo hace Kiai. Los datos del emisor van en cada XML que firmas.
Para consultar tu emisor: GET /api/v1/emisores/{id} (solo tu propio id; cualquier otro responde 404).
3.4 Corre el set de pruebas
- Consulta tus numeraciones (
GET /api/v1/numeraciones-autorizadas, sección 4). En habilitación recibes:SETT(tipofactura, rango 1 a 5.000.000, resolución 18760000001),NC(nota crédito) yND(nota débito), desde 1, sin vencimiento.
- Crea y emite los documentos que exige tu set de pruebas usando el
idde cada numeración comoresolucionId(sección 5). - Con
testSetIdy todavía sin habilitar, cada documento se envía al set de pruebas de la DIAN, que lo procesa de forma asíncrona: el documento quedatransmisionEstado: "pendiente", con el mensaje “Enviada al set de pruebas de habilitación DIAN; procesamiento asíncrono (consultar por ZipKey).” y el ZipKey entransmisionTrackId. - Kiai vuelve a consultar a la DIAN periódicamente (cada ~5 minutos por defecto) hasta obtener el veredicto; el
documento pasa entonces a
aceptadaorechazaday, si tienes webhook, te llega el evento.
Tenlo en cuenta:
- Una nota con referencia exige que la factura esté
aceptada. En el set de pruebas eso tarda unos minutos: espera el veredicto de la factura antes de crear sus notas (si no, la API responde 409). - Cuántos documentos de cada tipo exige tu set lo define la DIAN: lo ves en su portal, en «Detalle de set de pruebas».
- En habilitación también se envía automáticamente al adquiriente cada documento que la DIAN acepta: usa buzones de prueba tuyos, nunca el correo de un cliente real.
- Cuando la DIAN aprueba tu set, deja de procesar documentos de prueba. Si sigues enviando, esos documentos
quedan
rechazadacon el mensaje «La DIAN no procesó el documento porque el set de pruebas del emisor ya fue aprobado…». Es la señal de avisarle a Kiai (3.6); después de que te marque habilitado, retransmítelos conPOST /facturas/{id}/transmision(sección 6) y la DIAN los valida en el momento.
3.5 Sigue el avance
GET /api/v1/emisores/{id}/habilitacion (permiso facturacion.consultar):
{
"emisorId": "<ID_EMISOR>",
"ambiente": "habilitacion",
"habilitadoDian": false,
"testSetId": "<TU_TEST_SET_ID>",
"documentos": [
{ "tipo": "factura", "total": 10, "aceptadas": 8, "rechazadas": 1, "pendientes": 1 },
{ "tipo": "nota-credito", "total": 1, "aceptadas": 1, "rechazadas": 0, "pendientes": 0 },
{ "tipo": "nota-debito", "total": 1, "aceptadas": 0, "rechazadas": 0, "pendientes": 1 },
{ "tipo": "documento-soporte", "total": 0, "aceptadas": 0, "rechazadas": 0, "pendientes": 0 }
]
}
Los conteos cuentan documentos emitidos (con número) según su estado de transmisión.
3.6 Habilitación y paso a producción
- Cuando la DIAN aprueba tu set de pruebas, avísale a Kiai. Kiai marca tu emisor como habilitado: en
GET /emisores/{id}/habilitacionverás"habilitadoDian": true. Desde ese momento tus transmisiones ya no van al set de pruebas: la DIAN responde en el momento (aceptada o rechazada). Kiai reporta además el resultado a tu cuenta enusuarios.kiai.co. Avísale a Kiai por correo (hola@kiai.co) o por WhatsApp. - Solicita en la DIAN tu numeración de facturación de producción y asóciala al software de Kiai. La API la
toma directamente de la DIAN (no hay que registrarla en Kiai) y la verás con
origen: "dian". Kiai te acompaña en ese paso. - Kiai cambia tu emisor a
ambiente: "produccion"(editar el emisor es una operación de plataforma). Verifícalo conGET /emisores/{id}. - En producción,
GET /numeraciones-autorizadasdevuelve tu rango de la DIAN y unasNC/NDde producción, que empiezan de nuevo en 1. Las numeraciones de habilitación quedan en ese ambiente: emitir con ellas en producción responde 409.
3.7 Quién puede llamar cada operación del emisor
| Operación | Endpoint | Quién |
|---|---|---|
| Dar de alta tu emisor | POST /api/v1/emisores | Tu empresa (el emisor sale del token). |
| Ver tu emisor | GET /api/v1/emisores/{id} | Tu empresa, solo con tu propio id. |
| Ver el avance de habilitación | GET /api/v1/emisores/{id}/habilitacion | Tu empresa. |
| Editar, cambiar ambiente o dar de baja | PUT /api/v1/emisores/{id} | Solo Kiai. |
| Eliminar | DELETE /api/v1/emisores/{id} | Solo Kiai. |
| Marcar como habilitado | POST /api/v1/emisores/{id}/habilitacion | Solo Kiai. |
Si tu credencial intenta un PUT o un DELETE sobre el emisor, recibe 403 «El token es válido pero no tiene
permisos para este recurso.».
3.8 Cambiar los datos de tu empresa
Tu software no puede modificar el emisor: no hay PUT ni PATCH para ti. Revisa bien los datos del alta,
porque van dentro de cada XML que se firma.
Para cambiar algo (dirección, correo, teléfono, responsabilidades fiscales, razón social):
- Pídeselo a Kiai (
hola@kiai.coo WhatsApp) con todos los datos de tu empresa, no solo el que cambia. El cambio reemplaza el registro completo: un dato opcional que no envíes queda vacío. - Kiai lo aplica y lo verificas con
GET /api/v1/emisores/{id}. - Los documentos ya emitidos no cambian: cada uno conserva los datos con que se firmó. El cambio aplica a los documentos que emitas después.
La baja también la hace Kiai: desde ese momento toda llamada de tu credencial responde 403. Kiai no puede eliminar un emisor que ya tiene documentos.
4. Numeraciones autorizadas
La DIAN es la fuente de verdad. No hay alta ni importación de resoluciones: GET /api/v1/numeraciones-autorizadas
pregunta a la DIAN, en el ambiente de tu emisor, qué rangos tienes autorizados con el software de Kiai y los deja
listos para emitir. Tú solo puedes mover el consecutivo a seguir (y poner un logo).
- Rango con clave técnica ⇒
factura; sin clave técnica ⇒documento-soporte. - En habilitación, si la DIAN no reporta el set de pruebas, se agrega
SETT(1 a 5.000.000). - Siempre se agregan las numeraciones de notas
NCyND, desde 1, sin resolución y sin vencimiento. - El
idde cada numeración es estable: es elresolucionIdque envías al crear factura o nota. - La clave técnica nunca se expone.
- Si la DIAN deja de reportar una numeración, no se borra: queda
no-autorizaday emitir con ella responde 409.
curl -s "$API/numeraciones-autorizadas" -H "Authorization: Bearer $TOKEN"
Respuesta en habilitación (los id son ilustrativos):
{
"ambiente": "habilitacion",
"avisoDian": "La DIAN no reportó rangos de numeración para el emisor (301: No fue encontrado ningún rango de numeración para el NIT).",
"value": [
{
"id": "7c2e1a90-3b5d-8f42-9a61-4d0b2c8e7f13",
"tipoDocumento": "factura",
"origen": "set-pruebas",
"ambiente": "habilitacion",
"prefijo": "SETT",
"numeroResolucion": "18760000001",
"rangoDesde": 1,
"rangoHasta": 5000000,
"vigenciaDesde": "2019-01-19",
"vigenciaHasta": "2030-01-19",
"consecutivoSiguiente": 1,
"ultimoConsecutivoEmitido": null,
"disponibles": 5000000,
"estado": "vigente"
},
{
"id": "0e9d4c21-58a7-8b3c-b2f0-6a1e9d3c5b77",
"tipoDocumento": "nota-credito",
"origen": "por-defecto",
"ambiente": "habilitacion",
"prefijo": "NC",
"numeroResolucion": null,
"rangoDesde": 1,
"rangoHasta": 999999999999999999,
"vigenciaDesde": null,
"vigenciaHasta": null,
"consecutivoSiguiente": 1,
"ultimoConsecutivoEmitido": null,
"disponibles": 999999999999999999,
"estado": "vigente"
},
{
"id": "5b8f2d64-9c13-8e07-a4d9-3f6b1a0c2e58",
"tipoDocumento": "nota-debito",
"origen": "por-defecto",
"ambiente": "habilitacion",
"prefijo": "ND",
"numeroResolucion": null,
"rangoDesde": 1,
"rangoHasta": 999999999999999999,
"vigenciaDesde": null,
"vigenciaHasta": null,
"consecutivoSiguiente": 1,
"ultimoConsecutivoEmitido": null,
"disponibles": 999999999999999999,
"estado": "vigente"
}
]
}
avisoDian explica situaciones que no bloquean: la DIAN no reportó rangos (301/302), o no respondió y se
muestran las numeraciones ya registradas. Otras respuestas:
- 409
fuente: dian— la DIAN rechazó la consulta;reglasDiantrae el código (p. ej.303: el software no corresponde al NIT, es decir, tu numeración no está asociada al software de Kiai en la DIAN). - 409
fuente: cliente— problema con el certificado de firma: contacta a Kiai. - 503 — la DIAN no respondió y aún no tienes numeraciones registradas: reintenta según
Retry-After.
4.1 Estados de una numeración
estado | Significado (respecto a la fecha de Colombia) |
|---|---|
vigente | Se puede emitir. |
por-vencer | 30 días o menos para el fin de la vigencia. |
por-iniciar | La vigencia aún no empieza. |
vencida | Vigencia terminada. |
incompleta | La DIAN no informó rango o vigencia válidos: se lista al final, no se registra ni se emite. |
no-autorizada | La DIAN dejó de reportarla: se conserva por sus documentos; emitir con ella responde 409. |
4.2 Fija el consecutivo a seguir
Útil si migras de otro proveedor y ya usaste números del rango. Es el único dato editable:
curl -s -X PUT "$API/numeraciones-autorizadas/<ID_NUMERACION>/consecutivo-siguiente" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "consecutivoSiguiente": 1201 }'
- Debe estar dentro del rango (bordes incluidos); si no, 400.
- Debe ser mayor que el último número ya emitido con esa numeración; si no, 409 (reusar un número duplicaría el documento ante la DIAN).
- Cualquier otra propiedad en el cuerpo responde 400.
5. Emitir documentos
5.1 El ciclo: borrador y emisión
Emitir son dos llamadas:
POST /facturas(o/notas) crea el documento en borrador (201). La API verifica la estructura y el cuadre de totales; mientras sea borrador puedes corregirlo (PUT) o eliminarlo (DELETE).POST /facturas/{id}/emision(o/notas/{id}/emision) emite: toma el siguiente consecutivo de la numeración, asigna número y CUFE/CUDE y deja el documentoemitidacontransmisionEstado: "pendiente". La transmisión a la DIAN ocurre justo después, en segundo plano (sección 6).
Después de emitir, el documento no se edita ni se elimina. El comprador (adquiriente) viaja dentro del documento: no hace falta registrarlo antes. La API lo guarda o actualiza en tu directorio de adquirientes y el documento conserva una copia fija de esos datos, que es la que usan el XML, el CUFE, el PDF y el correo.
Envía Idempotency-Key en las dos llamadas: un reintento por un corte de red no duplica el borrador ni gasta un
consecutivo. Genera la llave una vez por operación y reutilízala si reintentas; los ejemplos usan uuidgen en
línea solo por brevedad.
5.1.1 Modificar o eliminar un borrador
| Operación | Llamada | Comportamiento |
|---|---|---|
| Corregir | PUT /facturas/{id} (o /notas/{id}) | Reemplaza el borrador completo, con el mismo cuerpo que el POST; vuelve a verificar los totales. Responde 200. |
| Eliminar | DELETE /facturas/{id} (o /notas/{id}) | Responde 204. |
| Cualquiera de los dos sobre un documento emitido | — | 409: un documento emitido no se edita ni se elimina. |
PUTyDELETEno usanIdempotency-Key(solo aplica a losPOST).- La fecha del documento: si no la envías, queda la del día en que creas el borrador, y la emisión no la
cambia. Si creas el borrador un día y lo emites otro, envía
fechaexplícita.
5.2 Totales: tú los envías, la API los verifica al centavo
Envías por línea los impuestos, retenciones y cargos/descuentos, y los totales del documento. La API recalcula todo con las reglas del Anexo Técnico FE 1.9 y responde 400 si algún valor no cuadra, con la ruta del campo y el valor recibido frente al esperado. El error aparece en el campo que origina el descuadre, no en sus totales.
| Concepto | Regla |
|---|---|
| Redondeo | Mitad al par (“bancario”) a 2 decimales. Montos, tarifas y porcentajes: máximo 2 decimales. Cantidad: máximo 6. |
valorTotal de la línea | round(cantidad × precioUnitario) − descuentos de línea + cargos de línea. En muestra gratis, 0. |
Cargo/descuento con porcentaje | valor = round(base × porcentaje / 100). En la línea, base = round(cantidad × precioUnitario). Un descuento no puede superar su base. |
Impuesto porcentual (IVA 01, INC 04, ICA 03…) | valor = round(base × tarifa / 100); ICA por mil (/ 1000). base = valorTotal de la línea (en muestra gratis, round(cantidad × precioReferencia)). IVA admite 0, 5, 16 y 19; INC admite 2, 4, 8 y 16. |
Impuesto nominal (bolsas 22, IBUA 34…) | valor = valorUnitario × base, con base = cantidad de la línea y unidadMedidaBase. IBUA / 100. |
Retenciones (05 ReteIVA, 06 ReteRenta, 07 ReteICA) | valor = round(base × tarifa / 100); ReteICA por mil. ReteIVA se practica sobre el IVA de la línea (15 o 100). Son informativas: no afectan totalAPagar. |
valorBruto | Σ valorTotal de las líneas. |
baseGravable | Σ, por línea, la base de su primer impuesto porcentual. |
totalImpuestos | Σ impuestos de las líneas. |
valorConImpuestos | valorBruto + totalImpuestos. |
totalDescuentos / totalCargos | Σ cargos/descuentos de cabecera (no afectan la base gravable). |
totalAnticipos | Σ anticipos (en salud, Σ recaudos). Informativos, salvo en salud de acreditación. |
totalRetenciones | Σ retenciones de líneas y documento. |
totalAPagar | valorConImpuestos − totalDescuentos + totalCargos (en salud de acreditación, además − recaudos). |
En una línea no se repite un tributo, y una línea sin impuestos es un ítem excluido. Los códigos válidos de cada
campo son los de las tablas del Anexo Técnico de Factura Electrónica de la DIAN, versión 1.9 (Resolución 000165):
por ejemplo, 13.2.1 para tipos de documento, 13.3.6 para unidades de medida, 13.3.4.2 para medios de pago y 13.4.3
para municipios. GET /api/v1/catalogos/{tipo} trae hoy los valores más usados, no la lista completa.
5.3 Factura de venta
Ejemplo del contrato: IVA 19 %, INC 8 %, impuesto a las bolsas, ReteFuente, ReteICA, descuento de línea y
descuento de cabecera. Ajusta resolucionId y las fechas a tu caso (las del ejemplo son ilustrativas).
curl -s -X POST "$API/facturas" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @factura.json
factura.json:
{
"adquiriente": {
"tipoDocumento": "31",
"numeroDocumento": "800197268",
"dv": "4",
"tipoOrganizacion": "1",
"razonSocial": "Cliente SAS",
"regimen": "48",
"responsabilidadesFiscales": "O-13",
"direccion": "Calle 9 # 8-7",
"codigoMunicipio": "11001",
"municipio": "Bogota",
"departamento": "Bogota D.C.",
"correo": "<CORREO_DE_PRUEBAS_TUYO>"
},
"resolucionId": "<ID_NUMERACION_FACTURA>",
"fecha": "2026-09-13",
"moneda": "COP",
"formaPago": "credito",
"fechaVencimiento": "2026-10-13",
"mediosPago": [ { "codigo": "47", "referencia": "TRF-001" } ],
"periodo": { "fechaInicio": "2026-09-01T00:00:00-05:00", "fechaFin": "2026-09-30T23:59:59-05:00" },
"ordenReferencia": { "numero": "OC-2026-15", "fecha": "2026-09-05" },
"lineas": [
{
"descripcion": "Servicio de consultoría",
"codigo": "43231500",
"esquemaCodigo": "001",
"unidadMedida": "94",
"cantidad": 3,
"precioUnitario": 10000.00,
"cargosDescuentos": [
{ "esCargo": false, "codigoRazon": "00", "descripcion": "Pronto pago", "porcentaje": 10, "base": 30000.00, "valor": 3000.00 }
],
"impuestos": [
{ "tributo": "01", "base": 27000.00, "tarifa": 19.00, "valor": 5130.00 }
],
"retenciones": [
{ "tributo": "06", "base": 27000.00, "tarifa": 2.50, "valor": 675.00 },
{ "tributo": "07", "base": 27000.00, "tarifa": 9.66, "valor": 260.82 }
],
"valorTotal": 27000.00
},
{
"descripcion": "Almuerzo ejecutivo",
"unidadMedida": "94",
"cantidad": 2,
"precioUnitario": 15000.50,
"impuestos": [
{ "tributo": "04", "base": 30001.00, "tarifa": 8.00, "valor": 2400.08 }
],
"valorTotal": 30001.00
},
{
"descripcion": "Producto en bolsa",
"unidadMedida": "94",
"cantidad": 4,
"precioUnitario": 100.00,
"impuestos": [
{ "tributo": "01", "base": 400.00, "tarifa": 19.00, "valor": 76.00 },
{ "tributo": "22", "base": 4, "valorUnitario": 66.00, "unidadMedidaBase": "94", "valor": 264.00 }
],
"valorTotal": 400.00
}
],
"cargosDescuentos": [
{ "esCargo": false, "codigoRazon": "01", "descripcion": "Descuento comercial", "base": 65271.08, "valor": 1000.00 }
],
"totales": {
"valorBruto": 57401.00,
"baseGravable": 57401.00,
"totalImpuestos": 7870.08,
"valorConImpuestos": 65271.08,
"totalDescuentos": 1000.00,
"totalCargos": 0.00,
"totalAnticipos": 0.00,
"totalRetenciones": 935.82,
"totalAPagar": 64271.08
}
}
Campos de la factura que conviene conocer:
formaPago:contadoocredito; concredito,fechaVencimientoes obligatoria y no puede ser anterior a la fecha.mediosPagolleva al menos uno (10efectivo,42consignación,47transferencia,48/49tarjeta…).fechaes opcional: por defecto, la fecha de hoy en Colombia.adquirientepuede ser{ "consumidorFinal": true }: la API completa los datos DIAN del consumidor final (13-222222222222); si envíascorreo, debe ser válido.- Otros casos que admite el mismo recurso: exportación (
monedadistinta de COP exigetrm), mandato (mandante), anticipos (anticipos, informativos), contingencia (contingencia, tipos03/04) y muestras gratis (esMuestraGratisconprecioReferenciaycodigoPrecioReferencia). El detalle está en el esquemaFacturaInputdel contrato.
Respuesta 201 (abreviada): lo guardado, idéntico a lo que irá al XML.
{
"id": "<ID_FACTURA>",
"emisorId": "<ID_EMISOR>",
"adquirienteId": "<ID_ADQUIRIENTE>",
"adquiriente": { /* copia del comprador, con "consumidorFinal": false */ },
"resolucionId": "<ID_NUMERACION_FACTURA>",
"tipoDocumento": "factura-venta",
"ambiente": null,
"estado": "borrador",
"numero": null,
"cufe": null,
"fecha": "2026-09-13",
"fechaEmision": null,
"moneda": "COP",
/* … trm, fechaTasa, mandante, anticipos, contingencia, periodo, sectorSalud, formaPago, mediosPago,
fechaVencimiento, ordenReferencia, observaciones, cargosDescuentos, retenciones, totales … */
"transmisionEstado": null,
"transmisionMensaje": null,
"transmisionFecha": null,
"transmisionTrackId": null,
"lineas": [ /* con "numero" y "esquemaCodigo" resueltos */ ],
"fechaCreacion": "2026-09-23T15:04:05.123456Z",
"fechaActualizacion": "2026-09-23T15:04:05.123456Z"
}
El dueño del documento viaja como emisorId (el id de tu emisor), igual en la API y en el openapi.yaml.
Emite:
curl -s -X POST "$API/facturas/<ID_FACTURA>/emision" \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
Respuesta 200 (abreviada):
{
"id": "<ID_FACTURA>",
"tipoDocumento": "factura-venta",
"ambiente": "habilitacion",
"estado": "emitida",
"numero": "SETT1",
"cufe": "<CUFE: 96 caracteres hexadecimales (SHA-384)>",
"fechaEmision": "2026-09-23T15:04:06.654321Z",
/* … */
"transmisionEstado": "pendiente",
"transmisionMensaje": null,
"transmisionFecha": null,
"transmisionTrackId": null
}
La emisión responde 409 si el documento ya no está en borrador, no tiene líneas, o la numeración no es de
tipo factura, es de otro ambiente DIAN que el de tu emisor, no está vigente, está agotada o ya no está
autorizada. En ese caso no se consume el consecutivo. La emisión responde aunque la DIAN esté caída: el
documento queda pendiente y se transmite cuando vuelva.
5.4 Notas crédito y débito
Mismo contrato de líneas, impuestos y totales que la factura. facturaId decide la forma de la nota y el
tipo de operación DIAN (tipoOperacion) se deriva solo:
Con referencia (envías facturaId) | Sin referencia (omites facturaId) | |
|---|---|---|
tipoOperacion | 20 crédito · 30 débito | 22 crédito · 32 débito |
| Factura | Debe estar aceptada por la DIAN, ser tuya, en la misma moneda y con fecha ≤ la de la nota. | — |
adquiriente | No se envía (400): la nota copia el de la factura. | Obligatorio. |
conceptoCorreccion | Obligatorio. | Prohibido. |
periodo | Opcional. | Obligatorio. |
resolucionId | El id de la numeración NC o ND. | Igual. |
Conceptos de corrección: crédito 1 devolución parcial, 2 anulación, 3 rebaja o descuento, 4 ajuste de
precio, 5 descuento por pronto pago, 6 descuento por volumen; débito 1 intereses, 2 gastos por cobrar,
3 cambio del valor, 4 otros.
Reglas contra la factura (se revalidan al emitir):
- La nota crédito no puede superar el saldo (
GET /facturas/{id}/saldo: total de la factura − notas crédito emitidas no rechazadas). Así haces correcciones parciales o totales. - La anulación (concepto
2) debe ser exactamente por el saldo total. - La nota débito no tiene tope.
- Si la factura es del sector salud, la nota hereda su extensión de salud y su periodo.
- Las notas no llevan anticipos. Las retenciones se guardan y se devuelven, pero no van al XML de la nota.
Nota crédito con referencia (ejemplo del contrato; cabe dentro del saldo de la factura del ejemplo 5.3):
{
"tipo": "credito",
"facturaId": "<ID_FACTURA_ACEPTADA>",
"resolucionId": "<ID_NUMERACION_NC>",
"conceptoCorreccion": "1",
"moneda": "COP",
"lineas": [
{
"descripcion": "Servicio de consultoría",
"codigo": "43231500",
"esquemaCodigo": "001",
"unidadMedida": "94",
"cantidad": 3,
"precioUnitario": 10000,
"cargosDescuentos": [ { "esCargo": false, "codigoRazon": "00", "porcentaje": 10, "base": 30000, "valor": 3000 } ],
"impuestos": [ { "tributo": "01", "base": 27000, "tarifa": 19, "valor": 5130 } ],
"retenciones": [ { "tributo": "06", "base": 27000, "tarifa": 2.5, "valor": 675 } ],
"valorTotal": 27000
},
{
"descripcion": "Almuerzo ejecutivo",
"unidadMedida": "94",
"cantidad": 2,
"precioUnitario": 15000.50,
"impuestos": [ { "tributo": "04", "base": 30001, "tarifa": 8, "valor": 2400.08 } ],
"valorTotal": 30001
}
],
"cargosDescuentos": [ { "esCargo": false, "codigoRazon": "01", "descripcion": "Descuento comercial", "base": 64531.08, "valor": 1000 } ],
"totales": {
"valorBruto": 57001,
"baseGravable": 57001,
"totalImpuestos": 7530.08,
"valorConImpuestos": 64531.08,
"totalDescuentos": 1000,
"totalCargos": 0,
"totalAnticipos": 0,
"totalRetenciones": 675,
"totalAPagar": 63531.08
}
}
Nota débito con referencia (intereses):
{
"tipo": "debito",
"facturaId": "<ID_FACTURA_ACEPTADA>",
"resolucionId": "<ID_NUMERACION_ND>",
"conceptoCorreccion": "1",
"lineas": [
{
"descripcion": "Intereses de financiacion por pago a credito",
"codigo": "INT-FIN-01",
"unidadMedida": "94",
"cantidad": 1,
"precioUnitario": 15000,
"impuestos": [ { "tributo": "01", "base": 15000, "tarifa": 19, "valor": 2850 } ],
"valorTotal": 15000
}
],
"totales": {
"valorBruto": 15000,
"baseGravable": 15000,
"totalImpuestos": 2850,
"valorConImpuestos": 17850,
"totalDescuentos": 0,
"totalCargos": 0,
"totalAnticipos": 0,
"totalRetenciones": 0,
"totalAPagar": 17850
}
}
Nota crédito sin referencia (ajuste de un periodo):
{
"tipo": "credito",
"adquiriente": {
"tipoDocumento": "31",
"numeroDocumento": "800197268",
"dv": "4",
"tipoOrganizacion": "1",
"razonSocial": "Cliente SAS",
"regimen": "48",
"responsabilidadesFiscales": "O-13",
"direccion": "Calle 9 # 8-7",
"codigoMunicipio": "11001",
"municipio": "Bogota",
"departamento": "Bogota D.C.",
"correo": "<CORREO_DE_PRUEBAS_TUYO>"
},
"resolucionId": "<ID_NUMERACION_NC>",
"periodo": { "fechaInicio": "2026-08-01T00:00:00-05:00", "fechaFin": "2026-08-31T23:59:59-05:00" },
"lineas": [
{
"descripcion": "Ajuste comercial sin referencia a factura",
"codigo": "AJU-SREF-01",
"unidadMedida": "94",
"cantidad": 1,
"precioUnitario": 20000,
"impuestos": [ { "tributo": "01", "base": 20000, "tarifa": 19, "valor": 3800 } ],
"valorTotal": 20000
}
],
"totales": {
"valorBruto": 20000,
"baseGravable": 20000,
"totalImpuestos": 3800,
"valorConImpuestos": 23800,
"totalDescuentos": 0,
"totalCargos": 0,
"totalAnticipos": 0,
"totalRetenciones": 0,
"totalAPagar": 23800
}
}
Crea con POST /api/v1/notas y emite con POST /api/v1/notas/{id}/emision, igual que la factura. La respuesta
trae tipoDocumento (nota-credito / nota-debito), numero (p. ej. NC1), cude y los campos de
transmisión.
5.5 Factura del sector salud (Resolución 948 de 2026)
Es una factura normal con el bloque sectorSalud, que la marca como factura de salud. Exige periodo (fecha y
hora del primer y último servicio) y moneda COP, y no admite anticipos ni mandante (los anticipos se
informan como recaudo con concepto 04). Los datos del paciente no se envían aquí: van en el RIPS, que no
hace parte de esta API.
tipoOperacion | Extensión de salud | Recaudos | ¿Restan de totalAPagar? |
|---|---|---|---|
SS-CUFE, SS-CUDE, SS-POS, SS-SNum (acreditación) | Sí | Obligatorios (1 a 4) | Sí |
SS-Reporte | Sí | Obligatorios (1 a 4) | No (informativos) |
SS-SinAporte | Sí | No lleva | — |
SS-Recaudo | No | No lleva | — |
Campos del bloque (cuando lleva extensión): codigoPrestador (código REPS de 10 dígitos), modalidadPago
(01–05), coberturaPlan (02–17), y numeroContrato o facturaSinContrato (01–07); con póliza,
numeroPoliza (excluyente con el contrato). Recaudos: un concepto por grupo, sin repetir (01 copago, 02
cuota moderadora, 03 pagos compartidos, 04 anticipo, 05 no aplica con valor 0, que va solo), cada uno con
valor (máximo 2 decimales) y fechaRecibido. La suma de recaudos no puede superar el total del documento.
En acreditación, totalAnticipos = Σ recaudos y totalAPagar = valorConImpuestos − totalDescuentos +
totalCargos − Σ recaudos.
Ejemplo SS-CUFE: consulta con IVA 0 %, material con IVA 19 %, copago y cuota moderadora:
{
"adquiriente": {
"tipoDocumento": "31",
"numeroDocumento": "800197268",
"dv": "4",
"tipoOrganizacion": "1",
"razonSocial": "Cliente SAS",
"regimen": "48",
"responsabilidadesFiscales": "O-13",
"direccion": "Calle 9 # 8-7",
"codigoMunicipio": "11001",
"municipio": "Bogota",
"departamento": "Bogota D.C.",
"correo": "<CORREO_DE_PRUEBAS_TUYO>"
},
"resolucionId": "<ID_NUMERACION_FACTURA>",
"moneda": "COP",
"formaPago": "contado",
"mediosPago": [ { "codigo": "10" } ],
"periodo": { "fechaInicio": "2026-09-20T07:30:00-05:00", "fechaFin": "2026-09-22T18:00:00-05:00" },
"sectorSalud": {
"tipoOperacion": "SS-CUFE",
"codigoPrestador": "0500102345",
"modalidadPago": "04",
"coberturaPlan": "16",
"facturaSinContrato": "01",
"recaudos": [
{ "concepto": "01", "valor": 12000, "fechaRecibido": "2026-09-20" },
{ "concepto": "02", "valor": 4500, "fechaRecibido": "2026-09-20" }
]
},
"lineas": [
{
"descripcion": "Consulta de medicina especializada",
"codigo": "CONS-ESP-01",
"unidadMedida": "94",
"cantidad": 1,
"precioUnitario": 100000,
"impuestos": [ { "tributo": "01", "base": 100000, "tarifa": 0, "valor": 0 } ],
"valorTotal": 100000
},
{
"descripcion": "Material medico quirurgico",
"codigo": "MAT-MQ-01",
"unidadMedida": "94",
"cantidad": 2,
"precioUnitario": 10000,
"impuestos": [ { "tributo": "01", "base": 20000, "tarifa": 19, "valor": 3800 } ],
"valorTotal": 20000
}
],
"totales": {
"valorBruto": 120000,
"baseGravable": 120000,
"totalImpuestos": 3800,
"valorConImpuestos": 123800,
"totalDescuentos": 0,
"totalCargos": 0,
"totalAnticipos": 16500,
"totalRetenciones": 0,
"totalAPagar": 107300
}
}
Un código fuera de catálogo responde 400: la API nunca lo reemplaza por un valor por defecto.
5.6 Documento soporte
El servicio tiene un recurso de documento soporte (compras a no obligados a facturar) en
/api/v1/documentos-soporte, pero no hace parte del contrato publicado ni de esta integración.
No lo uses: si lo necesitas, pídeselo a Kiai.
6. Estados, consulta y transmisión
6.1 Estados de un documento
| Campo | Valores | Qué significa |
|---|---|---|
estado | borrador → emitida | Borrador editable; emitida con número y CUFE/CUDE. |
transmisionEstado | null → pendiente → aceptada | rechazada | null en borrador. pendiente: en camino a la DIAN o esperando su veredicto. aceptada: validado por la DIAN. rechazada: la DIAN no lo validó. |
ambiente | null | habilitacion | produccion | Se fija al emitir y no cambia: te dice sin ambigüedad si el documento fue de prueba o real. |
transmisionMensaje | texto | Detalle del último resultado, incluidos los errores de la DIAN. |
transmisionTrackId | texto | CUFE/CUDE que devuelve la DIAN; en el set de pruebas, el ZipKey. |
El contrato lista también el estado anulada para facturas, pero ningún flujo lo asigna: no lo esperes. Anular
una factura se hace con una nota crédito de concepto 2 por el saldo total: la factura sigue
emitida y su saldo queda en 0.
Un documento solo está emitido ante la DIAN cuando transmisionEstado es aceptada. No lo entregues ni lo
des por válido con pendiente.
6.2 La transmisión es asíncrona
- Al emitir, el documento queda
pendientey Kiai lo transmite en segundo plano, normalmente en los segundos siguientes. - Si la DIAN falla de forma transitoria, Kiai reintenta solo: hasta 8 intentos con esperas crecientes (de 5 segundos hasta 5 minutos). Una caída de la DIAN no hace perder el documento.
- Si después de esos intentos sigue
pendientey no tienetransmisionTrackId, fuerza un reintento conPOST .../transmision(sección 6.4). - En el set de pruebas de habilitación el veredicto llega después (ver 3.4). Un documento
pendientecontransmisionTrackId(el ZipKey) ya está en manos de la DIAN: no lo retransmitas, porque le mandarías una copia y no acelera nada. Espera el veredicto. rechazadaes terminal: corrige lo que indican las reglas y crea un documento nuevo (el rechazado ya consumió su número y no se puede editar). Retransmitir sin cambios da el mismo rechazo.- Excepción: durante las 72 horas siguientes a un rechazo, Kiai vuelve a consultar a la DIAN. Si la DIAN
confirma que el documento sí quedó aceptado (típico de un rechazo por documento duplicado, regla 90), Kiai
lo corrige a
aceptaday te llega el webhook. Antes de reemitir un documento rechazado por duplicado, espera esa corrección o consultaGET .../application-response.
Para enterarte del resultado sin consultar, registra un webhook (sección 8).
6.3 Consultar
# Un documento, con sus líneas
curl -s "$API/facturas/<ID_FACTURA>" -H "Authorization: Bearer $TOKEN"
curl -s "$API/notas/<ID_NOTA>" -H "Authorization: Bearer $TOKEN"
# Listados paginados (resumen, sin líneas)
curl -s -G "$API/facturas" -H "Authorization: Bearer $TOKEN" \
--data-urlencode "\$filter=estado eq 'emitida' and numero eq 'SETT1'" \
--data-urlencode "\$orderby=fechaCreacion desc" \
--data-urlencode "\$top=20" --data-urlencode "\$count=true"
| Recurso | $filter admitido (combinable con and) | $orderby |
|---|---|---|
GET /facturas | estado eq '..', numero eq '..', cufe eq '..', adquirienteId eq {uuid} | fecha, numero, fechaCreacion (con o sin desc) |
GET /notas | tipo eq '..', estado eq '..', numero eq '..', cude eq '..', facturaId eq {uuid} | igual |
El listado es un resumen: no trae transmisionEstado ni permite filtrar por él. Para vigilar tus documentos
pendientes, guarda el id de cada uno y consúltalo por id, o usa webhooks (sección 8).
Otras consultas útiles:
GET /facturas/{id}/saldo— saldo acreditable frente a sus notas.GET /auditoria?documentoId={id}— la línea de vida del documento (creado, cambios de estado, transmisiones), con el usuario o proceso que originó cada cambio y sucorrelationId.GET /facturas/{id}/application-response(o/notas/{id}/...) — la respuesta firmada de la DIAN que prueba la validación, consultada en vivo por CUFE/CUDE. 409 si aún no hay CUFE o la DIAN todavía no tiene el documento; 503 si la DIAN no responde.- Un documento de otro emisor responde 404, igual que uno inexistente.
6.4 Reintentar la transmisión
POST /facturas/{id}/transmision (o /notas/{id}/transmision), sin cuerpo:
| Respuesta | Significado |
|---|---|
| 200 | Resultado registrado en el documento: aceptada, o pendiente si fue al set de pruebas. |
422 fuente: dian | La DIAN rechazó el documento; trae reglasDian. Queda rechazada. |
503 fuente: dian | La DIAN no respondió (o su canal está caído). El documento queda pendiente y Kiai lo reintenta solo. Respeta Retry-After. |
| 409 | No está emitido, ya fue aceptado (la DIAN lo rechazaría como duplicado), o una falla que reintentar no resuelve del lado de Kiai (p. ej. el certificado de firma): en ese caso contacta a Kiai. |
| 404 | No existe para tu emisor. |
7. PDF, XML, AttachedDocument y entrega al adquiriente
7.1 Representación gráfica (PDF)
curl -s "$API/facturas/<ID_FACTURA>/pdf" -H "Authorization: Bearer $TOKEN" -o factura.pdf
Disponible desde que el documento está emitido (409 en borrador). Incluye emisor y adquiriente, detalle con
descuentos y cargos, impuestos por tributo y tarifa, retenciones, datos de salud cuando aplica, montos en formato
colombiano, fechas en hora de Colombia, CUFE/CUDE y código QR. Si el generador está atendiendo el máximo de
documentos simultáneos responde 503 con Retry-After: reintenta.
Personalización opcional: logo por numeración (PUT /numeraciones-autorizadas/{id}/logo, PNG o JPEG en
base64, máximo 1 MB) y plantillas HTML propias (/plantillas-representacion-grafica, por tipo de documento o
numeración; editarlas no cambia cómo se imprime un documento ya emitido).
Cómo escribir una plantilla y qué variables puedes usar: Anexo B.
7.2 XML firmado
GET /facturas/{id}/xml (o /notas/{id}/xml): el XML UBL firmado, idéntico al transmitido a la DIAN, con el
nombre de archivo de la convención DIAN. 409 en borrador.
7.3 AttachedDocument
GET /facturas/{id}/attached-document (o /notas/{id}/...): el contenedor electrónico que se entrega al
comprador (Anexo Técnico 1.9 §6.4): el documento firmado tal como lo validó la DIAN más su ApplicationResponse,
firmado.
?formato=xml(por defecto): el AttachedDocument (ad…xml).?formato=zip: el paquete de entrega (z…zip) con el AttachedDocument y el PDF.- Solo para documentos aceptados por la DIAN (si no, 409).
- Se arma en el momento consultando a la DIAN, así que tarda lo que tarde la DIAN; si no responde, 503 con
Retry-After.
curl -s "$API/facturas/<ID_FACTURA>/attached-document?formato=zip" \
-H "Authorization: Bearer $TOKEN" -o entrega.zip
7.4 Envío automático al adquiriente
Cuando la DIAN acepta una factura o nota, Kiai programa un único envío automático al correo del adquiriente,
con el z…zip (AttachedDocument + PDF):
- Reintenta ante fallas transitorias (la DIAN no responde al armar el AttachedDocument, el proveedor de correo no
confirma) y lo da por
fallidoante fallas permanentes (adquiriente sin correo o con correo inválido). - Si el documento ya tenía una notificación exitosa, queda
omitido. - Consulta su estado en
GET /facturas/{id}/notificaciones/envio-automatico(o/notas/{id}/...):pendiente,enviado,fallidouomitido, conintentos,motivoyproximoIntento. Responde 404 si no hay envío automático (aún no aceptado, deshabilitado, o registro purgado tras 30 días). - También está activo para emisores en habilitación (ver 3.4).
- Los correos salen de
noreply@kiai.co, con el nombre «Kiai Facturación Electrónica».
7.5 Envío manual y trazabilidad de entrega
POST /facturas/{id}/notificacion (o /notas/{id}/notificacion) envía el mismo z…zip. Úsalo para reenviar o
copiar a otras personas; funciona también en habilitación.
curl -s -X POST "$API/facturas/<ID_FACTURA>/notificacion" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "copiaA": ["contabilidad@tuempresa.co"] }'
- Sin cuerpo: al correo del adquiriente.
destinatarios: reemplaza al adquiriente (p. ej. para una dirección corregida).copiaA: suma personas sin quitarle la entrega al adquiriente.anexos: hasta 5 archivos (5 MB en total; pdf, png, jpg, jpeg, gif, bmp, zip, xls, xlsx), en base64; viajan dentro del zip, enanexos.zip.- 409 si la DIAN no ha aceptado el documento o no hay destinatarios; 503 (sin enviar nada) si la DIAN no responde al armar el AttachedDocument.
GET /facturas/{id}/notificaciones lista cada envío (más recientes primero). enviado: true significa que el
proveedor de correo aceptó el mensaje; lo que pasó después está en estadoEntrega: enviado, demorado,
entregado, queja, rebotado o rechazado (con varios destinatarios manda el peor resultado), con
motivoEntrega, entregaReintentable y el detalle por destinatario en eventosEntrega.
8. Webhooks
Kiai te avisa cuando un documento llega a un estado final de transmisión, sin que tengas que consultar.
8.1 Registra tu endpoint
curl -s -X POST "$API/webhooks" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"url": "https://tu-sistema.example.com/kiai/webhooks",
"secret": "<SECRETO_COMPARTIDO_DE_16_A_200_CARACTERES>",
"eventos": "documento.transmision.aceptada;documento.transmision.rechazada"
}'
url: HTTPS absoluta (máximo 500 caracteres), pública (no se entrega a direcciones internas o privadas).secret: entre 16 y 200 caracteres. Se usa para firmar y nunca se devuelve.eventos: tipos separados por;, o*(por defecto) para todos.201con la suscripción (sin el secreto).409si ya tienes una suscripción a esa URL.- Registra el webhook antes de emitir: un evento se reparte entre las suscripciones activas en ese momento y no se reenvía a las que se creen después.
- Otras operaciones:
GET /webhooks,GET /webhooks/{id},DELETE /webhooks/{id}(cancela sus entregas pendientes). No hay edición: para cambiar el secreto, elimina y vuelve a crear la suscripción.
8.2 La entrega
POST a tu URL con Content-Type: application/json y estos encabezados:
| Encabezado | Contenido |
|---|---|
X-Kiai-Signature | sha256=<HMAC-SHA256(secret, cuerpo) en hexadecimal minúsculas> |
X-Kiai-Event-Id | Id del evento (el mismo en todos los reintentos): úsalo para no procesarlo dos veces. |
X-Kiai-Event-Type | documento.transmision.aceptada o documento.transmision.rechazada. |
Cuerpo:
{
"eventId": "<ID_EVENTO>",
"type": "documento.transmision.aceptada",
"documentoTipo": "factura",
"documentoId": "<ID_DOCUMENTO>",
"emisorId": "<ID_EMISOR>",
"estadoTransmision": "aceptada",
"ocurridoEn": "2026-09-23T15:04:21.1234567Z"
}
documentoTipo puede ser factura, nota, documento-soporte o evento-radian. El evento no trae el
documento: consúltalo con GET para ver su detalle (transmisionMensaje, reglas, número).
8.3 Verifica la firma
Calcula el HMAC sobre el cuerpo crudo (los bytes tal como llegaron, antes de parsear el JSON) y compáralo en tiempo constante.
Node.js:
const crypto = require('node:crypto');
function firmaValida(cuerpoCrudo /* Buffer */, cabecera /* X-Kiai-Signature */, secreto) {
const esperada = 'sha256=' + crypto.createHmac('sha256', secreto).update(cuerpoCrudo).digest('hex');
const a = Buffer.from(esperada, 'utf8');
const b = Buffer.from(cabecera || '', 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
C#:
using System.Security.Cryptography;
using System.Text;
static bool FirmaValida(string cuerpoCrudo, string cabecera, string secreto)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secreto));
var esperada = "sha256=" + Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(cuerpoCrudo))).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(esperada), Encoding.UTF8.GetBytes(cabecera ?? ""));
}
8.4 Reintentos
- Una entrega cuenta como exitosa solo con una respuesta 2xx en menos de 15 segundos. No se siguen redirecciones: un 3xx cuenta como fallo.
- Ante un fallo, Kiai reintenta con esperas crecientes (10 s, 20 s, 40 s, 80 s, 160 s): 6 intentos en total,
unos 5 minutos. Si todos fallan, la entrega queda
fallida. - Consulta la cola y las fallidas en
GET /webhooks/entregas?estado=pendiente|entregada|fallida(conintentos,httpStatusyultimoError). No hay reenvío manual de una entrega fallida: recupera el estado consultando los documentos.
Buenas prácticas: responde 2xx rápido y procesa después; ignora los X-Kiai-Event-Id repetidos; no asumas orden
entre eventos (pueden reintentarse por separado); y ten una consulta periódica de respaldo de tus documentos
pendiente.
9. Errores
9.1 Formato
Todo error es un Problem Details (RFC 9457) con Content-Type: application/problem+json:
| Campo | Contenido |
|---|---|
type, title, status, detail, instance | Estándar RFC 9457. El detail está en español. |
traceId | Id de la traza en Kiai. |
correlationId | Tu X-Correlation-Id (o el generado). |
fuente | De quién es la falla: cliente (la solicitud erró), dian (la DIAN no respondió o rechazó), kiai (error interno de Kiai). |
errors | Solo en 400: diccionario campo → mensajes. |
servicioDian, reglasDian, numeroDocumento | Solo en errores atribuidos a la DIAN. Cada regla: codigo, mensaje, tipo (rechazo impide la validación; notificacion solo advierte). |
9.2 Códigos
| HTTP | fuente | Significado | Qué hacer |
|---|---|---|---|
| 400 | cliente | Solicitud inválida: campo obligatorio, formato, código fuera de catálogo, totales que no cuadran, propiedad que no existe en el contrato, filtro OData no soportado. | Corrige lo que indica errors y reenvía. No reintentes igual. |
| 401 | cliente | Sin token, token vencido, firma inválida o audiencia distinta de facturacion. | Ver 2.5. |
| 403 | cliente | Falta el permiso de la operación, el token no identifica una empresa, tu empresa no tiene emisor activo, u operación reservada a Kiai. | Ver 2.5. |
| 404 | cliente | El recurso no existe o es de otro emisor. | Revisa el id. |
| 409 | cliente | Conflicto con el estado: documento que ya no es borrador, numeración no vigente, agotada, de otro tipo o ambiente, reglas de nota (factura no aceptada, saldo), consecutivo no válido, Idempotency-Key en curso, edición concurrente, certificado de firma no utilizable. | Lee el detail. Si es la llave en curso, espera Retry-After. Si es el certificado, contacta a Kiai. |
| 409 | dian | La DIAN rechazó una consulta (numeraciones, ApplicationResponse, eventos): trae reglasDian. | Corrige lo que indica la regla (p. ej. la asociación de tu numeración al software de Kiai). |
| 412 | cliente | If-Match no coincide con la versión actual (adquirientes). | Relee el recurso y reintenta. |
| 422 | dian | La DIAN rechazó el documento transmitido: trae reglasDian y numeroDocumento. El documento queda rechazada. | Corrige y emite un documento nuevo (ver 6.2). |
| 422 | cliente | Idempotency-Key reutilizada con otro cuerpo. | Usa una llave nueva. |
| 429 | kiai | Superaste el límite de tu emisor. | Espera Retry-After. |
| 500 | kiai | Error interno de Kiai. | Reintenta más tarde; si persiste, reporta traceId y correlationId. |
| 503 | dian | La DIAN no respondió o su canal está caído. Si era una transmisión, el documento queda pendiente y Kiai lo reintenta. | Espera Retry-After. Confirma en GET /estado-servicios. |
| 503 | — | El generador de PDF está ocupado (title: “El generador de PDF está ocupado.”). | Reintenta tras Retry-After. |
Ese 503 llega hoy con fuente: dian aunque la causa no es la DIAN: distínguelo por el title.
9.3 Ejemplo: la DIAN rechaza el documento (422)
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.21",
"title": "La DIAN rechazó el documento",
"status": 422,
"detail": "La DIAN rechazó la factura SETT15: Regla: FAJ43a, Rechazo: El DV del NIT del emisor no corresponde. El documento quedó registrado como rechazado; corrige lo que indican las reglas y vuelve a emitirlo.",
"instance": "/api/v1/facturas/7d9f.../transmision",
"traceId": "00-84b1...",
"correlationId": "5952f26eedc741b2a2ce29648af4a222",
"fuente": "dian",
"servicioDian": "SendBillSync",
"numeroDocumento": "SETT15",
"reglasDian": [
{ "codigo": "FAJ43a", "mensaje": "El DV del NIT del emisor no corresponde.", "tipo": "rechazo" }
]
}
9.4 Ejemplo: totales que no cuadran (400)
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "La solicitud no es válida.",
"status": 400,
"errors": {
"totales.totalAPagar": [ "El valor no cuadra: se recibió 64271.00 y el valor esperado es 64271.08." ]
},
"traceId": "00-84b1...",
"correlationId": "5952f26eedc741b2a2ce29648af4a222",
"fuente": "cliente"
}
Las claves de errors son la ruta del campo en tu cuerpo, p. ej. lineas[0].impuestos[1].valor. Una propiedad
que no existe en el contrato aparece con su nombre tal como la escribiste y el mensaje “La propiedad ’…’ no
existe en el contrato de la API v1…”.
9.5 Para diagnosticar
- Envía siempre
X-Correlation-Idy guárdalo junto a cada documento. - Ante una intermitencia,
GET /api/v1/estado-servicios(sin token) dice si Kiai y el canal DIAN estánoperativo,intermitenteocaido. - Al reportar un caso a Kiai, incluye
correlationId,traceId, eliddel documento y la hora.
10. Checklist de salida a producción
Credenciales y tokens
- Credencial de Facturación con los permisos mínimos por sistema;
client_secreten una bóveda, nunca en el código ni en logs. - Token pedido con
resource=facturacion, reutilizado durante su vida (60 min) y renovado antes de expirar. - Procedimiento de rotación y revocación de la credencial definido.
Emisor y DIAN
- Set de pruebas aprobado por la DIAN (o ya estabas habilitado con Kiai, 3.2) y
habilitadoDian: trueenGET /emisores/{id}/habilitacion. - Emisor en
ambiente: "produccion"(lo hace Kiai) confirmado conGET /emisores/{id}. - Datos del emisor correctos (NIT y DV, razón social, régimen, responsabilidades, dirección, municipio): van en cada XML.
- Numeración de producción visible en
GET /numeraciones-autorizadasconorigen: "dian"yestado: "vigente". - Si migras de otro proveedor: consecutivo siguiente fijado en la factura y en
NC/ND. -
correodel emisor vigente (recibe los avisos de numeración por vencer o agotarse).
Emisión
-
Idempotency-Keyen todos losPOST, con reintentos seguros ante cortes de red. - Totales calculados con redondeo mitad al par a 2 decimales; cantidades con máximo 6 decimales.
- Manejo de 400 (errores por campo), 409, 422 (no reintentar sin corregir) y 429/503 (respetar
Retry-After). - Un documento solo se da por válido con
transmisionEstado: "aceptada"; lospendientese vigilan. - Se guardan
id,numero,cufe/cude,ambienteycorrelationIdde cada documento. - Correos reales y válidos de los adquirientes (reciben el envío automático).
Operación
- Webhook registrado antes de emitir, con firma verificada e idempotencia por
X-Kiai-Event-Id. - Consulta de respaldo para documentos
pendientey para entregas de webhookfallida. - Monitoreo de
GET /estado-serviciosy registro decorrelationIden tus logs. - Política propia de conservación de tus documentos (XML firmado, AttachedDocument, PDF).
11. Aliados: registra y habilita a tus clientes por API
Un aliado es una empresa que registra a sus clientes en Kiai y los lleva hasta quedar habilitados ante la DIAN, todo desde su propio sistema. Si solo integras tu propia empresa, esta sección no es para ti: sigue de la 2 a la 10.
11.1 Cómo funciona
Estas operaciones son del servicio de identidad de Kiai (usuarios.kiai.co), no de la API de documentos:
| Qué | URL | Autenticación |
|---|---|---|
| Endpoint de token | https://usuarios.kiai.co/api/oauth/token | client_id + client_secret de aliado |
| Clientes | https://usuarios.kiai.co/api/v1/clientes | Token Bearer de aliado |
El recorrido:
- Kiai le da a tu empresa el rol de aliado y recibes tu credencial (11.2).
- Pides un token (11.3).
- Registras a tu cliente con los productos que va a usar (11.4) y consultas hasta que Facturación quede
LISTO(11.5). - Tu cliente asocia el software de Kiai en la DIAN y te entrega su
TestSetId(los pasos están en 3.2). - Pides su habilitación (11.6) y consultas hasta que quede
HABILITADO. - Creas la credencial de Facturación de tu cliente (11.7) y con ella emites a su nombre.
Reglas que conviene saber antes de empezar:
- Tus clientes son tuyos. No dices en el cuerpo quién es el aliado: sale de tu credencial. Un campo de aliado
en el cuerpo se rechaza (
400 ALIADO_NO_SE_ENVIA), y solo ves y operas los clientes que registraste tú. - El registro responde de inmediato. Tu cliente queda creado al instante; cada producto se activa después, por su cuenta, y tú consultas su estado.
- Registrar de nuevo es seguro. Si envías otra vez el mismo cliente, recibes
200con el mismo registro: no se crea un duplicado. - No hay ambiente de pruebas para el registro. Cada cliente que registras queda creado de verdad: usa los datos reales de tu cliente.
11.2 Tu credencial de aliado
El rol de aliado lo otorga Kiai. Escríbenos (ver Soporte) con el NIT de tu empresa; con el rol recibes tu credencial:
client_id: empieza porkiai_ali_.client_secret: se muestra una sola vez. Guárdalo en tu bóveda de secretos. Kiai solo conserva una huella y no puede recuperarlo.
Reglas:
- Hay una credencial activa por aliado.
- Si pierdes el secreto o sospechas que se filtró, avísale a Kiai de inmediato: se rota (mismo
client_id, secreto nuevo) o se revoca. - Esta credencial registra, consulta y habilita clientes, y crea la credencial con que emites por cada uno (11.7). Ella misma no emite documentos.
11.3 Pide el token
POST https://usuarios.kiai.co/api/oauth/token, igual que en 2.2 pero sin resource:
curl -s -X POST https://usuarios.kiai.co/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=<TU_CLIENT_ID_DE_ALIADO>" \
--data-urlencode "client_secret=<TU_CLIENT_SECRET>"
También acepta JSON con los mismos campos. Respuesta:
{
"access_token": "<TOKEN_JWT>",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "clientes.registrar clientes.leer"
}
- Vida del token: 60 minutos. Reutilízalo y pide uno nuevo antes de que expire; no hay token de refresco.
- Úsalo como
Authorization: Bearer <access_token>en las llamadas de esta sección.
Errores de este endpoint (formato { "error": "...", "error_description": "..." }):
| HTTP | error | Causa |
|---|---|---|
| 400 | invalid_request / unsupported_grant_type | Falta un campo, o grant_type no es client_credentials. |
| 401 | invalid_client | client_id o client_secret incorrectos, o la credencial fue revocada. |
| 403 | access_denied con ALIADO_BLOQUEADO | Kiai bloqueó tu rol de aliado. Contacta a Kiai. |
| 403 | access_denied con NO_ES_ALIADO | Tu empresa ya no tiene el rol de aliado. |
11.4 Registra a tu cliente
POST https://usuarios.kiai.co/api/v1/clientes con tu token de aliado:
curl -s -X POST https://usuarios.kiai.co/api/v1/clientes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"empresa": {
"tipoPersona": "juridica",
"tipoDocumento": "31",
"documento": "900123456",
"digitoVerificacion": "8",
"razonSocial": "Mi Cliente S.A.S",
"regimen": "48",
"responsabilidades": ["O-13"],
"ubicacion": {
"pais": "CO",
"departamento": "05",
"ciudad": "05001",
"direccion": "Calle 10 # 20-30"
}
},
"responsable": {
"nombres": "Ana María",
"primerApellido": "Gómez",
"correo": "ana@micliente.co",
"telefono": "3001234567"
},
"productos": ["EINV"]
}'
| Campo | Obligatorio | Regla |
|---|---|---|
empresa.tipoPersona | Sí | "juridica" o "natural". |
empresa.tipoDocumento | Sí | Código DIAN: "31" NIT, "13" cédula de ciudadanía. |
empresa.documento | Sí | Solo dígitos, como texto (entre comillas); máximo 18. Sin puntos, guiones ni espacios. |
empresa.digitoVerificacion | No | Un dígito. Con NIT, si lo envías debe ser el correcto. |
empresa.razonSocial | Sí | Máximo 200 caracteres. |
empresa.regimen | Recomendado | Código DIAN: "48" responsable de IVA, "49" no responsable. |
empresa.responsabilidades | Recomendado | Códigos del RUT ("O-13", "R-99-PN"…); máximo 30. |
empresa.ubicacion | Recomendado | pais ("CO"), departamento y ciudad con su código DANE ("05", "05001"), codigoPostal y direccion (máximo 100 caracteres). |
responsable.nombres | Sí | Máximo 50 caracteres. |
responsable.primerApellido | Sí | Con segundoApellido (opcional), máximo 100 caracteres entre los dos. |
responsable.correo | Sí | Máximo 50 caracteres, sin tildes. No puede tener ya una cuenta en Kiai. |
responsable.telefono | Sí | Entre 7 y 20 caracteres: dígitos y espacios; puede empezar con +. |
productos | Sí | Al menos uno. Facturación electrónica es "EINV". |
Tenlo en cuenta:
- Envía los datos fiscales como están en el RUT de tu cliente: son los que usa Facturación.
- El responsable recibe un correo para activar su cuenta y queda como administrador de la empresa. La
activación no detiene el recorrido: puedes habilitar a tu cliente y crear su credencial aunque todavía no haya
activado su cuenta. Mientras no la active, esa persona no puede entrar a
usuarios.kiai.co. - Persona natural:
"tipoPersona": "natural", con su cédula ("13") o su NIT ("31"). - Campos que no existen en el contrato se rechazan (
400 CAMPOS_DESCONOCIDOS): un error de escritura no pasa en silencio.
Respuesta 201 con la ficha del cliente (la misma de 11.5) y la cabecera Location. Guarda el id: es el
que usas para consultarlo y habilitarlo.
11.5 Consulta a tus clientes
| Llamada | Qué devuelve |
|---|---|
GET /api/v1/clientes/{id} | Un cliente tuyo. Uno que no es tuyo responde igual que uno que no existe: 404. |
GET /api/v1/clientes?$top=20&$skip=0&$count=true | Tus clientes, del más reciente al más antiguo, con el estado de cada producto. $top máximo 100. |
Solo se admiten $top, $skip y $count; cualquier otro operador responde 400 OPERADOR_NO_SOPORTADO.
{
"id": "<ID_CLIENTE>",
"tipoPersona": "juridica",
"tipoDocumento": "31",
"documento": "900123456",
"digitoVerificacion": "8",
"razonSocial": "Mi Cliente S.A.S",
"correo": "ana@micliente.co",
"creadoEn": "2026-10-08T14:02:11Z",
"productos": [
{
"codigo": "EINV",
"nombre": "Kiai Facturación",
"estado": "HABILITADO",
"aprovisionamiento": "LISTO",
"motivoAprovisionamiento": null,
"referenciaExterna": "<ID_EN_FACTURACION>",
"habilitaciones": [
{
"tipoDocumento": "FACTURA_ELECTRONICA",
"estado": "HABILITADO",
"motivoRechazo": null,
"actualizadoEn": "2026-10-08T14:14:09Z",
"testSetId": "<TEST_SET_ID>",
"documentosPedidos": 1
}
],
"motivoRechazo": null
}
]
}
Cada producto trae dos estados. El primero, aprovisionamiento, dice si el producto ya está activo para tu
cliente:
aprovisionamiento | Qué significa | Qué hacer |
|---|---|---|
PENDIENTE | Se está activando. | Consulta de nuevo en unos segundos. |
LISTO | Activo en el producto. | Ya puedes pedir la habilitación (11.6). |
ESPERANDO_ALIADO | Kiai está terminando de activar tu empresa como aliado. No es un error. | Nada: se activa solo. No registres de nuevo. |
ERROR | No se pudo activar; motivoAprovisionamiento dice por qué. | Corrige el dato y registra de nuevo. |
El segundo, estado, es la habilitación ante la DIAN: SIN_INICIAR, EN_PROCESO, HABILITADO o RECHAZADO
(11.6).
11.6 Habilita a tu cliente ante la DIAN
Antes, tu cliente asocia el software de KIAI S.A.S. en el portal de habilitación de la DIAN y copia su
TestSetId: son los pasos de «Si es la primera vez que facturas con Kiai», en 3.2. La DIAN entrega un
TestSetId por tipo de documento.
Con el producto EINV en LISTO, pides la habilitación un tipo de documento a la vez y en este orden:
- Factura electrónica (
FACTURA_ELECTRONICA). - Cuando la factura quedó
HABILITADO, el documento equivalente POS (DOCUMENTO_EQUIVALENTE_POS), si tu cliente lo usa.
curl -s -X POST https://usuarios.kiai.co/api/v1/clientes/<ID_CLIENTE>/productos/EINV/habilitaciones \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "tipoDocumento": "FACTURA_ELECTRONICA", "testSetId": "<TEST_SET_ID_DE_TU_CLIENTE>" }'
- Respuesta 202 con la cabecera
Location: Kiai envía el set de pruebas a la DIAN por su cuenta. Tú no emites documentos de prueba. - Repetir el mismo pedido no envía nada otra vez: responde 200 con el estado actual.
- Sigue el resultado con
GET /api/v1/clientes/{id}, dentro del productoEINV: cada tipo trae suestado, sutestSetIdy cuántos documentos de prueba se enviaron (documentosPedidos). Suele resolverse en segundos; si la DIAN tarda, consulta cada 10 a 30 segundos. - Si queda
RECHAZADO,motivoRechazodice por qué y qué hacer. Corregido el problema, vuelve a pedirla. - El
estadodel producto sigue a la factura electrónica: con la factura enHABILITADO, tu cliente ya está habilitado ante la DIAN con Kiai como proveedor tecnológico.
| HTTP | codigo | Cuándo |
|---|---|---|
| 409 | PRODUCTO_NO_LISTO | El producto EINV de tu cliente todavía no está LISTO. |
| 409 | HABILITACION_EN_CURSO | Tu cliente ya tiene una habilitación en curso: espera el resultado. |
| 409 | YA_HABILITADO | Ese tipo de documento ya quedó habilitado con otro set. |
| 409 | FACTURA_ELECTRONICA_PRIMERO | Pediste el POS sin la factura electrónica habilitada. |
| 400 | PRODUCTO_SIN_HABILITACION | Ese producto no se habilita ante la DIAN (solo EINV). |
| 404 | PRODUCTO_NO_ENCONTRADO | Tu cliente no tiene ese producto. |
11.7 Emite a nombre de tu cliente
Tu credencial de aliado no emite documentos. Para emitir por tu cliente creas, con tu mismo token de aliado, la credencial de Facturación de su empresa:
curl -s -X POST https://usuarios.kiai.co/api/v1/clientes/<ID_CLIENTE>/productos/EINV/credenciales \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "nombre": "ERP de mi empresa (producción)" }'
Respuesta 201:
{
"clientId": "<CLIENT_ID_DE_TU_CLIENTE>",
"clientSecret": "<CLIENT_SECRET_DE_TU_CLIENTE>",
"nombre": "ERP de mi empresa (producción)",
"scopes": "facturacion.emitir facturacion.consultar",
"aviso": "Guarda el clientSecret ahora: no se volverá a mostrar. ..."
}
- El cuerpo es un objeto JSON y puede ir vacío (
{}). Elnombrees opcional (máximo 100 caracteres); sin él, la credencial se llama «Integración de» y la razón social de tu empresa. No admite otros campos: los permisos son siemprefacturacion.emitiryfacturacion.consultar. - El
clientIdempieza porkiai_live_. ElclientSecretse muestra una sola vez: guárdalo en tu bóveda de secretos, asociado a ese cliente. - La credencial es de la empresa de tu cliente. Con ella pides el token con
resource=facturacion(2.2), consultas sus numeraciones (sección 4) y emites (sección 5): ese token opera a nombre de tu cliente, no de tu empresa. - Un token opera a nombre de una sola empresa. Guarda una credencial por cliente y pide el token con la del cliente por el que vas a emitir. No hay un token que sirva para varios.
- Hay una credencial activa creada por el aliado por cliente. Por esta vía solo se crea: no se rota ni se
revoca. Si pierdes el secreto, tu cliente la revoca en
usuarios.kiai.co, sección INTEGRACIONES, y creas otra. - Tu cliente la ve en INTEGRACIONES, junto a las que él mismo cree (2.1), y puede revocarla cuando quiera.
- Tu cliente ya está habilitado con Kiai: no repite el set de pruebas ni envía ese
TestSetIdpor otro camino (3.3). El paso a producción se coordina con Kiai (3.6).
| HTTP | codigo | Cuándo |
|---|---|---|
| 409 | PRODUCTO_NO_LISTO | El producto EINV de tu cliente todavía no está LISTO. |
| 409 | CREDENCIAL_ACTIVA | Tu cliente ya tiene una credencial activa creada por su aliado. |
| 400 | PRODUCTO_SIN_CREDENCIAL | Ese producto no tiene credencial por esta vía (solo EINV). |
| 404 | PRODUCTO_NO_ENCONTRADO | Tu cliente no tiene ese producto. |
| 503 | PERMISOS_NO_DISPONIBLES | Los permisos de Facturación no se pueden otorgar en este momento. Contacta a Kiai. |
11.8 Límites
- Tope diario de clientes nuevos. Tu rol de aliado puede tener un tope de clientes nuevos por día (día de
Colombia). Volver a registrar uno tuyo no cuenta. Al llegar al tope:
429 TOPE_DIARIO_ALCANZADOconRetry-After(segundos hasta la medianoche de Colombia). Si necesitas más, pídeselo a Kiai. No reintentes automáticamente un 429 de este endpoint: la espera puede ser de horas. - Altas simultáneas. Si envías muchas altas a la vez, Kiai las atiende una tras otra. Si una espera
demasiado:
503 ALTAS_SIMULTANEASconRetry-Afterde unos segundos; reintenta.
11.9 Errores
Todo error de estas operaciones responde application/problem+json (RFC 9457) con un campo codigo
estable. Tu sistema debe decidir por codigo, no por el texto de detail, que puede cambiar.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Cliente no encontrado",
"status": 404,
"detail": "No tienes un cliente con ese id.",
"traceId": "00-84b1...",
"codigo": "CLIENTE_NO_ENCONTRADO"
}
| HTTP | codigo | Cuándo |
|---|---|---|
| 400 | DATOS_INVALIDOS | Un dato no cumple el formato; errors dice cuál campo y por qué. |
| 400 | CAMPOS_DESCONOCIDOS | El cuerpo trae campos que no existen en el contrato. |
| 400 | ALIADO_NO_SE_ENVIA | El cuerpo intenta decir quién es el aliado. |
| 400 | PRODUCTO_NO_SOPORTADO | Un producto que no se registra por esta vía. |
| 400 | OPERADOR_NO_SOPORTADO / PARAMETRO_INVALIDO | Paginación con operadores o valores no admitidos. |
| 403 | ALIADO_BLOQUEADO | Kiai bloqueó tu rol de aliado; aplica también a tokens ya emitidos. |
| 403 | NO_ES_ALIADO | Tu empresa no tiene el rol de aliado. |
| 403 | TOKEN_SIN_EMPRESA | El token no identifica a tu empresa; pide uno nuevo. |
| 404 | CLIENTE_NO_ENCONTRADO | El cliente no existe o no es tuyo. |
| 409 | DOCUMENTO_REGISTRADO | Ese documento ya es cliente de Kiai por otra vía. Contacta a Kiai. |
| 409 | CORREO_NO_DISPONIBLE | El correo del responsable ya tiene una cuenta en Kiai. |
| 409 | CORREO_DISTINTO | Reenviaste un cliente tuyo con otro responsable. |
| 409 | PRODUCTOS_DISTINTOS | Reenviaste un cliente tuyo pidiendo productos que no tenía. |
| 429 | TOPE_DIARIO_ALCANZADO | Llegaste al tope diario de clientes nuevos (ver Retry-After). |
| 503 | ALTAS_SIMULTANEAS | Otra alta tuya está en curso (ver Retry-After). |
Ante un incidente, envíanos el traceId de la respuesta y la fecha y hora. Nunca nos envíes tu client_secret.
Anexo A — Otros recursos del contrato
| Recurso | Para qué | Notas |
|---|---|---|
/adquirientes | Directorio de compradores (crear, listar, editar, eliminar). | No es obligatorio: el comprador viaja dentro del documento. GET por id devuelve ETag; envíalo en If-Match al actualizar (412 si cambió). |
/adquirientes/dian/{tipoDocumento}/{numeroDocumento} | Consulta en la DIAN el nombre y correo registrados de un documento. | Solo lectura; útil para autocompletar. |
/catalogos/{tipo} y /catalogos/{tipo}/{codigo} | Catálogos DIAN/DANE con los valores más usados (la lista completa es la del Anexo Técnico 1.9): tipos-documento, tipos-organizacion, regimenes, responsabilidades-fiscales, unidades-medida, formas-pago, medios-pago, departamentos, municipios. | $top por defecto 100; padreCodigo eq '..' filtra municipios por departamento. |
/auditoria?documentoId= | Línea de vida de un documento. | Solo de tu emisor. |
POST /descargas | ZIP con PDF + XML firmado de hasta 50 documentos y un manifiesto.json con el estado de cada uno. | Los no encontrados o en borrador se reportan en el manifiesto. |
/facturas/{facturaId}/eventos | Eventos RADIAN (030 acuse, 031 reclamo, 032 recibo del bien, 033 aceptación expresa, 034 tácita) que registras sobre una factura emitida. | GET .../eventos/dian consulta en vivo en la DIAN todos los eventos de la factura, incluidos los de terceros. |
/plantillas-representacion-grafica | Plantillas HTML propias para el PDF. | El HTML se valida al guardarlo: sin scripts y con las imágenes embebidas como data:. Ver el Anexo B. |
/estado-servicios | Estado de Kiai y del canal DIAN. | Sin token. |
Anexo B — Plantillas del PDF
Si no creas ninguna plantilla, el PDF de tus documentos sale con la plantilla base de Kiai. Si quieres tu propio
diseño, crea una plantilla HTML con /plantillas-representacion-grafica. Este anexo explica cómo se escribe, qué se
valida al guardarla y qué variables puedes usar.
B.1 Cómo funciona
B.1.1 El motor: marcadores de doble corchete
La plantilla es HTML con marcadores de un motor propio de Kiai. No es Scriban, Liquid, Handlebars ni Razor:
no hay expresiones, ni comparaciones, ni else, ni filtros. Todos los valores llegan ya formateados como texto
(montos, fechas y nombres de catálogo); la plantilla solo decide dónde van y si se muestran.
| Marcador | Qué hace |
|---|---|
[[variable]] | Imprime el valor, escapado como HTML. Los saltos de línea se imprimen como <br>. |
[[FOR:lista]]…[[END:lista]] | Repite el contenido por cada elemento de la lista. Se puede anidar. |
[[indice]] (o [[Index]]) | Dentro de un FOR, la posición del elemento empezando en 1. En FOR anidados es la del FOR más interno. |
[[IF:nombre]]…[[END:IF:nombre]] | Imprime el contenido si nombre es una condición activa, una lista con elementos o una variable con valor no vacío. |
[[IFNOT:nombre]]…[[END:IFNOT:nombre]] | Lo contrario de IF (hace las veces de else). |
[[FIRST:lista]]…[[END:FIRST:lista]] · [[LAST:lista]]…[[END:LAST:lista]] | Dentro del FOR de esa lista, solo en el primer / último elemento. Existen por compatibilidad con plantillas antiguas. |
<template id="pie-pagina">…</template> | Pie de cada página del PDF. Admite <span class="pageNumber"></span> y <span class="totalPages"></span>. |
Ejemplo mínimo (pasa la validación al guardarlo):
<html><body>
<h1>[[documento.titulo]] No. [[documento.numero]]</h1>
<p>[[emisor.razonSocial]] · [[emisor.documento]]</p>
[[FOR:lineas]]<p>[[indice]]. [[descripcion]] — [[total]]</p>[[END:lineas]]
<p>Total a pagar: [[documento.moneda]] [[totales.totalAPagar]]</p>
</body></html>
Reglas del motor que conviene conocer:
- Los nombres distinguen mayúsculas y minúsculas.
[[Documento.Numero]]no existe; es[[documento.numero]]. - Sin espacios dentro del marcador. Un marcador admite de 1 a 120 caracteres sin espacios, corchetes,
<,>ni comillas.[[ documento.numero ]](con espacios) no se reconoce: se imprime literalmente en el PDF y la validación no lo reporta, porque para el motor es texto. - Una sola pasada. Un dato que contenga
[[…]](por ejemplo, en la descripción de una línea) se imprime como texto; nunca se interpreta. - Ámbito dentro de un
FOR. Una variable se busca primero en el elemento actual y luego hacia afuera, hasta el documento. Dentro de[[FOR:lineas]],[[valor]]es el valor de la línea; dentro de un[[FOR:impuestos]]anidado en la línea,[[valor]]es el del impuesto. Las variables del documento ([[documento.numero]]…) se pueden usar en cualquier nivel. - Las listas existen siempre, aunque estén vacías. Un
[[FOR:impuestos]]dentro de una línea sin impuestos no imprime nada: no toma los impuestos del documento. - Un monto en cero se imprime (
0,00) y cuenta como valor no vacío paraIF. Para ocultar filas opcionales usa las condiciones (totales.hayDescuentos,totales.hayCargos…), no la variable del monto. - Al imprimir, el motor es tolerante: una variable desconocida se imprime vacía y un bloque mal cerrado se cierra solo, para no tumbar un PDF. Al guardar la plantilla, en cambio, esos errores se rechazan (ver B.2).
B.1.2 A qué documentos aplica
| Documento | Cómo se descarga el PDF | Condición en la plantilla |
|---|---|---|
| Facturas de venta (tipos 01 a 04) | GET /api/v1/facturas/{id}/pdf | factura |
| Nota crédito (91) y nota débito (92) | GET /api/v1/notas/{id}/pdf | nota, notaCredito, notaDebito |
- Solo hay PDF de documentos emitidos: un borrador responde
409. - El PDF es tamaño carta, con márgenes de 10 mm (20 mm abajo cuando la plantilla trae pie de página). El archivo
se llama como el número del documento (
SETT123.pdf). - Si el generador está saturado responde
503conRetry-After: 5. - No hay vista previa. Ningún endpoint renderiza una plantilla sin emitir un documento. Para ver el resultado, emite un documento después de crear o editar la plantilla y descarga su PDF (ver B.1.4). Hazlo mientras tu emisor está en habilitación: en producción, cada documento que emites es real ante la DIAN.
B.1.3 Cómo se asigna una plantilla
Recurso: /api/v1/plantillas-representacion-grafica (listar, obtener, crear, actualizar, eliminar). Leer exige el
scope facturacion.consultar; crear, actualizar y eliminar exigen facturacion.emitir. La empresa dueña sale del
token: no la envíes.
Cuerpo de POST y PUT:
{
"nombre": "Sede Medellín",
"html": "<html>…</html>",
"tipoDocumento": "factura",
"numeracionId": "<ID_NUMERACION>"
}
tipoDocumento:factura,nota-creditoonota-debito; omitido onull= todos. Se compara contra el tipo de la numeración con que se emitió el documento; todas las facturas se emiten con numeraciones de tipofactura.numeracionId: una numeración autorizada de tu empresa (por ejemplo, la de una sede o punto de venta); omitido onull= todas.
Como el HTML viaja dentro del JSON, lo más cómodo es armar el cuerpo desde el archivo:
jq -n --rawfile html plantilla.html \
'{nombre: "Sede Medellín", html: $html, tipoDocumento: "factura"}' |
curl -s -X POST "$API/plantillas-representacion-grafica" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
--data-binary @-
Cuando se imprime un documento, entre las plantillas vigentes en la fecha de emisión de ese documento gana la más específica:
| # | Plantilla | Gana sobre |
|---|---|---|
| 1 | Con numeración y tipo de documento | todas |
| 2 | Solo con numeración | 3, 4, 5 |
| 3 | Solo con tipo de documento | 4, 5 |
| 4 | Sin asignación (general de tu empresa) | 5 |
| 5 | Plantilla base de Kiai (si no tienes ninguna aplicable) | — |
A igual especificidad gana la que empezó a regir más tarde. La plantilla base de Kiai no se puede descargar por la API.
B.1.4 Versionado: lo ya emitido no cambia
- El
idde una plantilla no cambia al editarla;versionsube en cadaPUT. - Cada edición crea una versión nueva, que rige desde ese instante (
vigenteDesde). Un documento se imprime siempre con la versión vigente cuando se emitió: editar o eliminar una plantilla no cambia el PDF de lo ya emitido. - Por lo mismo, una plantilla nueva no se aplica a documentos emitidos antes de crearla.
DELETEhace que deje de listarse y de aplicarse a documentos nuevos, que pasan a la siguiente plantilla aplicable de la tabla anterior (o a la base de Kiai si no queda ninguna).
B.2 Validación al guardar
POST y PUT validan el HTML al guardarlo, no al emitir.
B.2.1 Respuestas
| Código | Cuándo |
|---|---|
400 | La plantilla no pasa la validación. El detalle va en errors.html (un mensaje por hallazgo, en español), o en errors.nombre, errors.tipoDocumento o errors.numeracionId. Se reportan hasta 20 hallazgos; si hay más, un último mensaje pide corregirlos y reenviar. |
409 | Ya hay una plantilla vigente con ese nombre en tu empresa; la numeración no existe o no es de tu empresa; u otra edición simultánea ganó la carrera (recarga y reintenta). |
404 | GET, PUT o DELETE sobre una plantilla que no existe o ya se eliminó. |
B.2.2 Qué se rechaza
Campos:
nombrevacío o de más de 120 caracteres.htmlvacío o de más de 128 KiB en UTF-8 (131.072 bytes). La plantilla base de Kiai pesa unos 20 KB.tipoDocumentofuera defactura,nota-creditoynota-debito.numeracionIdcon el GUID vacío (00000000-…).
Estructura y diccionario (el HTML se analiza con el mismo analizador que usa el motor al imprimir):
- Un bloque sin cerrar o un cierre sin apertura.
- Una variable, lista o condición que no existe en el ámbito donde se usa.
[[descripcion]]solo existe dentro de[[FOR:lineas]];[[IF:obsequio]], también. [[FOR:x]],[[FIRST:x]]o[[LAST:x]]sobre algo que no es una lista (el mensaje trae las listas disponibles).[[IF:x]]sobre un nombre que no es condición, lista ni variable.- Una condición usada como variable:
[[factura]]no es válido; es[[IF:factura]].
Seguridad. El HTML lo abre el navegador de Kiai para imprimir el PDF, así que se rechaza todo lo que ejecute código, incruste otro documento o cargue algo de afuera:
-
Etiquetas
<script>,<iframe>,<frame>,<frameset>,<object>,<embed>,<applet>,<portal>,<base>y<link>. La revisión es sobre el texto completo: también se rechazan dentro de un comentario HTML. -
Atributos de evento
on…=(onclick,onload,onerror…). -
Cualquier URL en los atributos
src,srcset,href,xlink:href,poster,action,formaction,background,cite,pingymanifest, enurl(…)de CSS o en@import, salvo:- un ancla
#…; - un marcador
[[variable]]completo (por ejemplosrc="[[documento.codigoQr]]"); - un
data:literal; - el origen
https://assets.kiai.co, que el contrato menciona (ver la advertencia de B.2.3).
Eso incluye rutas relativas (
/logo.png),http://,file:,javascript:y enlaces absolutos a otros sitios. Por ejemplo, un<a href="https://catalogo-vpfe.dian.gov.co/…">escrito a mano se rechaza: usahref="[[documento.urlConsultaDian]]". - un ancla
B.2.3 Imágenes, fuentes y CSS
- CSS: en
<style>dentro de la plantilla o en atributosstyle.<link rel="stylesheet">se rechaza. - Imágenes: embebidas como
data:(base64). El logo y el QR ya llegan así ([[documento.logo]],[[documento.codigoQr]]). El logo es el de la numeración (PUT /api/v1/numeraciones-autorizadas/{id}/logo, PNG o JPEG de hasta 1 MB). - Fuentes: el generador tiene instaladas Liberation (Sans, Serif, Mono) y DejaVu. Una fuente propia
tiene que ir embebida con
@font-face { src: url(data:…) }, dentro del tope de 128 KiB. - Sin JavaScript: el PDF se genera con JavaScript deshabilitado.
- ⚠️
https://assets.kiai.cose acepta al guardar, pero hoy no se descarga al imprimir. El generador solo carga recursosdata:: una imagen apuntada a otro origen sale vacía en el PDF. Embebe todo comodata:. - ⚠️ Usa las variables de imagen solo en atributos (
src), no dentro de<style>. Los valores se escapan como HTML y el+del base64 sale como+. En un atributo el navegador lo decodifica; en el CSS de un<style>no, y la imagen queda rota.background: url('[[documento.logo]]')pasa la validación pero no se ve. - Pie de página: el contenido del último
<template id="pie-pagina">del documento se imprime en el margen inferior de cada hoja. Se dibuja fuera del documento, así que no hereda el CSS de<style>: sus estilos van en línea. Admite las variables del diccionario. - El tamaño carta y los márgenes los fija el servicio al generar el PDF: no dependas de
@pagepara cambiarlos.
B.3 Diccionario de variables
Todas las variables son texto ya formateado. No hay helpers ni filtros: el formato lo pone el servicio, siempre con convención colombiana.
| Formato | Cómo sale | Ejemplo |
|---|---|---|
| Monto | Miles con punto, dos decimales con coma | 10.000,00 · -3.000,00 |
| Cantidad, tarifa, porcentaje | Mínimo dos decimales y hasta seis | 3,00 · 1,50 · 0,125 |
| Fecha | dd/MM/yyyy | 13/09/2026 |
| Fecha y hora | dd/MM/yyyy HH:mm:ss, hora de Colombia (UTC-5) | 13/09/2026 15:49:26 |
| Valor ausente | Cadena vacía | — |
Tipos en las tablas: texto, monto, decimal, fecha, fecha y hora, data URI, URL.
B.3.1 Documento
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
documento.titulo | texto | Nombre del tipo de documento | Factura electrónica de venta · Nota crédito electrónica |
documento.codigoTipo | texto | Código DIAN del tipo de documento | 01 · 91 |
documento.numero | texto | Número completo (prefijo + consecutivo) | SETT123 |
documento.prefijo | texto | Prefijo de la numeración | SETT |
documento.fechaEmision | fecha y hora | Fecha de emisión | 13/09/2026 15:49:26 |
documento.fechaValidacion | fecha y hora | Fecha de validación DIAN, o Pendiente de validación si aún no se transmitió | 13/09/2026 16:00:53 |
documento.tipoOperacion | texto | Tipo de operación con su nombre | 10 - Estándar · 20 - Nota crédito que referencia una factura electrónica · SS-CUFE - Sector salud |
documento.codigoTipoOperacion | texto | Código del tipo de operación | 10 |
documento.moneda | texto | Moneda (código ISO) | COP |
documento.tasaCambio | monto | TRM; vacía si el documento no trae tasa de cambio | 3.950,50 |
documento.fechaTasaCambio | fecha | Fecha de la TRM; vacía sin tasa de cambio | 13/09/2026 |
documento.cantidadLineas | texto | Número de líneas | 3 |
documento.ambiente | texto | Producción o Habilitación | Producción |
documento.etiquetaIdentificador | texto | CUFE (facturas) o CUDE (notas) | CUFE |
documento.identificador | texto | CUFE o CUDE del documento | a1b2c3… (96 caracteres) |
documento.cufe | texto | Igual a documento.identificador (también en notas) | a1b2c3… |
documento.urlConsultaDian | URL | Consulta del documento en el catálogo DIAN de su ambiente (es el contenido del QR) | https://catalogo-vpfe.dian.gov.co/document/searchqr?documentkey=a1b2… |
documento.codigoQr | data URI | Imagen PNG del QR, generada por Kiai. Úsala en <img src> | data:image/png;base64,iVBOR… |
documento.logo | data URI | Logo de la numeración (PNG o JPEG); vacío si no tiene | data:image/png;base64,iVBOR… |
documento.observaciones | texto | Observaciones del documento, una por línea; si el emisor es un consorcio, además sus participantes con su porcentaje | Entrega en bodega 3 |
documento.leyendaLegal | texto | Leyenda de la factura como título valor, solo en facturas (01–04); vacía en notas. La redacta Kiai e incluye la razón social del emisor | Esta factura electrónica de venta se asimila… |
B.3.2 Forma y medios de pago
En las notas estas variables salen vacías (y la lista mediosPago, vacía).
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
documento.formaPago | texto | Crédito si el código es 2; Contado en cualquier otro caso | Crédito |
documento.codigoFormaPago | texto | Código de forma de pago | 2 |
documento.fechaVencimiento | fecha | Vencimiento; solo con forma de pago crédito | 13/10/2026 |
documento.mediosPago | texto | Medios de pago con su nombre, separados por ; | 47 - Transferencia bancaria |
documento.referenciaPago | texto | Referencias de pago informadas, separadas por ; | TRF-001 |
B.3.3 Referencias, periodo, orden de compra y contingencia
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
referencia.factura | texto | Factura que corrige la nota (notas con referencia) | SETT1 |
referencia.fecha | fecha | Fecha de esa factura | 13/09/2026 |
referencia.cufe | texto | CUFE de esa factura | f9e8… |
referencia.concepto | texto | Concepto de corrección con su descripción oficial | 1 - Devolución parcial de los bienes y/o no aceptación parcial del servicio |
referencia.codigoConcepto | texto | Código del concepto | 1 |
referencia.descripcionConcepto | texto | Descripción del concepto | Devolución parcial de los bienes y/o no aceptación parcial del servicio |
documento.periodo | texto | Periodo facturado | 01/08/2026 00:00:00 a 31/08/2026 23:59:59 |
documento.periodoInicio | fecha y hora | Inicio del periodo | 01/08/2026 00:00:00 |
documento.periodoFin | fecha y hora | Fin del periodo | 31/08/2026 23:59:59 |
documento.ordenCompra | texto | Número de la orden de compra | OC-4410 |
documento.fechaOrdenCompra | fecha | Fecha de la orden de compra | 01/09/2026 |
documento.facturaContingencia | texto | Consecutivo de la factura de contingencia | CT-0012 |
documento.fechaFacturaContingencia | fecha | Fecha de la factura de contingencia | 10/09/2026 |
B.3.4 Numeración y proveedor tecnológico
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
resolucion.numero | texto | Número de la resolución de la DIAN | 18760000001 |
resolucion.prefijo | texto | Prefijo | SETT |
resolucion.vigenciaDesde | fecha | Inicio de la vigencia | 19/01/2019 |
resolucion.vigenciaHasta | fecha | Fin de la vigencia | 19/01/2030 |
resolucion.consecutivoDesde | texto | Inicio del rango, con prefijo | SETT1 |
resolucion.consecutivoHasta | texto | Fin del rango, con prefijo | SETT5000000 |
resolucion.texto | texto | Texto de la autorización. En notas: Numeración del … al … | Autorización de numeración DIAN No. 18760000001 vigente del 19/01/2019 al 19/01/2030. Rango autorizado del SETT1 al SETT5000000 |
proveedorTecnologico.razonSocial | texto | Razón social del proveedor tecnológico (Kiai) | KIAI S.A.S |
proveedorTecnologico.nit | texto | NIT con DV | 901337751-9 |
proveedorTecnologico.nombreSoftware | texto | Nombre del software | Kiai Facturación |
proveedorTecnologico.texto | texto | Leyenda lista para el pie | Software: Kiai Facturación. Proveedor tecnológico: KIAI S.A.S NIT 901337751-9 |
B.3.5 Emisor y adquiriente
emisor.* es siempre tu empresa, que expide el documento, y adquiriente.* es el comprador. Las mismas 22
variables existen con los dos prefijos: emisor.<campo> y adquiriente.<campo>.
| Campo | Tipo | Descripción | Ejemplo |
|---|---|---|---|
rol | texto | Emisor o Adquiriente | Adquiriente |
razonSocial | texto | Razón social; en persona natural sin razón social, nombres y apellidos | IPS Cliente Demo SAS |
nombres | texto | Nombres (persona natural) | María Fernanda |
apellidos | texto | Apellidos (persona natural) | Gómez Ruiz |
tipoDocumento | texto | Nombre del tipo de documento de identidad | NIT · Cédula de ciudadanía |
codigoTipoDocumento | texto | Código DIAN del tipo de documento | 31 · 13 |
numeroDocumento | texto | Número sin dígito de verificación | 900123456 |
dv | texto | Dígito de verificación | 8 |
nit | texto | Número con DV, si lo tiene | 900123456-8 |
documento | texto | Tipo y número juntos | NIT 900123456-8 · Cédula de ciudadanía 1020304050 |
direccion | texto | Dirección | Calle 10 # 20-30 Of. 301 |
municipio | texto | Municipio | Medellín |
departamento | texto | Departamento | Antioquia |
ciudad | texto | Municipio y departamento | Medellín - Antioquia |
codigoPostal | texto | Código postal | 050021 |
pais | texto | Código del país | CO |
telefono | texto | Teléfono | 6044441234 |
correo | texto | Correos sin repetidos, separados por ; | cuentas@tuempresa.co; tesoreria@tuempresa.co |
regimen | texto | Régimen con su nombre | 48 - Responsable de IVA |
codigoRegimen | texto | Código del régimen | 48 |
responsabilidades | texto | Responsabilidades fiscales con su nombre | O-13 Gran contribuyente; O-15 Autorretenedor |
codigosResponsabilidades | texto | Solo los códigos (R-99-PN si no tiene ninguna) | O-13;O-15 |
B.3.6 Totales
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
totales.valorBruto | monto | Suma del valor de las líneas, neto de sus descuentos y cargos | 57.401,00 |
totales.baseGravable | monto | Base gravable | 57.401,00 |
totales.totalImpuestos | monto | Suma de los impuestos | 7.870,08 |
totales.valorConImpuestos | monto | Valor bruto más impuestos | 65.271,08 |
totales.totalIva | monto | Total de IVA (tributo 01) | 5.206,00 |
totales.totalInc | monto | Total de INC (tributo 04; no incluye el INC de bolsas, 22) | 2.400,08 |
totales.totalIca | monto | Total de ICA (tributo 03) | 0,00 |
totales.totalDescuentos | monto | Descuentos globales (de encabezado) | 1.000,00 |
totales.totalCargos | monto | Cargos globales (de encabezado) | 0,00 |
totales.totalAnticipos | monto | Anticipos o recaudos del sector salud | 16.500,00 |
totales.totalRetenciones | monto | Retenciones. Son informativas: no se restan del total a pagar | 935,82 |
totales.ajusteAlPeso | monto | Siempre 0,00 en la API v1 | 0,00 |
totales.totalAPagar | monto | Total a pagar (en notas, el total de la nota) | 64.271,08 |
totales.valorEnLetras | texto | Total a pagar en letras. En pesos termina en M/CTE; en otra moneda usa su código ISO | SESENTA Y CUATRO MIL DOSCIENTOS SETENTA Y UN PESOS CON 08/100 M/CTE |
B.3.7 Sector salud (Resolución 948 de 2026)
Salen vacías si el documento no es del sector salud. Una nota que corrige una factura de salud hereda estas variables (sin recaudos). Los datos del paciente no se exponen: van en el RIPS.
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
salud.tipoOperacion | texto | Tipo de operación de salud con su nombre | SS-CUFE - Acreditación de recaudos con referencia a factura electrónica |
salud.codigoTipoOperacion | texto | Código | SS-CUFE |
salud.codigoPrestador | texto | Código de habilitación del prestador (REPS) | 0500102345 |
salud.modalidadPago | texto | Modalidad de pago con su descripción | 04 - Pago por evento. |
salud.coberturaPlan | texto | Cobertura con su descripción | 16 - Plan de beneficios en salud financiado con UPC Regimen Contributivo |
salud.numeroContrato | texto | Número de contrato | CT-2026-118 |
salud.numeroPoliza | texto | Número de póliza | POL-88231 |
salud.facturaSinContrato | texto | Motivo de factura sin contrato, con su descripción | 01 - ATENCION DE URGENCIAS |
salud.periodoAtencion | texto | Periodo de atención | 01/09/2026 07:30:00 a 10/09/2026 18:00:00 |
B.3.8 Listas (se recorren con [[FOR:…]])
| Lista | Dónde se usa | Qué contiene |
|---|---|---|
lineas | Documento | Una por línea del detalle |
impuestos | Documento y dentro de cada línea | En el documento: el resumen por tributo y tarifa. Dentro de lineas: los de esa línea |
retenciones | Documento | Retenciones por tributo y tarifa (informativas) |
cargosDescuentos | Documento y dentro de cada línea | En el documento: los globales. Dentro de lineas: los de esa línea |
recaudos | Documento | Recaudos del sector salud, uno por concepto (copago, cuota moderadora…) |
anticipos | Documento | Anticipos informados |
mediosPago | Documento | Medios de pago (vacía en notas) |
notasTotales | Documento | Aclaraciones de lo que se muestra pero no resta del total |
En cualquier FOR también existe [[indice]] (o [[Index]]): la posición desde 1.
Elemento de lineas
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
numero | texto | Número de la línea | 1 |
codigo | texto | Código del producto o servicio | 43231500 |
tipoCodificacion | texto | Código del estándar de codificación | 001 · 999 |
descripcion | texto | Descripción | Consulta médica general |
observacion | texto | Observación de la línea | Incluye materiales |
marca | texto | Marca | Acme |
modelo | texto | Modelo | X-200 |
cantidad | decimal | Cantidad | 3,00 |
unidad | texto | Código de la unidad de medida | 94 |
precioUnitario | monto | Precio unitario | 10.000,00 |
precioReferencia | monto | Precio de referencia; solo si la línea es obsequio | 12.000,00 |
esObsequio | texto | Sí o No | No |
valorBruto | monto | Cantidad × precio unitario, antes de descuentos y cargos | 30.000,00 |
totalDescuentos | monto | Descuentos de la línea | 3.000,00 |
totalCargos | monto | Cargos de la línea | 0,00 |
cargosDescuentosTexto | texto | Descuentos y cargos de la línea, uno por renglón | -3.000,00 (10,00 %) |
valor | monto | Valor de la línea, neto de descuentos y cargos | 27.000,00 |
totalImpuestos | monto | Impuestos de la línea | 5.130,00 |
impuestosTexto | texto | Impuestos de la línea, uno por renglón | IVA 19,00 %: 5.130,00 |
total | monto | Valor más impuestos de la línea | 32.130,00 |
Condición del elemento: [[IF:obsequio]] (la línea es obsequio). Listas anidadas: impuestos y cargosDescuentos.
Elemento de impuestos y de retenciones
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
codigo | texto | Código DIAN del tributo | 01 |
nombre | texto | Nombre del tributo | IVA · ReteICA |
tarifa | texto | Tarifa con su unidad: porcentaje, por mil o valor por unidad | 19,00 % · 9,66 ‰ · 66,00 c/u |
base | monto | Base | 27.000,00 |
valor | monto | Valor | 5.130,00 |
etiqueta | texto | Nombre y tarifa | IVA 19,00 % · INC Bolsas 66,00 c/u |
Elemento de cargosDescuentos
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
tipo | texto | Descuento o Cargo | Descuento |
codigoRazon | texto | Código de la razón | 00 |
descripcion | texto | Descripción | Pronto pago |
porcentaje | decimal | Porcentaje | 10,00 |
porcentajeTexto | texto | Porcentaje con %; vacío si es cero | 10,00 % |
base | monto | Base | 30.000,00 |
valor | monto | Valor, siempre positivo | 3.000,00 |
valorConSigno | monto | Negativo en descuentos | -3.000,00 |
texto | texto | Valor con signo y porcentaje | -3.000,00 (10,00 %) |
Condición del elemento: [[IF:esDescuento]].
Elemento de recaudos (sector salud)
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
concepto | texto | Código del concepto | 01 |
nombre | texto | Copago, Cuota moderadora, Pagos compartidos, Anticipo | Copago |
etiqueta | texto | Nombre; con (informativo) cuando no resta del total | Copago (informativo) |
fechaRecibido | fecha | Fecha del primer recaudo del concepto | 05/09/2026 |
valor | monto | Suma del concepto | 12.000,00 |
valorEnTotales | monto | Con signo menos cuando resta del total | -12.000,00 |
Elemento de anticipos
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
identificacion | texto | Identificación del anticipo | ANT-01 |
fechaRecibido | fecha | Fecha de recibido | 01/09/2026 |
valor | monto | Valor | 50.000,00 |
Elemento de mediosPago
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
codigo | texto | Código DIAN del medio | 47 |
nombre | texto | Nombre del medio (Otro si el código no está en el catálogo de Kiai) | Transferencia bancaria |
referencia | texto | Referencia, solo si la informaste | TRF-001 |
valor | monto | Total a pagar cuando hay un único medio; vacío con varios | 64.271,08 |
Elemento de notasTotales
| Variable | Tipo | Descripción | Ejemplo |
|---|---|---|---|
texto | texto | Aclaración | Las retenciones son informativas: no se descuentan del total a pagar. |
B.3.9 Condiciones (para [[IF:…]] y [[IFNOT:…]])
No se imprimen: solo sirven para mostrar u ocultar bloques.
| Condición | Activa cuando |
|---|---|
factura | Es factura de venta (01 a 04) |
nota | Es nota crédito o débito |
notaCredito · notaDebito | Nota crédito (91) · nota débito (92) |
autorizacionDian | No es nota (lleva autorización de numeración DIAN) |
pendienteValidacion | El documento aún no tiene fecha de validación DIAN |
referencia | Es una nota que referencia una factura |
notaSinReferencia | Es una nota sin referencia a factura |
documento.referenciasFactura | Hay periodo facturado (fuera de salud), orden de compra o factura de contingencia |
credito | Forma de pago crédito (nunca en notas) |
mediosPago.conValor | Hay un único medio de pago (su valor es el total) |
salud | Es un documento del sector salud |
salud.recaudosInformativos | Salud con recaudos que se informan pero no restan del total |
totales.hayDescuentos · totales.hayCargos | Hay descuentos · cargos globales distintos de cero |
totales.hayAnticiposInformativos | Hay anticipos informativos (fuera de salud) |
obsequio | (solo dentro de [[FOR:lineas]]) La línea es obsequio |
esDescuento | (solo dentro de [[FOR:cargosDescuentos]]) Es descuento, no cargo |
IF también acepta una lista (verdadero si tiene elementos, p. ej. [[IF:retenciones]]) o una variable
(verdadero si no está vacía, p. ej. [[IF:documento.logo]]).
B.4 Ejemplo completo
Encabezado con logo, comprador, tabla de líneas con sus impuestos, totales, QR, CUFE/CUDE y pie de página. Pasa la validación al guardarla: úsala como punto de partida.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>[[documento.titulo]] [[documento.numero]]</title>
<style>
body { font-family: "Liberation Sans", Arial, sans-serif; font-size: 9pt; color: #222; margin: 0; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 3px 4px; vertical-align: top; }
.detalle th { background: #f0f0f0; text-align: left; }
.detalle td { border-bottom: 1px solid #ddd; }
.num { text-align: right; }
.qr img { width: 30mm; height: 30mm; }
.cufe { word-break: break-all; font-size: 7pt; }
</style>
</head>
<body>
<!-- Encabezado: emisor y datos del documento -->
<table>
<tr>
[[IF:documento.logo]]<td style="width: 35mm;"><img src="[[documento.logo]]" alt="Logo" style="max-width: 35mm; max-height: 20mm;"></td>[[END:IF:documento.logo]]
<td>
<strong>[[emisor.razonSocial]]</strong><br>
[[emisor.documento]]<br>
[[IF:emisor.direccion]][[emisor.direccion]] · [[emisor.ciudad]]<br>[[END:IF:emisor.direccion]]
[[IF:emisor.regimen]]Régimen: [[emisor.regimen]][[END:IF:emisor.regimen]]
</td>
<td class="num">
<strong>[[documento.titulo]]</strong><br>
No. [[documento.numero]]<br>
Emisión: [[documento.fechaEmision]]<br>
[[IF:documento.formaPago]]Forma de pago: [[documento.formaPago]]<br>[[END:IF:documento.formaPago]]
[[IF:credito]]Vence: [[documento.fechaVencimiento]][[END:IF:credito]]
</td>
</tr>
</table>
<!-- Comprador -->
<p><strong>[[adquiriente.rol]]:</strong> [[adquiriente.razonSocial]] · [[adquiriente.documento]] · [[adquiriente.ciudad]]</p>
[[IF:referencia]]
<p>Corrige la factura [[referencia.factura]] del [[referencia.fecha]]. Concepto: [[referencia.concepto]]</p>
[[END:IF:referencia]]
<!-- Detalle: un renglón por línea, con sus impuestos anidados -->
<table class="detalle">
<tr>
<th>#</th><th>Descripción</th><th class="num">Cant.</th><th class="num">Precio unit.</th>
<th class="num">Valor</th><th>Impuestos</th><th class="num">Total</th>
</tr>
[[FOR:lineas]]
<tr>
<td>[[indice]]</td>
<td>[[descripcion]][[IF:obsequio]] (obsequio)[[END:IF:obsequio]]</td>
<td class="num">[[cantidad]] [[unidad]]</td>
<td class="num">[[precioUnitario]]</td>
<td class="num">[[valor]]</td>
<td>[[FOR:impuestos]][[etiqueta]]: [[valor]]<br>[[END:impuestos]]</td>
<td class="num">[[total]]</td>
</tr>
[[END:lineas]]
</table>
<!-- Totales -->
<table style="width: 60%; margin-left: auto;">
<tr><td>Valor bruto</td><td class="num">[[totales.valorBruto]]</td></tr>
[[FOR:impuestos]]<tr><td>[[etiqueta]]</td><td class="num">[[valor]]</td></tr>[[END:impuestos]]
[[IF:totales.hayDescuentos]]<tr><td>Descuentos</td><td class="num">-[[totales.totalDescuentos]]</td></tr>[[END:IF:totales.hayDescuentos]]
[[IF:totales.hayCargos]]<tr><td>Cargos</td><td class="num">[[totales.totalCargos]]</td></tr>[[END:IF:totales.hayCargos]]
<tr><td><strong>Total a pagar</strong></td><td class="num"><strong>[[documento.moneda]] [[totales.totalAPagar]]</strong></td></tr>
</table>
<p>Son: [[totales.valorEnLetras]]</p>
[[FOR:notasTotales]]<p><em>[[texto]]</em></p>[[END:notasTotales]]
<!-- QR y CUFE/CUDE -->
<table>
<tr>
<td class="qr" style="width: 32mm;"><img src="[[documento.codigoQr]]" alt="QR de consulta DIAN"></td>
<td>
<strong>[[documento.etiquetaIdentificador]]:</strong>
<div class="cufe">[[documento.identificador]]</div>
Validación DIAN: [[documento.fechaValidacion]]
</td>
</tr>
</table>
[[IF:documento.leyendaLegal]]<p style="font-size: 7pt;">[[documento.leyendaLegal]]</p>[[END:IF:documento.leyendaLegal]]
<template id="pie-pagina">
<div style="width: 100%; padding: 0 10mm; font-family: Arial, sans-serif; font-size: 7pt; color: #555;">
[[resolucion.texto]]<br>
[[proveedorTecnologico.texto]] · Página <span class="pageNumber"></span> de <span class="totalPages"></span>
</div>
</template>
</body>
</html>
Notas del ejemplo:
- En la tabla de totales,
[[FOR:impuestos]]está fuera delineas: recorre el resumen del documento. En el detalle, el mismo[[FOR:impuestos]]recorre los de cada línea. [[totales.totalDescuentos]]llega positivo; el signo-lo pone la plantilla.- El pie usa estilos en línea porque no hereda el
<style>del documento.
B.5 Preguntas frecuentes
- ¿Puedo partir de la plantilla de Kiai? La API no la expone: arma la tuya a partir del ejemplo de B.4.
- Creé la plantilla y el PDF de una factura vieja no cambió. Es lo esperado: cada documento se imprime con la plantilla vigente cuando se emitió. Emite un documento nuevo para verla.
- Escribí una variable mal y la plantilla se guardó. Revisa que el marcador no tenga espacios
(
[[ documento.numero ]]no es un marcador y se imprime tal cual). - Mi imagen no aparece en el PDF. Al imprimir no se descarga nada de internet: embébela como
data:en un atributosrc, no dentro de<style>(ver B.2.3).
Anexo C — Diccionario de campos
Este anexo lista, esquema por esquema, cada campo que envías o recibes en la API v1 de Facturación: su tipo, su longitud o formato y si es obligatorio. Cada límite sale del contrato OpenAPI, de las validaciones de la API o de lo que admite el almacenamiento; cuando esas fuentes difieren, aquí ves el más estricto, así que si lo respetas no tendrás sorpresas. Primero va lo que envías (emisor, comprador, factura, nota y sus piezas) y después lo que recibes.
Convenciones
| Tipo | Qué significa |
|---|---|
texto | Cadena JSON. La longitud se cuenta en caracteres. |
entero | Número sin decimales. Los consecutivos y rangos de numeración son de 64 bits. |
decimal (2) | Número JSON con máximo 2 decimales. En los montos, hasta 17 dígitos enteros. |
decimal (6) | Número JSON con máximo 6 decimales (cantidades, unidades de un impuesto nominal, TRM). |
fecha | Texto AAAA-MM-DD, p. ej. 2026-09-25. |
fecha y hora | Texto ISO 8601 con zona, p. ej. 2026-09-01T07:30:00-05:00 o 2026-09-01T12:30:00Z. |
uuid | Identificador de 36 caracteres, p. ej. 3fa85f64-5717-4562-b3fc-2c963f66afa6. |
booleano | true o false. |
lista | Arreglo JSON; la columna Tipo dice de qué son sus elementos. |
objeto | Objeto JSON; su forma está en la sección enlazada. |
En la columna Obligatorio de lo que envías, Sí es siempre, No es que puedes omitirlo y Condicional es que depende de lo que diga la columna Notas. En lo que recibes, Sí significa que el campo siempre trae valor y No que puede venir en null.
Dos reglas generales: la API responde 400 si el cuerpo trae una propiedad que no está en el esquema, y las listas cargosDescuentos, impuestos y retenciones no admiten null (omítelas o envía []).
C.1 Emisor (EmisorInput)
Cuerpo del alta del emisor. El id del emisor no viaja: es el companyId de tu token.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipoDocumento | texto | 1 o 2 dígitos | Sí | Código DIAN del tipo de documento, p. ej. 31 (NIT). |
numeroDocumento | texto | máx. 20; solo dígitos, letras y guion | Sí | Sin el dígito de verificación. |
dv | texto | exactamente 1 dígito | Condicional | Obligatorio con NIT (tipoDocumento = 31); se valida con el algoritmo mod-11 de la DIAN. |
tipoOrganizacion | texto | 1 o 2 | Sí | 1 persona jurídica, 2 persona natural. |
razonSocial | texto | máx. 450 | Sí | |
nombres | texto | máx. 200 | No | |
apellidos | texto | máx. 200 | No | |
regimen | texto | 1 a 3 dígitos | Sí | Código DIAN del régimen, p. ej. 48 responsable de IVA, 49 no responsable. |
responsabilidadesFiscales | texto | máx. 120 | No | Códigos DIAN separados por ;, p. ej. O-13;O-15. |
direccion | texto | máx. 300 | Sí | |
codigoMunicipio | texto | exactamente 5 dígitos (código DANE) | Sí | P. ej. 11001 (Bogotá). |
municipio | texto | máx. 120 | Sí | |
departamento | texto | máx. 120 | Sí | |
codigoPostal | texto | máx. 10 | No | |
codigoPais | texto | exactamente 2 (ISO 3166-1 alfa-2) | No | Por defecto CO. |
correo | texto | correo válido; máx. 254 | Sí | |
telefono | texto | máx. 30 | No | |
testSetId | texto | máx. 100 | No | Id del set de pruebas que te asignó la DIAN para la habilitación. |
ambiente | texto | solo habilitacion | No | Por defecto habilitacion. produccion responde 400: el paso a producción lo hace Kiai cuando la DIAN te habilita. |
activo | booleano | true o false | No | Por defecto true. Omítelo: en false el emisor nacería dado de baja. |
C.2 Adquiriente del directorio (AdquirienteInput)
Cuerpo para registrar o editar un comprador en tu directorio. No envíes emisorId: el dueño es la empresa de tu token.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipoDocumento | texto | 1 o 2 dígitos | Sí | Código DIAN, p. ej. 31 NIT, 13 cédula de ciudadanía. |
numeroDocumento | texto | máx. 20; solo dígitos, letras y guion | Sí | Sin el dígito de verificación. |
dv | texto | exactamente 1 dígito | Condicional | Obligatorio con NIT (31); se valida con mod-11. |
tipoOrganizacion | texto | 1 o 2 | Sí | 1 persona jurídica, 2 persona natural. |
razonSocial | texto | máx. 450 | Sí | |
nombres | texto | máx. 200 | No | |
apellidos | texto | máx. 200 | No | |
regimen | texto | 1 a 3 dígitos | Sí | P. ej. 48 responsable de IVA, 49 no responsable. |
responsabilidadesFiscales | texto | máx. 120 | No | Códigos DIAN separados por ;, p. ej. R-99-PN. |
direccion | texto | máx. 300 | Sí | |
codigoMunicipio | texto | exactamente 5 dígitos (código DANE) | Sí | |
municipio | texto | máx. 120 | Sí | |
departamento | texto | máx. 120 | Sí | |
codigoPostal | texto | máx. 10 | No | |
codigoPais | texto | exactamente 2 (ISO 3166-1 alfa-2) | No | Por defecto CO. |
correo | texto | correo válido; máx. 254 | Sí | |
telefono | texto | máx. 30 | No | |
esNacional | booleano | true o false | No | Por defecto true. |
esResponsableIva | booleano | true o false | No | Por defecto false. |
C.3 Comprador en el documento (AdquirienteDocumento)
Es el adquiriente que viaja dentro de la factura y de la nota sin referencia. No tienes que registrarlo antes: la API lo crea o actualiza en tu directorio y el documento guarda una copia que no cambia aunque edites el directorio después. Tiene dos formas:
- Comprador identificado (
consumidorFinalenfalseu omitido): lleva los mismos campos y límites de C.2, y los obligatorios de allí también lo son aquí (por eso figuran como Condicional). - Consumidor final:
{ "consumidorFinal": true }basta. La API completa tipo de documento13, número222222222222, nombre “Consumidor final” y responsabilidadR-99-PN. Puedes añadircorreo,telefonoy la dirección; si informasdireccion,codigoMunicipioocodigoPostal, la dirección debe venir completa (direccion,codigoMunicipio,municipio,departamentoycodigoPais).
En la respuesta de la factura o la nota, este objeto trae todos sus campos.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
consumidorFinal | booleano | true o false | No | Por defecto false. |
tipoDocumento | texto | 1 o 2 dígitos | Condicional | Obligatorio para un comprador identificado. Con consumidor final, si lo envías, debe ser 13. |
numeroDocumento | texto | máx. 20; solo dígitos, letras y guion | Condicional | Obligatorio para un comprador identificado. Con consumidor final, si lo envías, debe ser 222222222222. |
dv | texto | exactamente 1 dígito | Condicional | Obligatorio con NIT (31); se valida con mod-11. |
tipoOrganizacion | texto | 1 o 2 | Condicional | Obligatorio para un comprador identificado. |
razonSocial | texto | máx. 450 | Condicional | Obligatoria para un comprador identificado; con consumidor final la fija la API. |
nombres | texto | máx. 200 | No | |
apellidos | texto | máx. 200 | No | |
regimen | texto | 1 a 3 dígitos | Condicional | Obligatorio para un comprador identificado. |
responsabilidadesFiscales | texto | máx. 120 | No | Códigos DIAN separados por ;. |
direccion | texto | máx. 300 | Condicional | Obligatoria para un comprador identificado; con consumidor final, solo si informas la dirección. |
codigoMunicipio | texto | exactamente 5 dígitos (código DANE) | Condicional | Igual que direccion. |
municipio | texto | máx. 120 | Condicional | Igual que direccion. |
departamento | texto | máx. 120 | Condicional | Igual que direccion. |
codigoPostal | texto | máx. 10 | No | Con consumidor final, enviarlo exige la dirección completa. |
codigoPais | texto | exactamente 2 (ISO 3166-1 alfa-2) | No | Por defecto CO. |
correo | texto | correo válido; máx. 254 | Condicional | Obligatorio para un comprador identificado; opcional con consumidor final. |
telefono | texto | máx. 30 | No | |
esNacional | booleano | true o false | No | Por defecto true. |
esResponsableIva | booleano | true o false | No | Por defecto false. |
C.4 Factura (FacturaInput)
Cuerpo con el que creas o editas una factura en borrador. No envíes emisorId: el emisor es la empresa de tu token.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
adquiriente | objeto (C.3) | — | Sí | Comprador con sus datos o { "consumidorFinal": true }. |
resolucionId | uuid | 36 caracteres | Sí | Id de una numeración autorizada de tipo factura (C.26). |
fecha | fecha | AAAA-MM-DD | No | Por defecto, hoy. |
moneda | texto | exactamente 3 (código ISO 4217) | No | Por defecto COP. La factura del sector salud va en COP. |
trm | decimal (6) | mayor que 0; hasta 12 dígitos enteros | Condicional | Obligatoria si moneda no es COP (exportación). Pesos por unidad de la moneda. |
fechaTasa | fecha | AAAA-MM-DD | No | Fecha de la TRM; por defecto, la del documento. |
mandante | objeto (C.13) | — | No | Solo en la factura de mandato. No se admite con sectorSalud. |
anticipos | lista de C.14 | sin límite de elementos | No | Informativos: no restan del total. No se admiten con sectorSalud (allí van en recaudos). |
contingencia | objeto (C.15) | — | No | Solo en la factura de contingencia. |
periodo | objeto (C.16) | — | Condicional | Obligatorio con sectorSalud. |
sectorSalud | objeto (C.17) | — | No | Su presencia marca la factura como del sector salud. |
formaPago | texto | contado o credito | No | Por defecto contado. |
mediosPago | lista de C.11 | mínimo 1 elemento | Sí | |
fechaVencimiento | fecha | AAAA-MM-DD; no anterior a fecha | Condicional | Obligatoria si formaPago = credito. |
ordenReferencia | objeto (C.12) | — | No | Orden de compra o pedido. |
observaciones | texto | máx. 1000 | No | |
lineas | lista de C.6 | mínimo 1 elemento | Sí | |
cargosDescuentos | lista de C.9 | sin límite de elementos | No | De cabecera: no afectan la base gravable. |
retenciones | lista de C.8 | sin límite de elementos | No | De documento; no pueden repetir un tributo que ya informaste en las líneas. |
totales | objeto (C.10) | — | Sí | La API los verifica al centavo contra las líneas. |
C.5 Nota crédito o débito (NotaInput)
La nota usa el mismo detalle que la factura: líneas, cargos y descuentos, retenciones y totales. Con facturaId corriges una factura (operación 20 crédito o 30 débito); sin él, emites una nota sin referencia (22 o 32). Las notas no llevan anticipos.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipo | texto | credito o debito | Sí | |
tipoOperacion | texto | 20, 22, 30 o 32 | No | Se deduce de tipo y facturaId. Si lo envías, no puede contradecirlos. |
facturaId | uuid | 36 caracteres | Condicional | Factura aceptada por la DIAN que corriges. Obligatoria con referencia (20/30); no la envíes en 22/32. |
adquiriente | objeto (C.3) | — | Condicional | Obligatorio sin referencia (22/32). Con referencia no se envía: la nota toma el comprador de la factura. |
resolucionId | uuid | 36 caracteres | Sí | Numeración de tipo nota-credito o nota-debito (C.26). |
conceptoCorreccion | texto | crédito: 1 a 6; débito: 1 a 4 | Condicional | Obligatorio con referencia; no se envía sin ella. En crédito, 2 es anulación de la factura. |
periodo | objeto (C.16) | — | Condicional | Obligatorio sin referencia (22/32); opcional con referencia. |
fecha | fecha | AAAA-MM-DD | No | |
moneda | texto | exactamente 3 (código ISO 4217) | No | Por defecto COP. |
observaciones | texto | máx. 1000 | No | |
lineas | lista de C.6 | mínimo 1 elemento | Sí | |
cargosDescuentos | lista de C.9 | sin límite de elementos | No | De cabecera. |
retenciones | lista de C.8 | sin límite de elementos | No | Se guardan y se devuelven, pero no van en el XML de la nota. |
totales | objeto (C.10) | — | Sí | En crédito, totalAPagar no puede superar el saldo de la factura; con concepto 2 debe ser igual. |
C.6 Línea del documento (LineaDocumentoInput)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
numero | entero | mayor o igual a 1 | No | En todas las líneas o en ninguna, sin repetir. Por defecto, la posición. |
descripcion | texto | máx. 500 | Sí | |
codigo | texto | máx. 60 | Condicional | Obligatorio si esquemaCodigo es distinto de 999. |
esquemaCodigo | texto | 001 (UNSPSC), 010 (GTIN), 020 (partida arancelaria) o 999 | No | Por defecto 999 (estándar del contribuyente). |
unidadMedida | texto | código de la tabla 13.3.6 del Anexo Técnico | Sí | P. ej. 94 = unidad. |
cantidad | decimal (6) | mayor que 0; hasta 13 dígitos enteros | Sí | |
precioUnitario | decimal (2) | mayor o igual a 0 | Sí | 0 en una muestra gratis. |
esMuestraGratis | booleano | true o false | No | Por defecto false. Una muestra gratis no admite cargos ni descuentos. |
precioReferencia | decimal (2) | mayor que 0 | Condicional | Obligatorio en una muestra gratis; no se envía en las demás líneas. |
codigoPrecioReferencia | texto | 01, 02 o 03 (tabla 13.3.10) | Condicional | Igual que precioReferencia. |
cargosDescuentos | lista de C.9 | sin límite de elementos | No | En la línea sí afectan la base gravable. |
impuestos | lista de C.7 | sin tributos repetidos | No | Vacía = ítem excluido. |
retenciones | lista de C.8 | sin tributos repetidos | No | La ReteIVA (05) exige que la línea tenga IVA (01). |
valorTotal | decimal (2) | mayor o igual a 0 | Sí | round(cantidad × precioUnitario) − descuentos + cargos; 0 en una muestra gratis. |
C.7 Impuesto de línea (Impuesto)
El tributo (tabla 13.2.2) decide la forma. Porcentual — 01 IVA, 03 ICA (por mil), 04 INC, 08, 20, 21, 30, 35, 36: lleva tarifa sobre base. Nominal — 02, 22 (bolsas), 23, 24, 25, 26, 32, 33, 34 (IBUA): lleva valorUnitario por unidad y unidadMedidaBase.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tributo | texto | uno de los códigos de arriba | Sí | Las retenciones no van aquí: van en retenciones. |
base | decimal | porcentual: 2 decimales, mayor o igual a 0; nominal: hasta 6 decimales, mayor que 0 | Sí | Porcentual: la base gravable de la línea. Nominal: las unidades (la cantidad de la línea). |
tarifa | decimal (2) | IVA: 0, 5, 16 o 19; INC: 2, 4, 8 o 16; los demás: mayor o igual a 0, máx. 99999.99 | Condicional | Obligatoria en un impuesto porcentual; no se envía en uno nominal. |
valorUnitario | decimal (2) | mayor o igual a 0 | Condicional | Obligatorio en un impuesto nominal; no se envía en uno porcentual. |
unidadMedidaBase | texto | código de la tabla 13.3.6 | Condicional | Igual que valorUnitario. |
valor | decimal (2) | mayor o igual a 0 | Sí | Porcentual: round(base × tarifa / 100) (ICA: / 1000). Nominal: valorUnitario × base (IBUA: / 100). |
C.8 Retención (Retencion)
Va en la línea o en el documento. Es informativa: no cambia totalAPagar.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tributo | texto | 05 ReteIVA, 06 ReteRenta o 07 ReteICA | Sí | Sin repetir en la misma línea ni en el documento. |
base | decimal (2) | mayor o igual a 0 | Sí | En la línea, la base gravable; la ReteIVA se practica sobre el IVA de la línea. |
tarifa | decimal (2) | ReteIVA: 15 o 100; ReteRenta: 0.1, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 6, 7, 10, 11 o 20; ReteICA: mayor o igual a 0, máx. 99999.99 | Sí | La ReteICA va por mil. |
valor | decimal (2) | mayor o igual a 0 | Sí | round(base × tarifa / 100); en ReteICA, por mil. |
C.9 Cargo o descuento (CargoDescuento)
En una línea afecta la base gravable y su base es cantidad × precioUnitario; en la cabecera no afecta la base gravable.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
esCargo | booleano | true o false | Sí | true recargo, false descuento. |
codigoRazon | texto | descuento: 00 o 01; recargo: 02 o 03 (tabla 13.3.8) | Condicional | Obligatorio en un descuento; opcional en un recargo. |
descripcion | texto | máx. 500 | No | |
porcentaje | decimal (2) | de 0 a 100 | No | Si lo envías, valor = round(base × porcentaje / 100). |
base | decimal (2) | mayor o igual a 0 | Sí | |
valor | decimal (2) | mayor o igual a 0 | Sí | Un descuento no puede superar su base. |
C.10 Totales del documento (TotalesDocumento)
Los calculas tú y la API los verifica al centavo; si no cuadran, responde 400 con el valor esperado y el recibido en cada campo.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
valorBruto | decimal (2) | mayor o igual a 0 | Sí | Suma de valorTotal de las líneas. |
baseGravable | decimal (2) | mayor o igual a 0 | Sí | Suma, por línea, de la base de su primer impuesto porcentual. |
totalImpuestos | decimal (2) | mayor o igual a 0 | Sí | Suma de los impuestos de las líneas. |
valorConImpuestos | decimal (2) | mayor o igual a 0 | Sí | valorBruto + totalImpuestos. |
totalDescuentos | decimal (2) | mayor o igual a 0 | No | Por defecto 0. Suma de los descuentos de cabecera. |
totalCargos | decimal (2) | mayor o igual a 0 | No | Por defecto 0. Suma de los cargos de cabecera. |
totalAnticipos | decimal (2) | mayor o igual a 0 | No | Por defecto 0. Suma de los anticipos; en la factura de salud con recaudos, suma de los recaudos. En las notas, 0. |
totalRetenciones | decimal (2) | mayor o igual a 0 | No | Por defecto 0. Suma de las retenciones de líneas y documento (informativo). |
totalAPagar | decimal (2) | mayor o igual a 0 | Sí | valorConImpuestos − totalDescuentos + totalCargos; en la factura de salud de acreditación (SS-CUFE, SS-CUDE, SS-POS, SS-SNum), además resta los recaudos. |
C.11 Medio de pago (MedioPago)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
codigo | texto | código de la tabla 13.3.4.2 | Sí | P. ej. 10 efectivo, 42 consignación, 47 transferencia, 48/49 tarjeta, ZZZ acuerdo mutuo. |
referencia | texto | máx. 150 | No | Por defecto, el número del documento. |
C.12 Orden de referencia (OrdenReferencia)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
numero | texto | máx. 60 | Sí | Número de la orden de compra o pedido. |
fecha | fecha | AAAA-MM-DD | No | Por defecto, la del documento. |
C.13 Mandante (MandanteInput)
Mandante de una factura de mandato: el emisor factura por cuenta de él.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipoDocumento | texto | máx. 2 | No | Código DIAN; por defecto 31 (NIT). |
numeroDocumento | texto | máx. 20 | Sí | |
dv | texto | máx. 1 | No | Si aplica. |
razonSocial | texto | máx. 450 | No | |
codigoMandato | texto | máx. 20 | No | Código de mandato. |
C.14 Anticipo (AnticipoInput)
Pago recibido antes de la factura. Es informativo: no reduce el total ni cambia el CUFE.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
identificacion | texto | máx. 60 | No | Identificación del pago. |
valor | decimal (2) | mayor que 0 | Sí | |
fechaRecibido | fecha | AAAA-MM-DD | No | Por defecto, la del documento. |
fechaRealizado | fecha | AAAA-MM-DD | No | Por defecto, la del documento. |
C.15 Contingencia (ContingenciaInput)
Factura emitida por contingencia (fuera de línea).
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipo | texto | 03 o 04 | No | Por defecto 03. 03 contingencia del facturador, 04 contingencia DIAN. |
consecutivoReferencia | texto | máx. 60 | Condicional | Consecutivo del documento referenciado; obligatorio si tipo = 03. |
cufeReferencia | texto | máx. 200 | Condicional | CUFE o CUDE del documento referenciado; obligatorio si tipo = 03. |
fechaReferencia | fecha | AAAA-MM-DD | No | |
codigoTipoDocumentoReferencia | texto | máx. 2 | Condicional | Código DIAN del tipo del documento referenciado; obligatorio si tipo = 03. |
C.16 Periodo del documento (PeriodoDocumentoInput)
Fecha y hora del primer y del último servicio.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
fechaInicio | fecha y hora | ISO 8601 con zona | Sí | P. ej. 2026-09-01T07:30:00-05:00. |
fechaFin | fecha y hora | ISO 8601 con zona; no anterior a fechaInicio | Sí |
C.17 Sector salud (SectorSaludInput)
Factura del sector salud según la Resolución 948 de 2026 del Ministerio de Salud. Los datos del paciente no van aquí: van en el RIPS. Con este bloque la factura exige periodo y moneda COP, y no admite anticipos ni mandante.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipoOperacion | texto | SS-CUFE, SS-CUDE, SS-POS, SS-SNum, SS-Recaudo, SS-Reporte o SS-SinAporte | Sí | |
codigoPrestador | texto | exactamente 10 dígitos (código REPS) | Condicional | Obligatorio salvo en SS-Recaudo, donde no se envía. |
modalidadPago | texto | 01 a 05 | Condicional | Igual que codigoPrestador. |
coberturaPlan | texto | 02 a 17 | Condicional | Igual que codigoPrestador. El 01 está inactivo. |
numeroContrato | texto | máx. 64 | Condicional | Obligatorio si hay contrato. Excluyente con numeroPoliza. No se envía en SS-Recaudo. |
numeroPoliza | texto | máx. 50 | No | SOAT o plan voluntario. Excluyente con numeroContrato. No se envía en SS-Recaudo. |
facturaSinContrato | texto | 01 a 07 | Condicional | Obligatorio sin numeroContrato y prohibido con él. No se envía en SS-Recaudo. |
recaudos | lista de C.18 | de 1 a 4 elementos, un concepto por elemento | Condicional | Obligatorio en SS-CUFE, SS-CUDE, SS-POS, SS-SNum y SS-Reporte; no se envía en SS-Recaudo ni en SS-SinAporte. La suma no puede superar el total de la factura. |
C.18 Recaudo del sector salud (RecaudoSaludInput)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
concepto | texto | 01 a 05 | Sí | 01 copago, 02 cuota moderadora, 03 pagos compartidos en planes voluntarios, 04 anticipo, 05 no aplica (va solo). |
valor | decimal (2) | mayor que 0; exactamente 0 con el concepto 05 | Sí | Sumatoria del concepto. |
fechaRecibido | fecha | AAAA-MM-DD | Sí | Fecha en que recibiste el recaudo. |
C.19 Consecutivo siguiente (ConsecutivoSiguienteInput)
Cuerpo de PUT /numeraciones-autorizadas/{id}/consecutivo-siguiente. Sirve, por ejemplo, si vienes de otro proveedor y ya usaste números del rango.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
consecutivoSiguiente | entero | mayor o igual a 1 y dentro del rango de la numeración | Sí | Debe ser mayor que el último número emitido con la numeración; si no, 409. |
C.20 Suscripción a webhooks (WebhookSuscripcionInput)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
url | texto | URL absoluta https://; máx. 500 | Sí | Tu receptor de eventos. |
secret | texto | de 16 a 200 | Sí | Secreto para verificar la firma HMAC. La API nunca lo devuelve. |
eventos | texto | máx. 500 | No | Por defecto * (todos). Tipos separados por ;, p. ej. documento.transmision.aceptada;documento.transmision.rechazada. |
C.21 Notificación por correo (NotificacionInput)
Cuerpo opcional al notificar una factura o una nota por correo. Sin cuerpo, el correo va al comprador del documento.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
destinatarios | lista de texto (correos) | sin límite de elementos; todos los correos del envío (con copiaA), unidos con ;, máx. 2000 caracteres | No | Reemplazan al correo del comprador. |
copiaA | lista de texto (correos) | sin límite de elementos; cuenta en el máx. de 2000 caracteres del envío | No | Se suman a los destinatarios; los repetidos se descartan sin distinguir mayúsculas. |
anexos | lista de C.22 | máx. 5 elementos y 5 MB en total | No | Viajan dentro del zip de entrega, en anexos.zip. |
C.22 Anexo de la notificación (AnexoInput)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
nombreArchivo | texto | sin límite; extensión pdf, png, jpg, jpeg, gif, bmp, zip, xls o xlsx | Sí | La extensión define el tipo del archivo. |
contenidoBase64 | texto | base64 válido y no vacío; 5 MB decodificados entre todos los anexos | Sí |
C.23 Factura (Factura)
Así viene una factura en las respuestas de la API.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipoDocumento | texto | factura-venta (el contrato también define documento-equivalente-pos, que esta guía no cubre) | Sí | Clase de documento, explícita. |
ambiente | texto | habilitacion o produccion | No | null mientras sea borrador. Es el ambiente con que se emitió y no cambia después. |
id | uuid | 36 caracteres | Sí | |
emisorId | uuid | 36 caracteres | Sí | La empresa de tu token. |
adquiriente | objeto (C.3) | — | Sí | Copia del comprador que usan el XML, el CUFE, el PDF y el correo. |
adquirienteId | uuid | 36 caracteres | Sí | Registro del comprador en tu directorio. |
resolucionId | uuid | 36 caracteres | Sí | |
estado | texto | borrador, emitida o anulada | Sí | anulada está reservado: hoy ninguna operación lleva ahí. Para anular, emite una nota crédito por el saldo (concepto 2). |
numero | texto | máx. 40 | No | Prefijo + consecutivo; se asigna al emitir. |
cufe | texto | máx. 200 | No | SHA-384; se calcula al emitir. |
fecha | fecha | AAAA-MM-DD | Sí | |
fechaEmision | fecha y hora | ISO 8601 con zona | No | |
moneda | texto | exactamente 3 | Sí | |
trm | decimal (6) | — | No | Solo en exportación. |
fechaTasa | fecha | AAAA-MM-DD | No | |
mandante | objeto (C.13) | — | No | Solo en la factura de mandato. |
anticipos | lista de C.14 | — | Sí | Puede venir vacía. |
contingencia | objeto (C.15) | — | No | Solo en la factura de contingencia. |
periodo | objeto (C.16) | — | No | En hora de Colombia. |
sectorSalud | objeto (C.17) | — | No | Solo en la factura del sector salud. |
formaPago | texto | contado o credito | Sí | |
mediosPago | lista de C.11 | — | Sí | |
fechaVencimiento | fecha | AAAA-MM-DD | No | |
ordenReferencia | objeto (C.12) | — | No | |
observaciones | texto | máx. 1000 | No | |
cargosDescuentos | lista de C.9 | — | Sí | Puede venir vacía. |
retenciones | lista de C.8 | — | Sí | Puede venir vacía. |
totales | objeto (C.10) | — | Sí | |
transmisionEstado | texto | pendiente, aceptada o rechazada | No | Estado del envío a la DIAN. |
transmisionMensaje | texto | máx. 2000 | No | Resultado de la transmisión, con los errores de la DIAN si los hubo. |
transmisionFecha | fecha y hora | ISO 8601 con zona | No | |
transmisionTrackId | texto | máx. 200 | No | CUFE que devolvió la DIAN. |
lineas | lista de C.6 | — | Sí | Lo guardado, idéntico a lo que va al XML; numero y esquemaCodigo siempre vienen resueltos. |
fechaCreacion | fecha y hora | ISO 8601 con zona | Sí | |
fechaActualizacion | fecha y hora | ISO 8601 con zona | Sí |
C.24 Nota (Nota)
Así viene una nota crédito o débito en las respuestas de la API.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
tipoDocumento | texto | nota-credito o nota-debito | Sí | Clase de documento, explícita. |
ambiente | texto | habilitacion o produccion | No | null mientras sea borrador. |
id | uuid | 36 caracteres | Sí | |
tipo | texto | credito o debito | Sí | |
tipoOperacion | texto | 20, 22, 30 o 32 | Sí | Siempre resuelto, aunque no lo hayas enviado. |
emisorId | uuid | 36 caracteres | Sí | La empresa de tu token. |
adquiriente | objeto (C.3) | — | Sí | El del cuerpo (22/32) o la copia de la factura corregida (20/30). |
adquirienteId | uuid | 36 caracteres | Sí | Registro del comprador en tu directorio. |
resolucionId | uuid | 36 caracteres | Sí | |
facturaId | uuid | 36 caracteres | No | null en notas sin referencia (22/32). |
facturaNumero | texto | máx. 40 | No | Número de la factura corregida. |
facturaCufe | texto | máx. 200 | No | CUFE de la factura corregida. |
conceptoCorreccion | texto | crédito: 1 a 6; débito: 1 a 4 | No | null en notas sin referencia. |
conceptoCorreccionDescripcion | texto | sin límite | No | Descripción oficial DIAN del concepto. |
periodo | objeto (C.16) | — | No | |
estado | texto | borrador o emitida | Sí | |
numero | texto | máx. 40 | No | Prefijo + consecutivo; se asigna al emitir. |
cude | texto | máx. 200 | No | SHA-384; se calcula al emitir. |
fecha | fecha | AAAA-MM-DD | Sí | |
fechaEmision | fecha y hora | ISO 8601 con zona | No | |
moneda | texto | exactamente 3 | Sí | |
observaciones | texto | máx. 1000 | No | |
cargosDescuentos | lista de C.9 | — | Sí | Puede venir vacía. |
retenciones | lista de C.8 | — | Sí | Puede venir vacía. |
totales | objeto (C.10) | — | Sí | |
transmisionEstado | texto | pendiente, aceptada o rechazada | No | Estado del envío a la DIAN. |
transmisionMensaje | texto | máx. 2000 | No | Resultado de la transmisión, con los errores de la DIAN si los hubo. |
transmisionFecha | fecha y hora | ISO 8601 con zona | No | |
transmisionTrackId | texto | máx. 200 | No | CUDE que devolvió la DIAN. |
lineas | lista de C.6 | — | Sí | numero y esquemaCodigo siempre vienen resueltos. |
fechaCreacion | fecha y hora | ISO 8601 con zona | Sí | |
fechaActualizacion | fecha y hora | ISO 8601 con zona | Sí |
C.25 Saldo de la factura (SaldoFactura)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
facturaId | uuid | 36 caracteres | Sí | |
totalFactura | decimal (2) | — | Sí | |
totalNotasCredito | decimal (2) | — | Sí | Notas crédito emitidas que la DIAN no rechazó. |
totalNotasDebito | decimal (2) | — | Sí | Informativo: no cambia el saldo. |
saldo | decimal (2) | — | Sí | totalFactura − totalNotasCredito: lo que aún puedes acreditar. |
C.26 Numeración autorizada (NumeracionAutorizada)
Prefijo, resolución, rango y vigencias los dicta la DIAN y no se editan; solo puedes mover el consecutivo siguiente (C.19).
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
id | uuid | 36 caracteres | Sí | Es el resolucionId que usas en facturas y notas. |
tipoDocumento | texto | factura, nota-credito, nota-debito o documento-soporte | Sí | documento-soporte no sirve para facturas ni notas. |
origen | texto | dian, set-pruebas o por-defecto | Sí | por-defecto: las numeraciones NC y ND de las notas. |
ambiente | texto | habilitacion o produccion | Sí | |
prefijo | texto | máx. 10 | No | P. ej. SETT. |
numeroResolucion | texto | máx. 50 | No | null en las numeraciones por defecto de las notas. |
rangoDesde | entero | — | No | null solo si está incompleta. |
rangoHasta | entero | — | No | En las notas por defecto, el mayor consecutivo que cabe en un número de 20 caracteres con prefijo. |
vigenciaDesde | fecha | AAAA-MM-DD | No | null en las notas por defecto (no vencen). |
vigenciaHasta | fecha | AAAA-MM-DD | No | |
consecutivoSiguiente | entero | — | No | Próximo consecutivo que tomará la emisión; null si está incompleta. |
ultimoConsecutivoEmitido | entero | — | No | null si aún no has emitido con ella. |
disponibles | entero | — | No | Consecutivos que quedan desde el siguiente. |
estado | texto | vigente, por-vencer, vencida, por-iniciar, incompleta o no-autorizada | Sí | por-vencer: 30 días o menos para el fin. Emitir con una no-autorizada responde 409. |
C.27 Error (ProblemDetails)
Todo error responde application/problem+json (RFC 9457). El contrato no marca campos obligatorios; las cuatro últimas filas son extensiones que solo aparecen en los casos indicados.
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
type | texto | sin límite | No | URI del tipo de error. |
title | texto | sin límite | No | Resumen del error. |
status | entero | código HTTP | No | |
detail | texto | sin límite | No | Explicación del caso concreto. |
instance | texto | sin límite | No | Ruta de la solicitud. |
traceId | texto | sin límite | No | Compártelo con soporte de Kiai. |
correlationId | texto | sin límite | No | Id de correlación de la solicitud (cabecera X-Correlation-Id). |
fuente | texto | kiai, dian o cliente | No | De quién es la falla: cliente (4xx), dian (la DIAN no respondió o rechazó) o kiai (500 o límite de solicitudes). |
errors | objeto | cada campo con su lista de mensajes | Condicional | Solo en los 400 de validación. |
servicioDian | texto | sin límite | Condicional | Operación de la DIAN involucrada, en el 503 y en los rechazos. |
reglasDian | lista de C.28 | — | Condicional | En el 422 de un documento rechazado y en el 409 de una consulta rechazada; puede venir vacía. |
numeroDocumento | texto | sin límite | Condicional | Número del documento rechazado; solo en el 422 de una transmisión. |
C.28 Regla DIAN (ReglaDian)
| Campo | Tipo | Longitud / formato | Obligatorio | Notas |
|---|---|---|---|---|
codigo | texto | sin límite | No | P. ej. FAJ43a; null si la DIAN no lo entregó en ese formato. |
mensaje | texto | sin límite | Sí | Descripción de la regla. |
tipo | texto | rechazo o notificacion | Sí | rechazo impide la validación; notificacion solo advierte. |
Soporte
Escríbenos a hola@kiai.co o por WhatsApp: por esos dos canales nos avisas que la DIAN aprobó tu set de pruebas y nos pides cambios en los datos de tu empresa.
Ante un incidente, envíanos el correlationId de la respuesta, el id y el número del documento, y la fecha y hora. Nunca nos envíes tu client_secret ni un token.