Documentación de la API de Wardian
Integra facturación electrónica, inventarios y más. Todas las peticiones se autentican con una API Key.
- Producción:
https://wardian.com.co/dist/api/ - Sandbox / pruebas:
https://sandbox.wardian.com.co/dist/api/ - Base URL (este entorno):
//wardian.com.co/dist/api/ - Formato: JSON
- Autenticación: header
X-API-Key(la key es distinta por entorno)
Autenticación
Envía tu API Key en el header X-API-Key en cada petición. Si falta o es inválida, la API responde 401 Unauthorized.
X-API-Key: wrd_tu_api_key_aqui
1 Generar tu API Key
En tu panel, ve a Software → Documentación → API Keys y presiona Generar API Key. Cópiala y guárdala; solo se muestra completa al crearla.
2 Primer request
Toda petición lleva el header X-API-Key. Para POST, envía el cuerpo en JSON con Content-Type: application/json. Las respuestas son JSON.
const res = await fetch("//wardian.com.co/dist/api/products/data_list.php", {
headers: { "X-API-Key": "wrd_tu_api_key_aqui" }
});
const data = await res.json();
const res = await fetch("//wardian.com.co/dist/api/third/create.php", {
method: "POST",
headers: {
"X-API-Key": "wrd_tu_api_key_aqui",
"Content-Type": "application/json"
},
body: JSON.stringify({ /* campos del endpoint */ })
});
const data = await res.json();
Códigos de error
| Código | Significado |
|---|---|
200 | OK |
400 | Petición inválida (faltan datos) |
401 | API Key inválida o ausente |
404 | Recurso no encontrado |
500 | Error interno |
Relaciones (catálogos)
Varios campos de productos, terceros y de la factura estándar no son valores libres: referencian catálogos (IDs de la DIAN / DANE). Obtén los valores válidos antes de crear.
Catálogos globales (datos de referencia, públicos). Usa table=all para traerlos todos.
| Campo que lo usa | table= | Descripción |
|---|---|---|
unit_measure_id | unidades | Unidades de medida DIAN |
standard_code_id | codigos_estandar | Códigos estándar de adopción |
tribute_id | tributos | Tributos (IVA, INC, etc.) |
identification_document_id | documentos_identidad | Tipos de documento (CC, NIT…) |
municipalities_code | municipios | Municipios (DANE) |
country_code | paises | Países |
| — | responsabilidades_iva, regimen_iva, autorretenciones, bancos | Otros catálogos disponibles |
const res = await fetch("//wardian.com.co/dist/api/references/tables.php?table=unidades");
const data = await res.json();
{
"success": true,
"data": [
{ "id": 70, "code": "94", "name": "unidad" }
]
}
Catálogos con endpoint propio (requieren X-API-Key)
| Campo | Endpoint | Notas |
|---|---|---|
categoria_id | api/settings/categories/list.php | Categorías del usuario |
numbering_range_id | api/numbering_ranges/list.php | Resoluciones / rangos de numeración |
payment_method_code | api/pos/list_payment_methods.php | Métodos de pago DIAN |
tax_rate | — | Valor directo: 0, 5, 8, 19 |
api/invoice/generate.php) usa estas mismas relaciones en su customer (identification_document_id, tribute_id, municipalities_code) y en cada ítem (unit_measure_id, standard_code_id, tax_rate), más numbering_range_id y payment_method_code.Productos
unit_measure_id, standard_code_id, tax_rate, categoria_id y tribute_id referencian catálogos. Consulta Relaciones / Catálogos para obtener los valores válidos. Son las mismas relaciones que usa la factura estándar.Lista los productos del usuario dueño de la API Key. Acepta paginación: ?pagina=1&search=texto.
{
"success": true,
"data": [
{ "id": 123, "code_reference": "SKU-001", "name": "Camiseta", "price": "50000.00", "tax_rate": "19.00" }
]
}
Crea un producto. Se envía como multipart/form-data (permite imágenes en files[]).
| Campo | Descripción | |
|---|---|---|
type | req | Tipo de producto |
code_reference | req | Código / referencia único |
name | req | Nombre del producto |
price | req | Precio de venta |
unit_measure_id | req | Unidad de medida (id DIAN) |
standard_code_id | req | Código estándar (id) |
tax_rate, cost_price, wholesale_price | opc | Impuesto, costo, precio mayorista |
categoria_id, details, requiere_stock, stock | opc | Categoría, detalles, control de stock |
contenido_presentacion, lote, fecha_vencimiento, cums_codigo | opc | Presentación, lote, vencimiento, CUMS |
files[] | opc | Imágenes del producto |
{
"type": "Producto",
"code_reference": "SKU-001",
"name": "Camiseta",
"price": "50000",
"unit_measure_id": "70",
"standard_code_id": "1",
"tax_rate": "19",
"categoria_id": "5"
}
{
"status": "success",
"message": "Producto registrado exitosamente",
"data": { "producto_id": 123, "code_reference": "SKU-001", "name": "Camiseta" }
}
multipart/form-data (campo files[]) en vez de JSON.Edita un producto existente. Mismos campos que crear, más:
| Campo | Descripción | |
|---|---|---|
id | req | ID del producto a editar |
{ "id": "123", "name": "Camiseta Premium", "price": "60000" }
{ "status": "success", "message": "Producto actualizado exitosamente" }
Terceros
Lista los terceros (clientes/proveedores) del usuario. Acepta ?pagina=1&search=texto.
{
"success": true,
"data": [
{ "id": 45, "identification": "901234567", "company_name": "ACME SAS", "email": "info@acme.co" }
]
}
Crea un tercero.
| Campo | Descripción | |
|---|---|---|
typeSelector | req | Tipo (Persona / Empresa) |
identification_document_id | req | Tipo de documento (id DIAN) |
identification | req | Número de identificación |
address, email, phone | req | Dirección, correo, teléfono |
tribute_id | req | Responsabilidad tributaria (id) |
dv, company_name, name, last_name, trade_name | opc | DV, razón social, nombres, nombre comercial |
ciiu, country_code, municipalities_code | opc | CIIU, país, municipio (DANE) |
{
"typeSelector": "Empresa",
"identification_document_id": "31",
"identification": "901234567",
"email": "info@acme.co",
"phone": "3001234567",
"address": "Calle 1 # 2-3",
"tribute_id": "21"
}
{
"status": "success",
"message": "Tercero creado exitosamente",
"data": { "tercero_id": 45, "name": "ACME SAS", "identification": "901234567", "email": "info@acme.co", "phone": "3001234567" }
}
Edita un tercero. Mismos campos que crear, más:
| Campo | Descripción | |
|---|---|---|
tercero_id | req | ID del tercero a editar |
{ "tercero_id": "45", "email": "nuevo@acme.co", "phone": "3009999999" }
{ "status": "success", "message": "Tercero actualizado exitosamente" }
Categorías
Lista las categorías de productos del usuario.
{
"success": true,
"data": [
{ "id": 5, "nombre": "Ropa", "descripcion": "Prendas de vestir", "color": "#CFFF00", "activo": 1 }
]
}
Crea una categoría de productos.
| Campo | Descripción | |
|---|---|---|
nombre | req | Nombre de la categoría |
descripcion, color, icono, activo | opc | Descripción, color, ícono, estado |
configuracion_puc_activa, cuenta_ingreso_venta, cuenta_costo_venta, cuenta_inventario, cuenta_compra… | opc | Cuentas contables PUC (si se activa la config) |
{ "nombre": "Ropa", "descripcion": "Prendas de vestir", "color": "#CFFF00", "activo": "1" }
{
"status": "success",
"message": "Categoría creada exitosamente",
"data": { "categoria_id": 5, "nombre": "Ropa", "descripcion": "Prendas de vestir", "color": "#CFFF00" }
}
Edita una categoría. Mismos campos que crear, más:
| Campo | Descripción | |
|---|---|---|
categoria_id | req | ID de la categoría a editar |
{ "categoria_id": "5", "nombre": "Ropa y Calzado" }
{ "status": "success", "message": "Categoría actualizada exitosamente" }
Crear factura estándar
id) y los productos, y ten a mano el numbering_range_id (ver Catálogos). La factura se envía a la DIAN y responde con el número y el CUFE.Genera una factura electrónica de venta estándar (multipart/form-data). Responde JSON con el resultado DIAN.
Cabecera
| Campo | Descripción | |
|---|---|---|
customer | req | ID del tercero (de api/third) |
numbering_range_id | req | Rango de numeración / resolución |
payment_form | req | 1 Contado · 2 Crédito |
payment_method_code | req | Método de pago (catálogo) |
observation | opc | Nota / observación |
payment_due_date | opc | Fecha de vencimiento (si crédito) |
company | opc | Nombre del emisor (para el correo/PDF) |
reference_code, cuenta_bancaria_id, cost_center_id | opc | Referencia, cuenta bancaria, centro de costo |
currency_code, currency_value, currency_date | opc | Moneda (por defecto COP) |
Ítems — products[]
| Campo | Descripción | |
|---|---|---|
id | req | ID del producto (de api/products) |
code_reference, name | req | Código y nombre del ítem |
quantity, price | req | Cantidad y precio unitario |
tax_rate | req | % IVA: 0, 5, 8, 19 |
unit_measure_id, standard_code_id, tribute_id | req | Catálogos (ver Relaciones) |
discount_rate, is_excluded | opc | % descuento, excluido de IVA (0/1) |
ret_fuente_rate, ret_iva_rate, ret_ica_rate | opc | Retenciones (si el cliente retiene) |
Ejemplo
{
"customer": "45",
"numbering_range_id": "502",
"payment_form": "1",
"payment_due_date": "2026-07-02",
"payment_method_code": "10",
"observation": "Gracias por su compra",
"products": [
{
"id": "123",
"code_reference": "SKU-001",
"name": "Camiseta",
"quantity": "2",
"price": "50000",
"tax_rate": "19",
"unit_measure_id": "70",
"standard_code_id": "1",
"tribute_id": "1",
"discount_rate": "0",
"is_excluded": "0"
}
]
}
{
"status": "success",
"message": "Factura creada exitosamente",
"data": {
"invoice_id": 47014,
"bill_number": "SETP990000227",
"cufe": "7b4a382a9a751cc88156a47f2eac24c7987353d1...",
"code_reference": "INV6a46c29683f15fdc6"
}
}
payment_due_date (una fecha, aun en contado) y en cada ítem discount_rate e is_excluded con valor (ej. 0). La DIAN rechaza campos vacíos/nulos con "Datos de la factura incompletos".Crear factura por mandato
operation_type 11) permite facturar por cuenta de terceros mandantes. Es idéntica a la factura estándar, pero cada ítem indica el mandante por el que se factura. La DIAN responde con el número y el CUFE.Genera una factura electrónica de venta por mandato. Responde JSON con el resultado DIAN.
Cabecera
Mismos campos que la factura estándar (customer, numbering_range_id, payment_form, payment_method_code, observation, payment_due_date, company, moneda…). El operation_type se fija internamente en 11.
Ítems — products[]
Mismos campos que la factura estándar (id, code_reference, name, quantity, price, tax_rate, catálogos, retenciones…) más los datos del mandante por el que se factura ese ítem:
| Campo | Descripción | |
|---|---|---|
mandate_identification | req | Identificación (NIT/CC) del tercero mandante. Si va vacío, el ítem se factura sin mandato. |
mandate_identification_document_id | req | Tipo de documento del mandante (catálogo documentos_identidad) |
mandate_dv | opc | Dígito de verificación del mandante (si aplica) |
Ejemplo
{
"customer": "45",
"numbering_range_id": "502",
"payment_form": "1",
"payment_due_date": "2026-07-02",
"payment_method_code": "10",
"observation": "Facturación por mandato",
"products": [
{
"id": "123",
"code_reference": "SKU-001",
"name": "Canon de arrendamiento",
"quantity": "1",
"price": "1000000",
"tax_rate": "0",
"unit_measure_id": "70",
"standard_code_id": "1",
"tribute_id": "1",
"discount_rate": "0",
"is_excluded": "0",
"mandate_identification": "901234567",
"mandate_identification_document_id": "31",
"mandate_dv": "8"
}
]
}
{
"status": "success",
"message": "Factura de mandato creada exitosamente",
"data": {
"invoice_id": 47021,
"code_reference": "INV6a46c29683f15fdc6",
"bill_number": "SETP990000228",
"api_status": "Created",
"cufe": "8c5b493b0b862dd99267b58a3fbd35d8098464e2...",
"comprobante_id": 90312
}
}
payment_due_date y en cada ítem discount_rate e is_excluded con valor. Para facturar por mandato, cada ítem debe incluir mandate_identification (y su mandate_identification_document_id) del mandante.Crear tiquete POS
Factura de Venta POS) a la DIAN. Mismo formato de request que la factura estándar; usa el rango de numeración POS de tu cuenta.Factura de Venta POS. Si no envías numbering_range_id, se detecta automáticamente el rango POS activo del titular.Genera un tiquete POS electrónico. Responde JSON con el resultado DIAN.
Cabecera
| Campo | Descripción | |
|---|---|---|
customer | req | ID del tercero (de api/third) |
payment_form | req | 1 Contado · 2 Crédito |
payment_method_code | opc | Método de pago DIAN (por defecto 10) |
numbering_range_id | opc | Rango POS. Si se omite, se auto-detecta el rango Factura de Venta POS activo |
observation | opc | Nota (por defecto "Tiquete POS Electrónico") |
payment_due_date, municipality_id, tip_amount, seller_id | opc | Vencimiento, municipio, propina, vendedor |
Ítems — products[]
| Campo | Descripción | |
|---|---|---|
id | req | ID del producto (de api/products) |
code_reference, name | req | Código y nombre del ítem |
quantity, price, discount_rate | req | Cantidad, precio unitario, % descuento |
tax_rate | req | % IVA: 0, 5, 8, 19 |
unit_measure_id, standard_code_id, tribute_id | req | Catálogos (ver Relaciones) |
is_excluded, requiere_stock, withholding_tax_rate | opc | Excluido de IVA (0/1), control de stock, retención |
Ejemplo
{
"customer": "8",
"payment_form": "1",
"payment_method_code": "10",
"observation": "Venta POS",
"products": [
{
"id": "72707",
"code_reference": "ENVIO-SHOPIFY",
"name": "Envío - Estándar",
"quantity": "1",
"price": "4000",
"tax_rate": "0",
"discount_rate": "0",
"unit_measure_id": "70",
"standard_code_id": "1",
"tribute_id": "22",
"is_excluded": "0",
"requiere_stock": "0"
}
]
}
{
"status": "success",
"message": "Factura POS creada exitosamente",
"data": {
"invoice_id": 47016,
"bill_number": "EPOS71",
"cufe": "20764680cd9bd7b8d62c72442e012887c912cfc8...",
"api_status": "Created",
"dian_response": { "bill": { "number": "EPOS71", "cufe": "2076...", "total": 4000, "id": "347619492" } }
}
}
requiere_stock=1 dentro de la transacción. Envía numbering_range_id explícito si manejas varios rangos POS por sucursal.Crear factura RIPS (salud)
rips_id) creado desde el módulo de RIPS. Los datos clínicos (paciente/servicios) se toman de ese paquete, no se envían en el request.Genera una factura electrónica RIPS. Responde JSON con el resultado DIAN.
Cabecera
| Campo | Descripción | |
|---|---|---|
rips_id | req | ID del paquete RIPS validado. De él se cargan pacientes y servicios de salud. |
invoice_type | req | Debe ser "rips" |
customer | req | ID del tercero (pagador — normalmente la EPS/entidad) |
numbering_range_id | req | Rango de numeración / resolución |
payment_form | req | 1 Contado · 2 Crédito |
payment_method_code | req | Método de pago (catálogo) |
payment_due_date | req | Fecha de vencimiento (una fecha, aun en contado) |
company | req | Nombre del emisor (requerido para la notificación por correo) |
observation | opc | Nota / observación |
cuenta_bancaria_id, cost_center_id, moneda | opc | Cuenta bancaria, centro de costo, divisa |
products) y los datos clínicos (health_data) se derivan automáticamente del paquete rips_id. No es necesario enviarlos.Ejemplo
{
"rips_id": "30",
"invoice_type": "rips",
"customer": "8",
"numbering_range_id": "100",
"payment_form": "1",
"payment_method_code": "10",
"payment_due_date": "2026-07-02",
"company": "NEXO CONTABLE",
"observation": "Servicios de salud - paquete RIPS"
}
{
"status": "success",
"message": "Factura creada exitosamente",
"data": {
"invoice_id": 47021,
"code_reference": "INV6a46da95177b095bb",
"bill_number": "SETP990000231",
"api_status": "Created",
"cufe": "ad3a694a73e1a211f585d259c329c160031dbe2f...",
"dian_response": { "bill": { "number": "SETP990000231", "total": 7350000 } }
}
}
rips_id debe estar en estado validado y pertenecer a tu cuenta; (2) la cuenta debe tener credenciales DIAN configuradas; (3) envía payment_due_date (la DIAN rechaza con "Datos de la factura incompletos" si falta) y company (requerido por la notificación por correo). Los ítems y datos clínicos salen del paquete RIPS.El flujo FEV-RIPS
RIPS = Registro Individual de Prestación de Servicios de Salud. FEV-RIPS es la pareja del RIPS con la factura electrónica de venta (FEV) en salud.
rips_id), después se factura. No hay un endpoint que reciba un RIPS crudo y lo facture en un solo paso.1. POST api/rips_invoice/rips.php → registra el RIPS (paquete_id)
2. GET api/rips_invoice/get_rips_for_invoice.php?rips_id=N → arma datos de factura
3. POST api/invoice/generate_rips.php → factura ante la DIAN (CUFE) + valida ante MinSalud (CUV)
CUV ≠ CUFE (el punto que más confunde)
- CUFE — lo asigna la DIAN al timbrar la factura. Identifica la factura.
- CUV — lo asigna MinSalud (MUV) al validar el RIPS. Identifica la validación.
numFactura) — no por el CUFE.Estados del paquete
| estado | Significado |
|---|---|
pendiente | Validó localmente; falta su factura. En producción los RIPS sin factura quedan aquí hasta facturarse. |
validado | CUV real de MinSalud. |
rechazado | El MUV lo rechazó (ver mensaje_respuesta con resultadosValidacion). |
error | Falló la llamada al validador. |
Estructura del payload RIPS
Forma del payload que registras en Registrar paquete RIPS. Sigue la estructura oficial Res. 2275/2023. Es también lo que genera Wardian al facturar desde un evento clínico.
Raíz — sin factura vs. con factura
La raíz es excluyente: un paquete se reporta como nota sin factura o con factura, nunca ambos.
{
"numDocumentoIdObligado": "900123456",
"numFactura": null,
"numNota": "00001234",
"tipoNota": "RS",
"usuarios": [ /* … */ ]
}
{
"numDocumentoIdObligado": "900123456",
"numFactura": "SETP990000001",
"numNota": null,
"tipoNota": null,
"usuarios": [ /* … */ ]
}
cufe (o al facturar el paquete), la plataforma resuelve la FEV localmente y reescribe la raíz a numFactura = número de esa factura, con tipoNota/numNota = null. Para notas crédito/débito coexisten numFactura + tipoNota (NC/ND) + numNota.Usuario
Cada entrada de usuarios[] es un paciente con sus servicios. Códigos de residencia según la guía oficial (país Colombia = 170).
{
"consecutivo": 1,
"tipoDocumentoIdentificacion": "CC",
"numDocumentoIdentificacion": "1234567890",
"fechaNacimiento": "1985-06-15",
"codSexo": "M",
"codPaisResidencia": "170",
"codMunicipioResidencia": "11001", // DANE — de pacientes.municipio_id (o el tercero)
"codZonaTerritorialResidencia": "02", // 02 = urbana
"incapacidad": "NO", // "SI" | "NO"
"codPaisOrigen": "170",
"tipoUsuario": "01",
"servicios": { "consultas": [ … ], "procedimientos": [ … ] }
}
codMunicipioResidencia es null si el paciente no tiene municipio DANE cargado (columna pacientes.municipio_id, con respaldo al tercero enlazado). El validador oficial lo exige — cárgalo desde la ficha del paciente.Servicios — consultas vs. procedimientos
Wardian clasifica cada servicio en su bloque oficial según el tipo de evento: Consulta/Control → consultas[]; el resto (incl. Procedimiento, Urgencia) → procedimientos[]. Difieren en algunos campos:
{
"codPrestador": "800100123456",
"fechaInicioAtencion": "2024-02-20 10:30:00",
"numAutorizacion": "0000000",
"codConsulta": "890201",
"modalidadGrupoServicioTecSal": "01",
"grupoServicios": "01",
"codServicio": 890,
"finalidadTecnologiaSalud": "11",
"causaMotivoAtencion": "38",
"codDiagnosticoPrincipal": "I10",
"codDiagnosticoRelacionado1": null,
"codDiagnosticoRelacionado2": null,
"codDiagnosticoRelacionado3": null,
"tipoDiagnosticoPrincipal": "02",
"tipoDocumentoIdentificacion": "CC",
"numDocumentoIdentificacion": "1234567890",
"vrServicio": 50000,
"conceptoRecaudo": "05",
"valorPagoModerador": 0,
"numFEVPagoModerador": null,
"consecutivo": 1
}
{
"codPrestador": "800100123456",
"fechaInicioAtencion": "2024-02-20 14:00:00",
"idMIPRES": null,
"numAutorizacion": "0000000",
"codProcedimiento": "880201",
"modalidadGrupoServicioTecSal": "01",
"grupoServicios": "01",
"codServicio": 890,
"viaIngresoServicioSalud": "01",
"finalidadTecnologiaSalud": "11",
"codDiagnosticoPrincipal": "I10",
"codDiagnosticoRelacionado": null,
"codComplicacion": null,
"tipoDocumentoIdentificacion": "CC",
"numDocumentoIdentificacion": "1234567890",
"vrServicio": 500000,
"conceptoRecaudo": "05",
"valorPagoModerador": 0,
"numFEVPagoModerador": null,
"consecutivo": 1
}
numFEVPagoModerador = número de la FEV en todo ítem con valorPagoModerador > 0, en cualquiera de los bloques.causaMotivoAtencion (38) y tipoDiagnosticoPrincipal (02) son valores por defecto hasta que se capture el dato clínico real (sobreescribibles por cuenta vía rips_configuracion). Los bloques urgencias, hospitalizacion, medicamentos, otrosServicios y recienNacidos aún no se generan (requieren datos que el modelo clínico no almacena).conceptoRecaudo (campos C18/P17), presente en consultas y procedimientos. El Anexo no define tipoPagoModerador — es un alias del mismo concepto. Como el MUV (nivel 1) valida con esquema estricto y rechaza claves desconocidas, se emite únicamente conceptoRecaudo.Registrar paquete RIPS
paquete_id (= rips_id) que usarás para facturar. Aquí no se crea FEV.Cuerpo
| Campo | Descripción | |
|---|---|---|
payload | req | El JSON RIPS completo — ver Estructura del payload (numDocumentoIdObligado, usuarios[] con servicios.consultas/servicios.procedimientos, …). |
usuario_id_rips | opc | Dueño del RIPS. Por defecto, quien envía. Un principal puede enviar por sus subusuarios. |
cufe | opc | CUFE de una FEV ya emitida. Si se envía, el RIPS se convierte a modo con factura (la raíz numFactura toma el número de esa factura) y se valida así. El CUFE se resuelve localmente contra tus facturas; no se escribe dentro del RIPS. |
{
"usuario_id_rips": 29,
"payload": {
"numDocumentoIdObligado": "900123456",
"numNota": "00001234",
"tipoNota": "RS",
"usuarios": [ { "...": "ver Estructura del payload" } ]
}
}
{
"status": "ok",
"cuv": "a1b2c3...",
"paquete_id": 30,
"usuario_id_rips": 29,
"usuario_id_envio": 29
}
WARDIAN- es simulado (validador inalcanzable) y solo ocurre fuera de producción. La opción cufe requiere la estructura moderna usuarios[].Datos para facturar
Solo devuelve paquetes con cuv no vacío.
{
"status": "success",
"data": {
"rips_id": 30,
"rips_cuv": "a1b2c3...",
"rips_cufe": null,
"numbering_range_id": 100,
"invoice_type": "rips",
"health_data": { "provider_code": "...", "patient": { "...": "" } },
"items": [ { "code_references": "890201", "name": "Consulta", "price": 50000 } ]
},
"suggested_customer": { "id": 8, "name": "EPS ..." }
}
rips_cufe (tomado del payload_fev) es null hasta que el RIPS se factura. Pasa data a Crear factura RIPS.Revalidar / con factura
Cuerpo
| Campo | Descripción | |
|---|---|---|
rips_id | req | ID del paquete a revalidar. |
cufe | opc | Si se envía, convierte el paquete a modo con factura (raíz numFactura = número de esa FEV, resuelta localmente) antes de reenviar. El payload convertido se persiste. |
{ "rips_id": 30, "cufe": "ad3a694a73e1..." }
{
"status": "success",
"message": "RIPS regenerado correctamente con CUV real",
"cuv": "a1b2c3...",
"estado": "validado",
"is_simulated": false
}
404 si no existe una factura con ese CUFE en tu cuenta. Sin cufe, revalida el paquete tal cual (sin cambiar a con factura).Requisitos antes de tu primer RIPS
Tres cosas tienen que existir en la cuenta antes de que Registrar paquete RIPS devuelva un CUV real. Si falta alguna, la llamada no falla: degrada en silencio, y eso es lo que más tiempo hace perder.
1. NIT de la cuenta
Sale de mi_cuenta.identification y alimenta el campo raíz numDocumentoIdObligado. Debe cumplir ^[0-9]{8,15}$. Sin NIT el paquete no se construye — es el único de los tres que da un error explícito.
2. Prestador
Al menos una fila de prestador, que alimenta codPrestador en cada servicio.
| Campo | Descripción |
|---|---|
codigo_habilitacion | Código de habilitación REPS. Opcional, pero solo se usa si tiene exactamente 12 caracteres. |
numero_id | Respaldo: si no hay habilitación de 12 caracteres, se usa el NIT/cédula rellenado con ceros a la izquierda hasta 12. |
es_profesional | 1 para profesional independiente (persona), 0 para organización. La tabla mezcla ambos a propósito. |
15-100562 tiene 9 caracteres, así que no se usa y el sistema cae al NIT sin avisar. Si esperas ver tu código de habilitación en el payload y ves ceros a la izquierda, es esto.3. Credenciales SISPRO
Se leen de rips_endpoints ligadas al prestador: client_id = número de documento, client_secret = clave, más tipo_documento y el indicador reps (desmárcalo si usas código IPSnoREPS).
is_simulated: true) para que puedas integrar; en producción el paquete se queda en pendiente. Nunca des por bueno un CUV sin mirar is_simulated.Catálogos de referencia y sus límites
El payload referencia varios catálogos oficiales. El validador comprueba los códigos contra ellos, así que conviene saber qué hay cargado de verdad — porque no está todo.
Dos niveles
- Oficial —
rips_cups,rips_cie10,rips_cums,rips_serviciosy los catálogos de dominio. Globales, sin columna de tenant. - Propios del prestador —
clinical_cups,clinical_cums,clinical_diagnosticos,clinical_eps. Los das de alta tú; el validador los acepta igual.
Al buscar códigos se unen los dos, y ante un duplicado gana la fila oficial.
rips_cie10 cubre solo las letras A E I J K M N R — no tiene el capítulo F (trastornos mentales) ni el Z. rips_cups cubre solo los capítulos 01–35 (quirúrgicos) — no tiene el capítulo 89, es decir, ningún código de consulta. Un código CUPS o CIE-10 perfectamente válido a nivel nacional puede ser rechazado simplemente porque aún no está cargado.Qué hacer mientras tanto
Da de alta los códigos que uses como códigos propios. El validador los acepta con el mismo peso que los oficiales, y quedan disponibles en los desplegables. Es la vía soportada, no un truco: el respaldo existe justamente porque la carga oficial se sabe incompleta.
Rechazos del validador
Un estado de rechazado viene de MinSalud, no de la plataforma. El detalle llega en mensaje_respuesta, dentro del arreglo resultadosValidacion.
Causas frecuentes
| Causa | Qué revisar |
|---|---|
| Código CUPS o CIE-10 inexistente | Puede ser real pero no estar cargado — ver Catálogos y sus límites. Regístralo como código propio. |
| Diagnóstico principal Z | Prohibido por norma. Usa un diagnóstico distinto de Z00–Z99. |
| Bloque de servicios vacío | servicios debe traer al menos una consultas[] o un procedimientos[]. |
| Servicio que no coincide con el REPS | El codServicio reportado debe corresponder a lo que el prestador tiene inscrito en el REPS. Es la causa de rechazo más común. |
| Raíz mal formada | Sin factura y con factura son excluyentes: o numFactura, o numNota+tipoNota. Nunca ambos. |
Endpoints retirados
api/rips_invoice/batch_create.php responde 501 a propósito. Antes era un stub que no creaba nada; ahora falla de forma explícita para no engañar a integraciones que aún lo llamen. Crea las facturas una a una.Adjuntar XML de factura (venta, POS, RIPS y mandato)
Acepta multipart/form-data (archivo) o JSON (base64). La API Key determina el usuario: solo puedes adjuntar sobre tus facturas.
Campos
| Campo | Descripción | |
|---|---|---|
bill_number | req | Número de la factura de venta (debe existir en tu cuenta) |
xml | opc* | Archivo XML (multipart). Máx 5 MB |
xml_base64 | opc* | El XML codificado en base64 (JSON o form). Máx 5 MB |
* Envía uno de los dos. El XML debe ser de factura (raíz Invoice o AttachedDocument); si trae CUFE (cbc:UUID) debe coincidir con el de la factura — así no se puede adjuntar el XML de otra.
Ejemplo real
XML de ejemplo por tipo de factura — todos son documentos reales tal como se enviaron a la DIAN:
| Tipo | Factura | Descarga |
|---|---|---|
| Estándar | SETP990000122 | XML |
| POS | EPOS84 | XML |
| RIPS (salud) | FEPI13 | XML |
| Mandato | FEG240 | XML |
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" …>
<cbc:ID>FEPI13</cbc:ID>
<cbc:UUID schemeID="1" schemeName="CUFE-SHA384">4fa0ee0c14522b25d3218f3ce42605c8d31ebc157a5e5f2c…</cbc:UUID>
<cbc:IssueDate>2026-08-31</cbc:IssueDate>
…
{
"bill_number": "FEPI13",
"xml_base64": "PEludm9pY2UgeG1sbnM9InVybjpvYXNpczpuYW1lczpzcGVjaWZpY2F0aW9uOnVibDpzY2hl…"
}
{
"success": true,
"message": "XML adjuntado a la factura FEPI13.",
"data": {
"factura_id": 663033,
"bill_number": "FEPI13",
"cufe": "4fa0ee0c14522b25d3218f3ce42605c8d31ebc157a5e5f2cb7688a7f778d0e4631b2fbd3a428d7457e7f2f2b0f598501",
"bytes": 19740,
"adjuntado_en": "2026-08-31 16:10:00"
}
}
Errores
| HTTP | Motivo |
|---|---|
401 | Falta o es inválida la X-API-Key |
404 | La factura bill_number no existe en tu cuenta |
409 | El CUFE del XML no coincide con el de la factura |
413 / 422 | XML muy grande / mal formado o de otro tipo de documento |
Crear documento soporte
id), los ítems y el numbering_range_id del rango de documento soporte. Se envía a la DIAN y responde con el número y el CUDS.Genera un documento soporte electrónico. Acepta application/x-www-form-urlencoded o multipart/form-data. Responde JSON con el resultado DIAN. El documento soporte no lleva IVA (compra a no obligado).
Cabecera
| Campo | Descripción | |
|---|---|---|
customer | req | ID del proveedor (tercero, de api/third) |
numbering_range_id | req | Rango de numeración de documento soporte |
payment_form | req | 1 Contado · 2 Crédito |
payment_method_code | req | Método de pago (catálogo) |
issue_date | opc | Fecha de emisión (por defecto hoy) |
payment_due_date | opc | Fecha de vencimiento (si crédito) |
observation | opc | Nota / observación |
cuenta_bancaria_id, cuenta_cxp_codigo, cost_center_id | opc | Cuenta bancaria (pago), CxP (crédito), centro de costo |
save_draft | opc | 1 = guardar como Borrador (no se envía a la DIAN) |
Ítems — products[]
| Campo | Descripción | |
|---|---|---|
code_reference, name | req | Código y nombre del ítem |
quantity, price | req | Cantidad y precio unitario |
discount_rate | req | % descuento (ej. 0) |
unit_measure_id, standard_code_id | req | Catálogos (ver Relaciones) |
ret_fuente_rate, ret_ica_rate, withholding_config_id | opc | Retenciones (ReteFuente / ReteICA) |
Ejemplo
{
"customer": "48",
"numbering_range_id": "100",
"payment_form": "1",
"payment_method_code": "47",
"issue_date": "2026-07-14",
"observation": "Compra a proveedor no obligado",
"products": [
{
"code_reference": "SERV-01",
"name": "Servicio de mantenimiento",
"quantity": "1",
"price": "500000",
"discount_rate": "0",
"unit_measure_id": "70",
"standard_code_id": "1",
"ret_fuente_rate": "4"
}
]
}
{
"status": "success",
"success": true,
"data": {
"document_id": 618,
"code_reference": "INV6a46c29683f15fdc6",
"number": "DS-1",
"cuds": "a1b2c3d4e5f6...",
"api_status": "Created"
}
}
{
"status": "success",
"success": true,
"message": "Borrador guardado",
"data": { "document_id": 618, "estado": "Borrador" }
}
save_draft=1 el documento queda en Borrador (no va a la DIAN) para revisar/emitir después desde el panel. Un borrador se puede convertir en documento soporte emitido.Qué es el RDA
El flujo
| # | Paso | Dónde |
|---|---|---|
| 1 | Cargar las credenciales que emitió MinSalud (por prestador y por entorno) | Ajustes → RDA (IHCE) |
| 2 | Registrar el profesional con documento y registro RETHUS | Ajustes → Prestadores (es_profesional=1) |
| 3 | Completar una atención clínica con diagnóstico CIE-10 | Historia clínica → Eventos |
| 4 | Se construye el Bundle, se valida local y se envía | automático si auto_enviar está activo, o POST manual |
| 5 | IHCE devuelve el identificador del RDA, que se persiste | Historia clínica → RDA (IHCE) |
Lo que hay que saber del contrato
| Aspecto | Valor |
|---|---|
| Estructura | Bundle con type: "document". entry[0] debe ser la Composition, y solo puede haber una. |
| Referencias | Ids planos, sin # y sin fullUrl: "subject": {"reference": "CC-80189301"}. Personas usan TipoDoc-NumDoc; la IPS, su código de habilitación pelado. |
| Tipo de documento | Composition.type = LOINC 51845-6 (Outpatient Consult note) para el RDA ambulatorio. |
| Secciones | Nueve obligatorias. Las que no tengan datos igual se envían, con emptyReason = nilknown. |
| Obligatorios en el Bundle | Composition, Patient, Practitioner y DocumentReference son 1..1. La Organization de la IPS es 0..1. |
| Transporte | OAuth2 client_credentials contra Azure AD. Cada llamada lleva Authorization: Bearer, Ocp-Apim-Subscription-Key y Content-Type: application/fhir+json. |
| Duplicados | IHCE responde 409 si coinciden Encounter.subject + period + serviceProvider + participant. Wardian lo detecta localmente antes de enviar. |
client_id, el client_secret y la Ocp-Apim-Subscription-Key al asignar credenciales. Se solicitan por la Mesa de Servicios del micrositio IHCE.11450-4, desde el CIE-10 del evento) y Pagadores (48768-6, si el paciente tiene código EAPB). Las otras siete viajan con emptyReason porque su perfil exige datos que el sistema aún no captura de forma codificada. El Bundle es estructuralmente válido en cualquier caso.47519-4) solo existe en los RDA de hospitalización y urgencias; en consulta externa el CUPS viaja en Encounter.serviceType.Credenciales IHCE
Cuerpo
| Campo | Descripción | |
|---|---|---|
entorno | req | qa | preproduccion | produccion |
base_url | req | La URL que emitió MinSalud. Debe ser https://. |
tenant_id | req | Tenant de Azure AD contra el que se pide el token. |
client_id | req | |
client_secret | req | Obligatorio al crear. Al editar, envíalo vacío para conservar el guardado. |
subscription_key | req | El Ocp-Apim-Subscription-Key. Mismo criterio que el secreto. |
scope | req | Scope OAuth2, normalmente api://…/.default. |
prestador_id | opc | IPS a la que pertenece la credencial, si el tenant opera varias. |
auto_enviar | opc | 0 por defecto. Con 1, cada atención completada dispara el envío. |
op_enviar_ambulatorio | opc | Ruta de la operación. Configurable porque el Manual documenta menos operaciones que la guía FHIR. |
{
"entorno": "qa",
"base_url": "https://<la-que-emitio-minsalud>",
"tenant_id": "00000000-0000-0000-0000-000000000000",
"client_id": "<client-id>",
"client_secret": "<client-secret>",
"subscription_key": "<ocp-apim-subscription-key>",
"scope": "api://rda/.default",
"auto_enviar": 0
}
POST .../rda_credenciales/test.php con {"entorno":"qa"} pide un token real a Azure AD. Un success confirma tenant/client/secret/scope, pero no valida la subscription key ni la URL base: eso se prueba en el primer envío.GET .../rda_credenciales/list.php devuelve las credenciales del tenant con los secretos enmascarados, y POST .../delete.php con {"id":N} elimina una.Previsualizar Bundle
{
"evento_id": 4242,
"entorno": "qa",
"valido": true,
"errores": [],
"bundle": {
"resourceType": "Bundle",
"type": "document",
"identifier": { "value": "…uuid…" },
"entry": [
{ "resource": { "resourceType": "Composition", "id": "Composition-0" } },
{ "resource": { "resourceType": "Patient", "id": "CC-1000200300" } },
{ "resource": { "resourceType": "Practitioner", "id": "CC-79123456" } },
{ "resource": { "resourceType": "Organization", "id": "230010255201" } },
{ "resource": { "resourceType": "Location", "id": "230010255201-01" } },
{ "resource": { "resourceType": "Encounter", "id": "Encounter-0" } },
{ "resource": { "resourceType": "DocumentReference", "id": "DocumentReference-0" } },
{ "resource": { "resourceType": "Condition", "id": "Condition-0" } }
]
}
}
Encounter.diagnosis es 1..4), sin profesional registrado, o sin código de habilitación en la IPS.Enviar RDA
borrador con los errores.Cuerpo
| Campo | Descripción | |
|---|---|---|
evento_id | req | Evento clínico a reportar. |
entorno | opc | qa por defecto. |
forzar | opc | Reenvía aunque el contenido sea idéntico a un envío ya aceptado. |
confirmar_produccion | opc | Obligatorio si entorno es produccion. Salvaguarda contra envíos accidentales. |
{
"estado": "aceptado",
"paquete_id": 17,
"http_code": 200,
"rda_id": "RDA-000123",
"mensaje": "RDA aceptado por IHCE.",
"errores": []
}
Estados y códigos
| Estado | HTTP | Significado |
|---|---|---|
aceptado | 200 | IHCE devolvió el identificador del RDA. |
duplicado | 200 | El encuentro ya se había reportado con el mismo contenido. No se reenvía. |
enviado | 202 | Respondió 200 pero sin identificador reconocible. |
borrador | 422 | No pasó la validación local. No se envió. |
rechazado | 422 | IHCE devolvió 400. Los issue del OperationOutcome vienen en errores. |
error | 502 | Fallo de red o HTTP inesperado. Reintentable. |
dist/cron/rda_retry_pending.php reintenta solo error y enviado, con espera creciente (5/15/60/240 min) y techo de 5 intentos. Nunca reintenta rechazado ni borrador: son defectos de contenido y reintentarlos da el mismo 400.Consultar envíos
Devuelve los paquetes del tenant, el conteo por estado y un bloque aptitud con lo que falta para que los envíos pasen la validación de registros de MinSalud (EVOL, RETHUS, REPS, DIVIPOLA).
{
"paquetes": [
{ "id": 17, "estado": "aceptado", "entorno": "qa", "rda_id": "RDA-000123",
"fecha_evento": "2026-07-20", "diagnostico_codigo": "J00", "intentos": 1 }
],
"por_estado": { "aceptado": 12, "rechazado": 1 },
"aptitud": {
"pacientes_total": 340,
"pacientes_sin_fecha_nac": 0,
"pacientes_sin_sexo_biologico": 12,
"profesionales": 3,
"ips_con_habilitacion": 1,
"eventos_completados": 512,
"eventos_sin_diagnostico": 4,
"credenciales": { "qa": "lista", "preproduccion": "sin configurar", "produccion": "sin configurar" }
}
}
list.php no devuelve el Bundle: es información clínica y pesa. Para el detalle completo (Bundle enviado, OperationOutcome y bitácora) usa GET .../rda/data.php?id=17.Nómina — cómo funciona
Las APIs de nómina cubren el ciclo laboral completo: colaboradores y contratos, liquidación mensual, radicación ante la DIAN, liquidaciones definitivas, prestaciones sociales, préstamos, dotación, PILA y control de asistencia.
Todas cuelgan de //wardian.com.co/dist/api/roster/ y se autentican con el mismo header X-API-Key que el resto de la API.
1. Los errores responden 200 — pero solo si tu API Key es válida
Hay que distinguir dos situaciones, porque se comportan de forma completamente distinta:
| Situación | Respuesta |
|---|---|
| API Key ausente o inválida | 302 — redirección al formulario de login, en HTML. No es JSON y no lleva status |
| API Key válida, petición incorrecta | 200 OK con el error en el cuerpo |
{ "status": "error", "message": "Seleccione un colaborador" }
response.json() sin mirar antes, una key caducada o mal escrita no te dará 401: te dará un error de parseo sobre el HTML de la pantalla de login. Comprueba el código de estado antes de interpretar el cuerpo, y trata cualquier 3xx como fallo de autenticación.Una vez dentro, ningún endpoint de nómina fija código de estado: los fallos de validación y de permisos llegan todos como 200. La tabla de códigos de error general no aplica aquí. Ramifica siempre por el campo status.
Las dos excepciones son api/roster/documentos/download.php (que sí devuelve 403/404/500 en texto plano, no JSON) y api/roster/asistencia/ip_location.php (que devuelve 502).
2. El cuerpo JSON solo funciona con API Key
Los endpoints de nómina leen $_POST. Cuando te autenticas con X-API-Key y envías Content-Type: application/json, la plataforma decodifica el cuerpo y lo expone como si fuera un formulario — así que puedes enviar JSON con normalidad. Es el modo recomendado.
Las únicas excepciones que además leen JSON por su cuenta son nomina/create.php, nomina/temporal_create.php y datos_ley_manage.php. Y hay tres que exigen multipart/form-data obligatoriamente porque reciben ficheros: nomina/import.php, pila/mark_assisted_bulk.php y los de subida de documentos.
3. Varios campos que parecen anidados son texto JSON
Algunos parámetros se envían como una cadena JSON dentro de un campo, no como estructura anidada: liquidacion/create.php → detalle_mensual, detalle_deducciones, vacaciones_periodos; dotacion/deliver.php → checklist; dotacion/plantillas/save.php → items; asistencia/apply_to_nomina.php → novedades; asistencia/send_link.php → ids.
Orden del ciclo mensual
Los endpoints no son independientes: cada paso depende de lo que escribió el anterior.
1. Contrato create_collaborator.php (crear_contrato=1) ó contrato_regularizar.php
↓ sin contrato el motor no resuelve salario, fechas ni periodo de pago
2. Cifras de ley GET smmlv_data.php?year=2026
↓ si responde estimado:true, escríbelas con datos_ley_manage.php
3. Previsualizar nomina/calculate_batch.php
↓ devuelve data[] con el cálculo por colaborador
4. Guardar nomina/create.php ← reenvía ese data[] como detalles[]
↓ una sola nómina por (periodo, mes, año)
5. Completar nomina/auto_liquidate.php
↓ revisa status: puede ser "partial"
6. Contabilizar generate_payroll_accounting.php (opcional)
7. Aprobar nomina/send_approval.php (opcional)
8. Radicar send_payroll_dian.php ← una llamada POR COLABORADOR
⚠ punto de no retorno: marca Pagada y debita caja
send_payroll_dian.php, send_payroll_adjustment.php, nomina/adjust.php, nomina/delete.php, nomina/create.php y nomina/auto_liquidate.php (ambos amortizan préstamos), nomina/import.php con pago_opcion=ahora, nomina/temporal_create.php con action=mark_paid, liquidacion/approve.php, loans/create.php (que por defecto desembolsa), pila/pay.php, y todos los que mandan correo o WhatsApp.Colaboradores y contratos
crear_contrato=1) o regularízalo después.Lista paginada de colaboradores.
| Campo | Descripción | |
|---|---|---|
pagina, search | opc | Paginación y búsqueda por nombre o documento |
estado, tipo, tipo_empleado, contrato | opc | Filtros |
mostrar_inactivos | opc | Incluye los desactivados |
Ficha completa de un colaborador.
Crea un colaborador y, opcionalmente, su contrato laboral.
| Campo | Descripción | |
|---|---|---|
id_type, identification | req | Tipo y número de documento |
first_name, last_name | req | Nombres. middle_name y second_last_name son opcionales |
base_salary | req | Salario mensual, también en contratos quincenales |
contract_type, contract_start_date | req | Tipo y fecha de inicio. contract_type=6 (prestación de servicios) queda fuera de la nómina |
crear_contrato | opc | 1 genera el contrato laboral en la misma llamada |
periodo_pago, jornada, modalidad_trabajo | opc | Mensual/Quincenal, jornada y modalidad |
fecha_fin, fecha_fin_pactada, periodo_prueba_dias, descripcion_obra | opc | Según el tipo de contrato |
codigo_eps, codigo_afp, codigo_arl, codigo_ccf, codigo_cesantias | opc | Administradoras de seguridad social. Sin ellos no se puede generar la planilla PILA |
arl_risk, cotizante_tipo, cotizante_subtipo, worker_type, worker_subtype | opc | Clasificación PILA |
health_percentage, pension_percentage | opc | Porcentajes de cotización |
bank, custom_bank, account_type, account_number, payment_method | opc | Datos de pago. account_type: 1=Ahorros, 2=Corriente |
integral_salary, es_colaborador_interno, bonificaciones_fiscalizadas | opc | Cambian la matemática — ver el aviso de abajo |
auxilio, position, cargo_detalle, sucursales, notas | opc | Auxilio pactado, cargo, sucursales y notas |
email, phone, address, departamento, municipality | opc | Contacto. El email es necesario para el portal y los desprendibles |
paga_autoretencion, medicina_prepagada, tipo_empleado | opc | Autorretención (Art. 114-1 ET), medicina prepagada y tipo |
·
integral_salary — el salario integral ya incorpora cesantías, intereses y prima (Art. 132 CST). El IBC se calcula sobre el 70%, y en prestaciones solo procede compensar vacaciones.
·
es_colaborador_interno — pone el auxilio de transporte en cero.
·
bonificaciones_fiscalizadas — con valor 1, las bonificaciones entran en la base de prestaciones y liquidación y suman al IBC (Art. 127 CST). Con 0 quedan fuera.Mismos campos que crear, más id y los de retención en la fuente: retefuente_dependientes, retefuente_dependientes_pct, retefuente_vivienda, retefuente_medicina_prepagada, retefuente_afc, retefuente_aporte_vol_pension.
Crea el contrato mínimo para un colaborador que no tiene ninguno. Es el prerrequisito del motor de nómina.
| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Colaborador |
tipo_contrato, fecha_inicio | req | Tipo y fecha de inicio |
salario_pactado | req | Siempre el importe mensual |
fecha_terminacion, cargo, auxilio_pactado, es_salario_integral, periodo_pago, observaciones | opc | Resto de condiciones |
Activa o desactiva un colaborador. Campos: collaborator_id req, activo req.
multipart/form-data. Campos: collaborator_id req, fichero en foto req.
Histórico salarial. Campos: collaborator_id req, fecha_referencia y page opc.
Saldo de vacaciones. Campos: collaborator_id req, fecha_referencia opc.
Cifras de ley del año
Devuelve el salario mínimo, el auxilio de transporte y la UVT del año.
estimado: true, las cifras del año todavía no están cargadas y el sistema está proyectando. Cárgalas antes de liquidar.Escribe las cifras legales del año. Acepta cuerpo JSON nativo.
{
"anio": 2026,
"smmlv": 1623500,
"auxilio": 200000,
"uvt": 49799,
"decreto_smmlv": "Decreto 2613 de 2025",
"decreto_auxilio": "Decreto 2614 de 2025",
"es_transitorio": 0,
"observaciones": ""
}
Ciclo de la nómina
Lista las nóminas. Filtros opcionales: year y collaborator_id.
{
"status": "success",
"data": [
{
"id": "512", "periodo": "1", "mes": "8", "anio": "2026",
"estado": "Borrador", "creado_en": "2026-08-30 11:02:41",
"total_colaboradores": "13", "total_neto": "31450000.00",
"periodo_texto": "Ago 2026", "periodo_tipo": "Mensual"
}
],
"count": 1
}
year si llevas histórico.
total_colaboradores cuenta filas, incluidas las que aún están sin liquidar en cero — no es "colaboradores liquidados".
Los números llegan como cadenas.
Calcula la nómina de un colaborador. No guarda nada.
| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Colaborador |
periodo_year, periodo_month | opc | Por defecto, el mes en curso |
periodo | opc | 1 mensual · 2 días 1–15 · 3 día 16 al fin de mes. Por defecto 1 |
dias_trabajados | opc | Por defecto, los días del periodo |
otros_ingresos, otras_deducciones | opc | Importes adicionales |
{
"status": "success",
"data": {
"collaborator_id": 48,
"nombre_completo": "ANDRÉS FELIPE GÓMEZ RUIZ",
"salario_base": 2500000, "auxilio_transporte": 200000, "aplica_auxilio": true,
"dias_trabajados": 30, "dias_base": 30, "dias_periodo": 30,
"periodo_pago": "Mensual",
"salario_proporcional": 2500000, "auxilio_proporcional": 200000,
"base_seguridad_social": 2500000,
"salud_empleado": 100000, "pension_empleado": 100000, "fsp": 0,
"prestamos_descontados": 150000,
"detalle_prestamos": [
{ "id": "31", "tipo": "prestamo_empresa", "valor_descontado": 150000 }
],
"total_devengado": 2700000, "total_deducido": 350000, "neto_a_pagar": 2350000,
"smmlv": 1623500, "periodo_year": 2026, "periodo_month": 8
}
}
dias_trabajados de la respuesta con el que mandaste.
El contrato manda sobre la ficha: se resuelve el contrato vigente a la fecha de fin del periodo, así que recalcular un mes antiguo usa el contrato de entonces.
El auxilio es el legal del año de la nómina, no el guardado en la ficha, y solo aplica si el salario no supera 2 SMMLV.
Las horas extra y bonificaciones solo aparecen si ya existe una nómina creada para ese mes; si no, salen en cero.
Leer los préstamos no los reserva. La amortización real ocurre al guardar.
La misma calculadora para varios colaboradores. Es el paso previo a guardar.
| Campo | Descripción | |
|---|---|---|
collaborator_ids | req | Array de ids. No admite cadena JSON: envía un array real |
periodo_year, periodo_month, periodo, dias_trabajados | opc | Se aplican igual a todos |
{
"status": "success",
"data": [ /* un objeto por colaborador, idéntico al de calculate.php */ ],
"errors": [ { "collaborator_id": 99, "message": "Colaborador no encontrado (ID: 99)" } ],
"count": 12
}
status es "success" aunque fallen todos. Si los 13 colaboradores dan error, recibes data: [], count: 0 y 13 entradas en errors. Compara count con los ids que enviaste y revisa errors.
No acepta
otros_ingresos ni otras_deducciones: para eso usa calculate.php uno a uno.Guarda la nómina. Acepta cuerpo JSON nativo. La cabecera nace en estado Borrador y las filas en Liquidada.
| Campo | Descripción | |
|---|---|---|
periodo_year | req | Debe ser 2020 o posterior |
periodo_month | req | 1 a 12 |
detalles | req | Array no vacío. Reenvía aquí el data[] de calculate_batch.php |
periodo | opc | Por defecto 1 |
centro_costo_id, observaciones | opc | Centro de costo y notas |
detalles[]| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Si falta, se guarda una fila huérfana sin que la petición falle. Inclúyelo siempre |
neto_a_pagar | req | Es el importe que se paga. Se guarda tal cual, sin recalcular |
salario_base, auxilio_transporte | opc | Importes mensuales; el sistema los prorratea |
dias_trabajados | opc | Se recorta por la vigencia del contrato |
salud_empleado, pension_empleado | opc | Se registran siempre, aunque sean cero |
fsp, otras_deducciones | opc | Solo se registran si son mayores que cero |
detalle_prestamos | opc | Array con id y valor_descontado por préstamo. Sin esto no se amortiza nada |
{
"periodo_year": 2026,
"periodo_month": 8,
"periodo": 1,
"observaciones": "Nómina de agosto",
"detalles": [
{
"collaborator_id": 48,
"salario_base": 2500000,
"auxilio_transporte": 200000,
"dias_trabajados": 30,
"salud_empleado": 100000,
"pension_empleado": 100000,
"fsp": 0,
"prestamos_descontados": 150000,
"detalle_prestamos": [ { "id": 31, "valor_descontado": 150000 } ],
"otras_deducciones": 0,
"neto_a_pagar": 2350000
}
]
}
{ "status": "success", "message": "Nómina guardada exitosamente", "data": { "nomina_id": 512 } }
devengado − deducido = neto, así que una petición armada a mano puede pagar cualquier importe. Genera siempre los detalles[] desde calculate_batch.php.
Si envías
prestamos_descontados pero omites detalle_prestamos, al colaborador se le descuenta el dinero pero el saldo del préstamo no baja.
Solo cabe una nómina por periodo, mes y año. Rehacerla exige eliminarla antes, y eso se bloquea si ya hay documentos vivos en la DIAN.
Liquida con valores por defecto los colaboradores que quedaron pendientes en la nómina. Único campo: nomina_id req.
{ "status": "success", "message": "13 colaborador(es) liquidado(s) con datos por defecto.",
"liquidados": 13, "errores": [] }
{ "status": "success", "message": "Todos los colaboradores ya están liquidados", "liquidados": 0 }
{ "status": "partial",
"message": "11 colaborador(es) liquidado(s), pero 2 quedaron sin liquidar y NO se pueden enviar a la DIAN así.",
"liquidados": 11, "errores": ["Colaborador #99: Colaborador no encontrado (ID: 99)"] }
status, no dos. Tratar todo lo que no sea "error" como éxito esconde filas sin liquidar que después se radican en cero ante la DIAN. Revisa liquidados y errores.
Este endpoint también amortiza préstamos.
Radicar ante la DIAN
Genera o regenera los asientos contables de la nómina. Campo: nomina_id req.
Radica el documento de nómina electrónica de un colaborador ante la DIAN.
| Campo | Descripción | |
|---|---|---|
nomina_id | req | Nómina |
collaborator_id | req | Colaborador. Una llamada por persona |
caja_id | opc | Cuenta de la que sale el dinero |
Pagada, debita la caja y obtiene el CUNE. A partir de aquí la nómina no se puede editar ni eliminar sin pasar antes por la nota de ajuste.
Radica también las filas con neto cero cuando tienen novedades: su guarda de "todo en cero" exige además cero días y cero base.
Nota de ajuste, anulación o regeneración contable de una nómina ya radicada.
| Campo | Descripción | |
|---|---|---|
nomina_id | req | Nómina |
action | req | nota_ajuste · anular · regenerar_contabilidad |
pila_confirmed | opc | 1 confirma el aviso sobre la planilla PILA vinculada |
nota_ajuste reabre la nómina para corregirla. Anula los documentos en la DIAN, devuelve el dinero a la cuenta de origen, restaura los saldos de préstamos, anula los comprobantes contables, y deja la cabecera en Ajustada con las filas de nuevo en Liquidada. Después hay que volver a radicar cada colaborador con send_payroll_dian.php."DIAN procesada pero error al revertir contabilidad": quedará anulado en la DIAN y sin revertir en la plataforma.
El éxito parcial también responde
"success". Revisa el array resultados[] buscando entradas con error.Envía la nota de ajuste a la DIAN. Campos: nomina_id, collaborator_id, cune_referencia.
Pagada y ya no se puede radicar. Para deshacer usa nomina/adjust.php con action=nota_ajuste.Ajustar y eliminar
Eliminar una nómina revierte de verdad: devuelve el dinero, restaura los préstamos, anula los comprobantes y borra las provisiones. Por eso conviene previsualizar antes.
Simulacro: dice qué se deshará y qué no. No modifica nada.
{
"status": "success",
"nomina": { "id": 512, "periodo": "2026-08 (periodo 1)", "estado": "Enviada",
"colaboradores": 13, "total": 31450000 },
"se_deshace": [
{ "concepto": "Devolución a caja/banco", "detalle": "Bancolombia Cta. Corriente", "valor": 31450000 },
{ "concepto": "Saldos de préstamos restaurados", "detalle": "3 pago(s)", "valor": 450000 },
{ "concepto": "Comprobantes contables eliminados", "detalle": "3 comprobante(s)", "valor": null }
],
"no_se_deshace": [
"5 comprobante(s) siguen vigentes en la DIAN. Debe enviar la Nota de Ajuste antes de poder eliminar.",
"Ya se enviaron los desprendibles por correo a los colaboradores. Esos correos no se pueden retirar."
],
"bloqueado": true
}
bloqueado: true significa que delete.php se va a negar. Ocurre cuando quedan documentos vivos en la DIAN o no se puede determinar de qué cuenta salió el dinero. Consúltalo antes de ofrecer un botón de eliminar.Elimina la nómina revirtiendo todos sus efectos. Irreversible.
| Campo | Descripción | |
|---|---|---|
nomina_id | req | Nómina |
motivo | req | Mínimo 10 caracteres. Queda en la auditoría |
pila_confirmed | opc | 1 confirma el aviso de la planilla PILA |
{
"status": "success",
"message": "Nómina NE-512 eliminada correctamente",
"revertido": {
"caja_devuelto": 31450000, "prestamos_restaurados": 3, "prestamos_valor": 450000,
"comprobantes_anulados": 3, "pagos_programados_borrados": 1, "prestaciones_ajustadas": 13
},
"avisos": ["El préstamo #31 lo creó esta nómina pero ya tiene pagos de otras; se conserva."]
}
{ "status": "error", "code": "dian_pendiente",
"message": "No se puede eliminar: 5 comprobante(s) siguen vigentes en la DIAN...",
"dian": { "enviados": 13, "ajustados": 8, "pendientes": 5 } }
{ "status": "error", "code": "motivo_requerido",
"message": "El motivo es demasiado corto; explique brevemente por qué se elimina." }
{ "status": "error", "code": "reversion_imposible",
"message": "No se puede determinar de qué cuenta salió el dinero..." }
{ "status": "pila_confirm",
"message": "Esta nómina tiene una planilla PILA en estado PAGADA.",
"pila": { "planilla_id": "88", "estado": "pagada", "sent_to_suaporte": true } }
code: "reversion_imposible", no se borró nada — normalmente falta marcar una cuenta como predeterminada en Bancos.Importar y pagos temporales
Crea una nómina completa desde un Excel. Requiere multipart/form-data — este endpoint no acepta JSON.
| Campo | Descripción | |
|---|---|---|
archivo | req | Fichero .xlsx o .xls |
anio, mes, periodo | opc | Año ≥ 2020, mes 1–12, periodo 1–3 |
pago_opcion | opc | ahora · fecha · despues (por defecto) |
cuenta_bancaria_id | opc | Obligatoria con ahora y fecha |
fecha_pago | opc | Obligatoria con fecha |
La fila 1 son los encabezados. Escríbelos sin tildes: se ignoran mayúsculas, espacios y asteriscos, pero no se normalizan los acentos.
| Columna | Descripción | |
|---|---|---|
identificacion, dias_trabajados, salario_base | req | Obligatorias. Sin ellas la importación se rechaza |
auxilio_transporte | opc | Si se omite, se calcula el legal |
horas_extra_diurnas (25%), horas_extra_nocturnas (75%), horas_extra_dom_diurnas (100%), horas_extra_dom_nocturnas (150%) | opc | En horas |
recargo_nocturno (35%), recargo_dom_diurno (80%), recargo_dom_nocturno (110%) | opc | En horas. Son recargos, no multiplicadores |
vacaciones_dias/_valor, incapacidad_dias/_valor, licencia_dias/_valor | opc | Si das días sin valor, se calcula |
bonificaciones, otros_ingresos | opc | Ingresos adicionales |
deduc_salud_pct, deduc_pension_pct | opc | Por defecto 4 cada uno |
deduc_fsp, deduc_retefuente, deduc_prestamos, deduc_otros | opc | Deducciones |
{
"status": "success",
"message": "11 colaborador(es) importados en nómina #513",
"data": { "nomina_id": 513, "processed": 11, "skipped": 2, "total_neto": 27340000 },
"warnings": ["Fila 5: Identificación '9998887' no encontrada"]
}
deduc_prestamos escribe una deducción, pero el saldo del préstamo no baja.
Las filas con identificación vacía se saltan sin aviso, así que las filas en blanco al final reducen el
processed en silencio.
Solo se cargan colaboradores activos y que no sean de prestación de servicios.
Si la contabilización falla, ocurre después de guardar y el error no se reporta: recibirás éxito con la nómina creada y sin comprobantes.
Pagos a personal temporal, al margen de la nómina electrónica (sin DIAN ni PILA). Un solo endpoint con cuatro operaciones según action. Acepta JSON.
action | Campos | Qué hace |
|---|---|---|
create (por defecto) | empleados req, periodo, mes, anio, observaciones | Crea el pago temporal |
mark_paid | id req, caja_id | Marca como pagado, contabiliza y debita la caja |
delete | id req | Elimina. Bloqueado si ya está pagado |
get_detail | id req | Cabecera y detalle |
empleados[]collaborator_id, dias_trabajados, salario_base, bonificaciones, deducciones, notas.
{
"action": "create",
"periodo": "Mensual",
"mes": 8, "anio": 2026,
"observaciones": "Refuerzo temporada",
"empleados": [
{ "collaborator_id": 77, "dias_trabajados": 12, "salario_base": 2000000,
"bonificaciones": 0, "deducciones": 0, "notas": "" }
]
}
periodo es texto libre ("Mensual"), no el número 1/2/3 del resto de la API.
mark_paid es idempotente: repetirlo responde éxito sin volver a cobrar. Pero una vez pagado no hay vuelta atrás: no existe "despagar" y el borrado queda bloqueado.
collaborator_id no se valida contra la lista de colaboradores.Lista los pagos temporales. Campos: pagina, search (busca por id u observaciones, no por nombre). Página fija de 10. Estados: Pendiente y Pagado.
Envía un enlace de aprobación de la nómina por correo o WhatsApp.
| Campo | Descripción | |
|---|---|---|
nomina_id | req | Nómina |
method | opc | email (por defecto) · zapgo · whatsapp_direct |
email | opc | Si se omite, se usa el de la cuenta |
phone | opc | Obligatorio para los dos métodos de WhatsApp |
Llamar dos veces invalida el enlace anterior: solo vive el último token, con 7 días de vigencia.
El token se crea antes del envío: si el correo falla recibirás error, pero el enlace anterior ya quedó anulado.
El estado de la nómina no cambia aquí. Éxito no significa aprobada: eso ocurre cuando el destinatario abre el enlace.
whatsapp_direct no envía nada: solo devuelve una URL wa.me para que la abras tú.Liquidaciones
Liquidación definitiva al terminar el contrato: cesantías, intereses, prima, vacaciones, indemnización y compensación de dotación.
calculate → create (borrador; ya reserva los abonos de préstamos) → approve (termina el contrato, consolida préstamos, compensa dotación y contabiliza). Para deshacer: preview_delete → delete o void.Lista las liquidaciones. Campos: collaborator_id, estado, year, page. Página fija de 20.
Detalle completo de una liquidación.
detalle_mensual, detalle_deducciones y vacaciones_periodos_detalle vuelven como cadenas de texto, no como objetos. Hay que decodificarlos.Calcula la liquidación de un colaborador. No guarda nada. Su respuesta es exactamente lo que espera create.php.
| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Colaborador |
fecha_liquidacion | req | Fecha de terminación |
motivo_terminacion | req | Ver catálogos. Solo Despido sin justa causa genera indemnización |
modo_nomina | opc | con_nomina (por defecto) descuenta los días que la nómina del mes ya pagó · sin_nomina paga el periodo completo |
bonificaciones, otras_deducciones | opc | Importes adicionales |
nominas_vinculadas | opc | Ids de nóminas a descontar. Si lo omites se toman todas las elegibles; si lo envías vacío, ninguna |
modo_nomina decide si el mes se paga dos veces. Con sin_nomina se liquida el periodo entero: si esa nómina ya se pagó, estos importes la pagan por segunda vez. Ojo, el valor por defecto no es el mismo en calculate que en create — indícalo siempre de forma explícita.Cálculo masivo. Campo collaborator_ids req (array real). No acepta nominas_vinculadas.
bonificaciones y otras_deducciones se aplican a CADA colaborador, no se reparten entre ellos. La respuesta lo avisa en avisos[].Guarda la liquidación en estado Borrador. Reenvía aquí la salida de calculate.php.
| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Colaborador |
contrato_laboral_id | req | Formalmente opcional, pero omitirlo hace que al aprobar se terminen TODOS los contratos vigentes del colaborador |
fecha_ingreso, fecha_liquidacion, motivo_terminacion | opc | Se guardan sin validar |
| ~30 campos de importes | opc | cesantias, intereses_cesantias, prima_s1, prima_s2, vacaciones, indemnizacion, dotacion_pendiente, salud_empleado, pension_empleado, fsp, total_devengado, total_deducido, neto_a_pagar… se guardan tal cual, sin recalcular ni verificar que cuadren |
detalle_mensual, detalle_deducciones, vacaciones_periodos | opc | Cadenas JSON, no estructuras anidadas |
nominas_vinculadas_detalle | opc | Solo se lee con modo_nomina=con_nomina. Sub-campos: nomina_id, dias, dias_auxilio, total |
detalle_deducciones se registra un abono provisional y baja de inmediato el saldo del préstamo, para que la nómina del mes no descuente lo mismo dos veces.
Solo cabe una liquidación activa por colaborador; hay que anular la anterior para crear otra.
Aprueba la liquidación: termina el contrato, consolida préstamos, compensa dotación y contabiliza.
| Campo | Descripción | |
|---|---|---|
id | req | Debe estar en Borrador |
pago_opcion | opc | ahora · fecha · despues (por defecto) |
cuenta_bancaria_id | opc | Necesaria con ahora/fecha. Si falta, el pago se omite en silencio |
fecha_pago | opc | Obligatoria con fecha |
auto_pila | opc | 1 solo prevalida los códigos de seguridad social; no genera la planilla |
Aprobada sin comprobante.Simulacro de eliminación, igual que el de nómina: se_deshace[], no_se_deshace[] y bloqueado. No modifica nada.
Elimina revirtiendo todos los efectos. Campos: id req, motivo req (mínimo 10 caracteres). Requiere permiso de eliminación.
Anula sin borrar: revierte todo pero conserva el registro en estado Anulada. Campos: id req, motivo opc. Requiere permiso de eliminación.
avisos[].Prestaciones sociales
Cesantías, intereses, prima por semestre y vacaciones compensadas.
calcular_prestacion → register_payment. La vista previa y la escritura usan la misma función, así que el importe que ves es exactamente el que se guarda.Saldo causado, pagado y pendiente por concepto. Campos: collaborator_id opc, year opc.
collaborator_id cambia la forma de la respuesta: pasa a modo agregado y desaparecen salario_actual, fecha_ingreso, periodos_vacaciones y otras diez claves. En ese modo los contadores de días se suman entre personas y no significan nada.
Usa
prima.s1 y prima.s2; los escalares sueltos de prima son heredados del modelo anual antiguo.Previsualiza el pago de una prestación.
| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Colaborador |
concepto | req | Ver catálogos |
periodo_year | req | Entre 2000 y 2100 |
fecha_corte | opc | Por defecto la legal, pero nunca posterior a hoy |
{
"status": "success",
"data": {
"concepto": "cesantias", "concepto_label": "Cesantías",
"dias": 120, "base_calculo": 2749095, "valor": 916365,
"formula": "(2749095 × 120) / 360",
"base_detalle": [
{ "concepto": "Salario mensual", "valor": 2500000 },
{ "concepto": "Auxilio de transporte", "valor": 249095 }
],
"fecha_corte": "2026-04-30", "dias_base": 360,
"ya_pagado": 0, "plazo_pago_legal": "2027-02-14",
"norma": "Art. 249 CST y Ley 50 de 1990: se consignan al fondo a más tardar el 14 de febrero.",
"avisos": []
}
}
Lo mismo para toda la plantilla. Campos: concepto req, periodo_year req, fecha_corte opc. Devuelve elegibles[] y omitidos[] con su motivo. Puede tardar: cuenta ~1 segundo por colaborador.
Registra el pago. Campos: collaborator_id req, concepto req, periodo_year req, fecha_pago req, fecha_corte, fondo_cesantias, observaciones opc.
valor no tiene efecto.
La clave de duplicado es concepto + año + semestre + fecha de corte, a propósito: así un retiro parcial de cesantías y la consignación de febrero del mismo año pueden convivir.
No genera asiento contable ni movimiento de caja: solo alimenta el saldo y la liquidación.
Registra el pago para toda la plantilla, en una sola transacción.
status: "error" aunque la transacción se haya confirmado, y con data lleno. Ramifica por los contadores de data.Histórico de pagos. Campos: collaborator_id, concepto, year, page. Página fija de 20.
Borra un pago registrado. Campo: id req.
Recalcula las provisiones de un año. Campo: year opc.
Préstamos y deducciones
plan_preview → create → payment. Previsualiza siempre: es el único que avisa si el plan supera el tope de 240 cuotas.Calcula el plan de cuotas sin guardar nada.
| Campo | Descripción | |
|---|---|---|
monto_original | req | Envía enteros: aquí los puntos decimales se eliminan |
fecha_inicio | req | Primera cuota |
modo | opc | cuotas (por defecto) · fecha |
cuota_mensual / numero_cuotas | opc | Uno de los dos en modo cuotas. Si envías ambos, manda la cuota |
fecha_fin | opc | Obligatoria en modo fecha |
periodo_pago | opc | mensual (por defecto) · quincenal |
{
"status": "success",
"data": {
"numero_cuotas": 6, "cuota": 200000, "ultima_cuota": 200000,
"total_plan": 1200000, "fecha_fin_estimada": "2026-07-15", "periodo_pago": "mensual",
"cuotas": [
{ "numero_cuota": 1, "fecha_vencimiento": "2026-02-15",
"valor": 200000, "valor_abonado": 0, "estado": "Pendiente" }
]
}
}
ultima_cuota puede diferir de cuota: ahí se concentra el residuo para que el plan sume el monto exacto.Registra un préstamo, libranza o embargo.
| Campo | Descripción | |
|---|---|---|
collaborator_id, monto_original, fecha_inicio | req | Datos base |
tipo | req | prestamo_empresa · libranza · embargo |
cuota_mensual / numero_cuotas | opc | Al menos uno |
periodo_pago, descripcion, observaciones | opc | Condiciones |
pago_opcion | opc | Por defecto ahora, a diferencia del resto de módulos |
cuenta_bancaria_id, fecha_pago | opc | Para el desembolso |
pago_opcion. Omitirlo y enviar cuenta_bancaria_id contabiliza un desembolso real de caja. Solo prestamo_empresa genera asientos; libranzas y embargos no.
Si el plan supera 240 cuotas, el préstamo se crea igualmente pero sin plan, y el error no se reporta. Previsualiza antes.
Registra un abono. Campos: prestamo_id req, valor req (no puede exceder el saldo), fecha_pago, observaciones, nomina_id opc.
Activo. Uno que una liquidación dejó en Cancelado se rechaza a propósito. No existe endpoint para anular un abono.Lista préstamos. Campos: collaborator_id, tipo, estado, page. Trae alerta_pago (proximo/vencido) y dias_para_pago con signo.
Abonos de un préstamo. En los descontados por liquidación, periodo, mes y anio vienen vacíos.
Elimina el préstamo y sus abonos. Campo: id req.
{
"status": "error", "code": "prestamo_referenciado",
"message": "No se puede eliminar el préstamo: tiene abonos ligados a liquidación #42 (Aprobada)...",
"bloqueos": ["liquidación #42 (Aprobada), 300.000 descontados", "2 abono(s) descontados en nómina"]
}
Dotación
Entregas de dotación por cuatrimestre (Art. 230-235 CST).
generate → checklist_get → deliver → send_email (opcional). pending alimenta la compensación de la liquidación.Programa las entregas del año. Campos: year opc, collaborator_id opc.
valor_estimado es el 5% del salario: una cifra de referencia, no un coste real.
Es idempotente, pero re-ejecutarlo tras un aumento de sueldo no refresca los valores ya creados. Los colaboradores sin fecha de ingreso se saltan sin contarse.
Entregas del año. Campos: collaborator_id, year (siempre se aplica: por defecto el año actual), estado. Incluye un resumen por estado.
Entregas pendientes de todos los años, con total_compensacion. Es lo que se envía como dotacion_pendiente a la liquidación.
Ítems de una entrega o de una plantilla. Campos: entrega_id, plantilla_id opc.
id; los de una plantilla traen requiere_talla y no traen ids. Usa el campo source (entrega/plantilla/empty) para distinguirlos.Marca la entrega como realizada. Campos: id req (debe estar Pendiente), fecha_entrega, descripcion, observaciones, checklist (cadena JSON), pago_opcion, cuenta_bancaria_id, fecha_pago.
Sub-campos de checklist[]: item_nombre req, categoria, cantidad, talla, entregado, observaciones.
Enviar
checklist vacío no toca los ítems; enviarlo con contenido los borra y reemplaza.Envía el acta al colaborador para firma digital. Campos: id req, email opc.
Elimina una entrega. Campo: id req.
Plantillas de dotación
Campo: solo_activas=1 opc. Aquí la clave del ítem es nombre (en checklist_get el mismo dato se llama item_nombre).
Crea o actualiza. Campos: id (0 = crear), nombre req, descripcion, activa, items (cadena JSON: nombre, categoria, requiere_talla, cantidad_default).
items es reemplazo total, no fusión: omitirlo al actualizar borra todos los ítems y la respuesta sigue diciendo que se guardó correctamente.Campo: id req.
Expediente del colaborador
Incapacidades
Registra una incapacidad y la sincroniza con la nómina abierta del periodo.
| Campo | Descripción | |
|---|---|---|
collaborator_id | req | Colaborador |
tipo | req | Común o Laboral. Con tilde: escribirlo sin ella lo hace pagar al 100% como si fuera laboral |
fecha_inicio, fecha_fin | req | Los días se calculan solos, ambos extremos incluidos |
diagnostico, entidad_responsable, numero_incapacidad, estado | opc | Datos del soporte |
valor_reconocido | opc | Solo documental: el valor que entra en nómina lo calcula el servidor |
documento_soporte | opc | Fichero, máx. 10 MB (multipart/form-data) |
{
"status": "success",
"message": "Incapacidad registrada correctamente y sincronizada con nómina del período",
"data": { "id": "12", "dias": 5, "synced_nomina_id": "88" }
}
Común días 1–90 a dos tercios del salario diario, días 91 en adelante a la mitad, siempre con el mínimo del salario mínimo diario. Laboral al 100% (ARL).
Si
synced_nomina_id viene vacío, no había nómina abierta y la incapacidad no descuenta todavía.Lista las incapacidades del colaborador.
Actualización parcial: solo se escriben los campos presentes. Campo id req.
Campo: id req.
Memorandos
Crea un memorando y envía el enlace de firma al colaborador.
| Campo | Descripción | |
|---|---|---|
collaborator_id, asunto, fecha | req | Datos base |
tipo | req | Llamado de atención · Descargo · Suspensión · Acta de compromiso · Felicitación · Otro |
suspension_inicio, suspension_fin | opc | Solo se leen con tipo=Suspensión; en el resto se descartan sin avisar |
descripcion, documento_soporte | opc | Texto y adjunto (máx. 10 MB) |
Suspensión afecta a la nómina: genera una licencia no remunerada que quita esos días del pago y de la causación de prestaciones.
En entorno de pruebas el correo no llega al colaborador, aunque la respuesta diga que se envió.
Lista los memorandos.
Campo: id req.
Plantilla de la carta. Campo tipo opc. El campo source indica si es propia (custom) o la interna (default). Marcadores disponibles: {{nombre_empresa}}, {{nit_empresa}}, {{nombre_colaborador}}, {{identificacion}}, {{cargo}}, {{tipo}}, {{asunto}}, {{descripcion}}, {{fecha}}.
Campos: contenido_template req, template_id, nombre, tipo_memorando opc.
template_id inexistente responde éxito sin guardar nada. Y tipo_memorando solo se puede fijar al crear: en las actualizaciones se ignora.Documentos
multipart/form-data. Campos: collaborator_id req, tipo_documento req (cedula, eps, arl, pension, ccf, examen_medico, otro), fichero en archivo req, más observaciones y fecha_documento opc.
Máximo 10 MB. Formatos: PDF, JPEG, PNG, WebP y Word.
Lista los documentos. No enlaces a ruta_archivo directamente: usa download.php.
Descarga el fichero.
403, 404 o 500 con el mensaje en texto plano. Es la excepción a la regla de que todo llega como 200.Campo: id req. Irreversible.
Certificado de ingresos y retenciones (Formulario 220)
action | Campos | Devuelve |
|---|---|---|
collaborators | — | Colaboradores con nómina |
years | — | Años con nómina |
history | anio | Certificados ya generados |
generate | collaborator_id req, anio | Un PDF, no JSON |
batch_generate | anio — requiere POST | Solo registra historial |
batch_generate no produce ningún PDF pese a su nombre: solo deja constancia en el historial. Para obtener los documentos hay que llamar a generate colaborador por colaborador.
generate escribe en el historial antes de renderizar: descargar un certificado modifica la auditoría.PILA / Seguridad social
Generación, validación y pago de la planilla integrada de liquidación de aportes, contra el operador SuAporte.
1. generate → escribe el archivo plano estado: generada
2. validate → obtiene código y número estado: validada
3. inconsistencies → necesita el CÓDIGO
autocorrect → necesita el CÓDIGO estado: corregida
4. totals → necesita el NÚMERO
payment_url → necesita el NÚMERO
mark_assisted → necesita el NÚMERO, escribe el PIN
5. pay → necesita el PIN estado: pagada
query_payment → necesita el PIN
6. reverse_payment → exige estado 'pagada' estado: aprobada
suporte_codigo_planilla y suaporte_numero_planilla son campos distintos, y cada grupo de endpoints usa uno u otro. Nada en la API pone el estado aprobada en el camino de ida: solo lo escribe reverse_payment.Genera el archivo plano (Resolución 2388 de 2016).
| Campo | Descripción | |
|---|---|---|
periodo_year, periodo_month | opc | Por defecto el mes en curso. Año entre 2020 y 2099 |
nomina_ids | opc | Nóminas a consolidar. Todas deben ser del mismo mes |
tipo_planilla | opc | Por defecto E |
{
"status": "error",
"message": "Hay 2 colaborador(es) sin códigos de SS completos. Sincronice BDUA para EPS/AFP.",
"data": { "incomplete": [
{ "id": 90, "nombre": "Juan Diaz", "identification": "79123456",
"missing": ["EPS", "AFP"], "bdua_last_sync": null }
]}
}
download.php.
No se puede generar una segunda planilla del periodo mientras exista otra que no esté en error.
Genera la planilla a partir de una nómina. Campos: nomina_id req, tipo_planilla opc.
status: "info"), vinculada a una existente, generada, o status: "needs_selection" cuando la nómina es quincenal y hay otras del mismo mes. En ese caso hay que reenviar a generate.php con las nomina_ids elegidas.Planilla de retiro. Campo: liquidacion_id req (debe estar Aprobada). Los días se topan en 30 por norma (Decreto 1990 de 2016).
Valida el archivo contra SuAporte. Campo: planilla_id req. Devuelve codigo_planilla, numero_planilla, contadores e inconsistencias[].
Corrección automática. Campo: planilla_id req.
corregida, pero la nómina vinculada sigue reportando validada.Inconsistencias paginadas. Campos: planilla_id req, page, limit (máx. 500).
Totales de la planilla según el operador. Requiere el número. Esta consulta guarda los totales, así que también escribe.
Marca la planilla como asistida y obtiene el PIN. Campos: planilla_id req, causal opc.
Paga la planilla. Campo: planilla_id req (debe tener PIN).
URL de pago por PSE. También guarda la URL obtenida.
Consulta el estado del pago.
estado_planilla y pago_estado son los valores locales; payment_info viene del operador. Pueden discrepar.Reversa el pago. Campo: planilla_id req (estado exactamente pagada).
Lista planillas. Campos: periodo_year, periodo_month, estado, page, limit (máx. 100).
Cabecera y detalle por colaborador, con IBC y cotizaciones.
Descarga el archivo plano. No devuelve JSON: transmite el fichero de texto.
Nóminas consolidables del periodo. Campos: periodo_year, periodo_month. Incluye is_complete_month.
Estado de sincronización. Campos: module req (nomina, prestaciones o liquidacion), más nomina_id, liquidacion_id, year, month según el módulo.
Marcación asistida masiva
Solo multipart/form-data — este endpoint no acepta JSON ni con API Key. Fichero Excel en archivo req. Devuelve un id_archivo que hay que consultar después.
Estado del proceso masivo.
Afiliaciones (BDUA / RUAF)
Consulta la afiliación de un colaborador y actualiza su EPS y AFP. Campo: collaborator_id req.
confirmado, sin_afiliacion y no_registrado. El campo message se compone dinámicamente: no lo analices, usa estado_eps y estado_afp.Consulta por documento, sin tocar ninguna ficha. Campos: tipo_documento req, numero_documento req.
Sincroniza toda la plantilla. Sin parámetros.
Consulta si toca sincronizar (GET) o la ejecuta (POST con force opcional).
Configuración y catálogos
Configuración de SuAporte, con el PIN enmascarado.
status: "success" con data: null. Contémplalo.Campos: tipo_documento req, numero_documento req, pin y clave_secreta (obligatorios la primera vez; en blanco conservan el valor), aportante_tipo, codigo_arl, codigo_ccf, bdua_auto_sync_days opc.
Prueba la conexión. Sin parámetros. El campo authorized devuelve siempre false y no debe interpretarse.
GET lista el catálogo (filtro tipo: EPS, AFP, ARL, CCF, CES). POST admite action: add, edit o toggle.
CES* (cesantías) son internos: PILA no reporta cesantías y no aparecen en ningún listado oficial.Datos de la empresa como aportante en SuAporte. aportante_create.php y aportante_update.php los crean y actualizan (campo tipo_aportante: pyme por defecto, independiente o corporate).
Asistencia y horarios
Marcación
Registra entrada, salida o descanso. Tiene dos formas de autenticarse, y cada una pide cosas distintas.
Con qr_token | Con API Key / sesión | |
|---|---|---|
| Identificación | identification (el collaborator_id se ignora a propósito) | collaborator_id o identification |
| Obligatorio además | nada | motivo siempre, y pin si la empresa lo configuró |
| Auditoría | no | siempre — se trata como marcación manual |
| Campo | Descripción | |
|---|---|---|
tipo | opc | entrada (por defecto) · salida · descanso_inicio · descanso_fin. Es el único enum validado |
latitud, longitud | opc | Obligatorias si la empresa tiene alguna geocerca activa |
motivo | opc | Obligatorio por la vía autenticada. Entre 5 y 255 caracteres |
pin | opc | Clave de marcación manual, si está configurada |
modo | opc | manual_completo para registrar un día pasado; exige fecha y al menos una hora |
face_snapshot, firma | opc | Imágenes en base64 |
tipo_marcacion, verificacion_tipo, observaciones | opc | Etiquetas libres |
salida para el almuerzo partiría el día en dos y descuadraría el cómputo.
entrada siempre crea una jornada nueva: varias sesiones por día son intencionales.
El objeto
estado de la respuesta es la situación después de marcar, para repintar los botones sin otra llamada.
verificacion_tipo es una etiqueta autodeclarada: el cotejo facial ocurre en el navegador, no en el servidor.Genera el código QR de marcación. Sin collaborator_id devuelve el de empresa (el que se proyecta en la entrada, sirve para toda la plantilla); con él, uno personal.
{
"status": "success",
"data": {
"token": "eyJ1aWQiOjcsInRzIjoxNzg3NTA0OTMxLCJub25jZSI6...",
"url": "https://wardian.com.co/dist/roster/asistencia_qr.php?token=...",
"scope": "empresa", "expires_in": 480, "refresh_in": 40
}
}
expires_in y refresh_in de la respuesta; no los fijes en tu código. Cambian según el alcance y la configuración: por defecto 480 segundos de vigencia para el de empresa y 3600 para los enlaces personales, refrescando cada 40. Refrescar no es caducar: un código que desaparece de pantalla sigue valiendo el resto de su ventana.Código fijo para el cartel impreso. GET lo consulta; POST con accion=rotar lo cambia — y eso invalida todos los carteles ya impresos. Rotar exige ser administrador.
Marcaciones del periodo. Campos: collaborator_id, fecha_desde, fecha_hasta opc (por defecto, el mes en curso). Incluye descansos[], horas_brutas y horas_trabajadas (ya con descansos descontados).
Resumen por colaborador: dias_marcados, total_horas, dias_sin_salida.
total_horas es bruto: no descuenta descansos, al contrario que list.php. Los dos endpoints discreparán para quien tome descansos. Y dias_marcados cuenta registros, no fechas distintas.Corrige una marcación. Campos: id req, motivo req, pin si aplica, más hora_entrada, hora_salida y observaciones opc. Es actualización parcial: lo que omitas se conserva.
Elimina una marcación y sus descansos. Campos: id req, motivo req, pin si aplica.
status: "success" con el mensaje "No encontrada". Comprueba el mensaje.Configura la clave de marcación manual. Campos: pin req (4–20 caracteres; vacío la desactiva), pin_actual req si ya existe una — también para desactivarla. Solo administradores.
Del reloj a la nómina
Calcula horas extra y recargos comparando las marcaciones con el horario. Solo propone, no escribe. Campos: fecha_desde req, fecha_hasta req, collaborator_id opc.
{
"status": "success",
"data": [
{ "collaborator_id": 88, "nombre": "Ana Perez",
"novedades": [
{ "fecha": "2026-08-14", "tipo": "HORA EXTRA DIURNA",
"factor": 25, "horas": 1.5, "valor": 18750, "es_recargo": false }
],
"resumen": { "dias_trabajados": 21, "horas_extra": 6.5 },
"origen_horario": "asignado" }
],
"sin_horario": [ { "collaborator_id": 91, "nombre": "Luis Gomez" } ],
"horario_inferido": [ { "collaborator_id": 90, "nombre": "Juan Diaz" } ]
}
sin_horario[] son los que tienen marcaciones pero nada calculable; horario_inferido[] se calcularon contra un horario deducido, no asignado: revísalos.
El
valor ya incorpora el factor. No lo vuelvas a aplicar.Aplica esas novedades a la nómina. Campos: nomina_id req, novedades req (cadena JSON: el data[] de calculate_extras).
Los ítems malformados se saltan sin avisar: compara el número de inserciones que devuelve con lo que enviaste.
Un colaborador con la lista de novedades vacía no es un caso neutro: limpia las suyas.
Sugiere los días a pagar según las ausencias reales. Campos: nomina_id req, collaborator_id req. Solo lectura.
data.aplica: cuando vale false la respuesta solo trae ese campo y un motivo. No resta domingos, festivos ni días ya justificados por vacaciones o incapacidad.Geocercas
Un endpoint con varias operaciones según action: list, create, update, delete, toggle.
| Campo | Descripción | |
|---|---|---|
nombre | req | En create y update |
poligono | req | Mínimo 3 puntos. Cada punto es [latitud, longitud] — al revés que GeoJSON |
collaborator_ids | opc | A quién aplica. Omitirlo en update borra todas las asignaciones |
color, id | opc | Color y, en update/delete/toggle, el id |
{
"action": "create",
"nombre": "Sede Norte",
"poligono": [[4.7010,-74.0460],[4.7015,-74.0440],[4.6995,-74.0445]],
"collaborator_ids": [88, 90, 91]
}
clock.php para toda la empresa: a partir de ahí toda marcación exige coordenadas y se rechaza fuera de los polígonos. Un polígono mal formado no da error: simplemente nadie podrá marcar.
create devuelve el id nuevo en la raíz de la respuesta, no dentro de data.Envío de enlaces y biometría
Envía el enlace de marcación por correo o WhatsApp. action: destinatarios (simulacro), enviar, marcar_enviado.
Campos: ids req en enviar (cadena JSON, máximo 25 por llamada), canales (email y/o whatsapp), momento, alcance, collaborator_id.
Configura el envío automático. Campos: envio_auto, envio_canal_email, envio_canal_whatsapp, envio_salida, envio_anticipacion_min (5 a 50 minutos). Solo administradores. No tiene consulta GET.
Enrola el patrón facial. Campos: collaborator_id req, descriptor req (cadena JSON con al menos 64 números), source_photo opc. Uno por colaborador: volver a enrolar sobrescribe.
Estado de enrolamiento facial y foto por colaborador. Sin parámetros.
Horarios
Horarios con su detalle diario y descansos. Campo solo_activos=1 opc. Los días van de 1 (lunes) a 7 (domingo).
Crea o actualiza un horario. Campos: id (0 = crear), nombre req, descripcion, detalle (cadena JSON).
Sub-campos de detalle[]: dia_semana req, hora_entrada, hora_salida, es_descanso, almuerzo_inicio, almuerzo_fin, descansos[] (con inicio, fin, nombre).
detalle. Omitir detalle deja el horario vacío.
Las horas semanales se recalculan solas: el valor que envíes se descarta.
Campo: id req. Se bloquea si el horario está asignado a contratos vigentes.
Aprobaciones y portal del empleado
approvals/ no funcionan con API Key. Verifican la sesión antes de resolver la clave, así que una petición autenticada solo con X-API-Key recibe una redirección al login con cuerpo vacío — y un response.json() fallará al analizar el HTML. Son exclusivos de sesión de navegador. A diferencia del resto de nómina, sí devuelven códigos HTTP reales (401, 405, 500).Solicitudes del portal. Campos: type req (vacation, incapacidad, permission), status opc (Pendiente por defecto, Aprobada, Rechazada, Cancelada, all).
status inválido no da error: cae en silencio a Pendiente.Campos: request_type req y request_id req. Ojo: aquí el parámetro se llama request_type, mientras que en list.php es type.
Aprueba la solicitud y la sincroniza con la nómina abierta. Campos: request_type req, request_id req, observaciones opc.
status: "success" no garantiza que la novedad se creara ni que saliera el correo: esos bloques fallan en silencio. Comprueba data.synced_nomina_id.
Solo se pueden aprobar solicitudes en estado
Pendiente. Los permisos no generan novedad de nómina.Campos: request_type req, request_id req, observaciones req (obligatorias al rechazar). No tiene ningún efecto sobre la nómina.
Envía el acceso al portal del empleado. Campos: collaborator_id req (o la palabra __all__), method opc (email por defecto, whatsapp).
Con
method=whatsapp y __all__ solo se prepara un mensaje, no una difusión.
status es "error" si no se envió ningún correo, aunque los contadores de data vengan completos. Ramifica por ellos.Catálogos y estados
Estados de la nómina
| Estado | Cuándo |
|---|---|
Borrador | Al crearla. Es el estado inicial |
Enviada | Tras generarla (desprendibles y contabilidad ejecutados) |
Aprobada / Rechazada | Cuando alguien resuelve el enlace de aprobación |
Ajustada | Tras una nota de ajuste; la nómina vuelve a ser corregible |
Por colaborador, dentro de la nómina: Activo (sin liquidar) → Liquidada → Pagada o Nota ajuste. Los dos últimos son terminales: bloquean el recálculo.
nomina/preview_delete.php.Motivos de terminación (liquidación)
| Valor a enviar | Significado |
|---|---|
Renuncia | Renuncia voluntaria |
Despido justa causa | Despido con justa causa |
Despido sin justa causa | Despido sin justa causa — el único que genera indemnización (Art. 64 CST) |
Mutuo acuerdo | Mutuo acuerdo |
Fin contrato | Terminación de contrato a término fijo |
Fin obra | Terminación de obra o labor |
Periodo de prueba | Terminación en periodo de prueba (sin indemnización, Art. 80 CST) |
Conceptos de prestaciones
| Valor | Concepto | Corte legal | Plazo de pago |
|---|---|---|---|
cesantias | Cesantías | 31 de diciembre | 14 de febrero siguiente |
intereses_cesantias | Intereses sobre cesantías | 31 de diciembre | 31 de enero siguiente |
prima_s1 | Prima, primer semestre | 30 de junio | 30 de junio |
prima_s2 | Prima, segundo semestre | 31 de diciembre | 20 de diciembre |
vacaciones_compensadas | Vacaciones compensadas | a la fecha | — |
Otros enumerados
| Ámbito | Valores |
|---|---|
Préstamos — tipo | prestamo_empresa · libranza · embargo. Al liquidar cobran en ese orden inverso: primero el embargo |
Préstamos — estado | Activo (el único que admite abonos) · Pagado · Cancelado |
Préstamos — periodo_pago | mensual · quincenal |
| Dotación — estados | Pendiente · Entregada (sin vuelta atrás) · Compensada |
| Dotación — cuatrimestres | 1 (30 abr) · 2 (31 ago) · 3 (20 dic) |
Liquidación — estado | Borrador · Aprobada · Pagada · Anulada |
PILA — estado | generada · validada · corregida · aprobada · pagada · error |
| PILA — administradoras | EPS · AFP · ARL · CCF · CES |
Incapacidades — tipo | Común · Laboral (con tilde) |
Memorandos — tipo | Llamado de atención · Descargo · Suspensión · Acta de compromiso · Felicitación · Otro |
Documentos — tipo_documento | cedula · eps · arl · pension · ccf · examen_medico · otro |
Asistencia — tipo | entrada · salida · descanso_inicio · descanso_fin |
Nómina — periodo | 1 mensual · 2 primera quincena · 3 segunda quincena |
Cuentas bancarias — account_type | 1 Ahorros · 2 Corriente |
| Opciones de pago | ahora · fecha · despues |