API de marcayliquida

Tres endpoints POST para conectar tu ERP o tu sistema de nómina con marcayliquida: dar de alta trabajadores (de uno en uno o en lote) y justificar ausencias por rango de fechas, sin entrar al panel.

Autenticación

Todas las peticiones se autentican con la API key de tu empresa. La clave identifica a la empresa: no hace falta enviar el nombre de la empresa en el cuerpo, y no es posible escribir datos de otra empresa con tu clave.

Cabeceras

Envía la clave en Authorization o, si tu sistema sólo admite cabeceras simples, en x-api-key. Las dos formas son equivalentes.

cabeceras
Authorization: Bearer fmk_a1b2c3d4e5f6...
Content-Type: application/json

# alternativa
x-api-key: fmk_a1b2c3d4e5f6...
Content-Type: application/json

Cuida la clave

La clave da acceso de escritura a los datos de tu empresa. Guárdala en el gestor de secretos de tu servidor, nunca en el navegador ni en una app móvil, y no la publiques en repositorios. Si se filtra, pide su revocación y una nueva clave.

Cómo obtener la clave

Solicita tu API key al equipo de marcayliquida indicando el nombre de tu empresa. La clave se muestra una única vez al generarla: guárdala en ese momento, porque después sólo queda registrado su prefijo (por ejemplo fmk_a1b2c3d4) y ya no se puede recuperar. Puedes pedir varias claves (una por integración) y revocarlas de forma independiente.

Convenciones

Reglas comunes a los tres endpoints.

URL base

url base
https://marcayliquida.com/api/v1

Formato

  • Todos los endpoints son POST con cuerpo JSON y Content-Type: application/json.
  • Las fechas van en formato YYYY-MM-DD (por ejemplo 2026-08-05).
  • Los importes son números sin separadores de miles: 1300000, no "1.300.000".
  • Se rechazan los campos desconocidos con 422: así un nombre mal escrito no pasa desapercibido. Revisa error.detalles para ver qué campo sobra.
  • Los campos opcionales aceptan null o cadena vacía; ambos se interpretan como "no enviado".

Respuesta correcta

Siempre incluye "ok": true. Los códigos son 201 cuando se creó algo y 200 cuando la petición se procesó sin crear registros nuevos (por ejemplo una actualización, un validar_solo o un lote con filas omitidas).

Respuesta de error

error 422
{
  "ok": false,
  "error": {
    "codigo": "datos_invalidos",
    "mensaje": "Los datos del trabajador no son válidos.",
    "detalles": [
      { "campo": "salario_base", "mensaje": "El salario base debe ser mayor que cero." },
      { "campo": "fecha_ingreso", "mensaje": "Usa el formato YYYY-MM-DD, por ejemplo 2026-08-05." }
    ]
  }
}

Modo de prueba

Los tres endpoints aceptan "validar_solo": true: validan el cuerpo y responden lo que harían, sin escribir nada. Es la forma recomendada de probar una integración nueva.

Códigos de error

codigoHTTPSignificado
no_autenticado401No se envió la API key.
clave_invalida401La API key no existe, fue revocada o está inactiva.
json_invalido400El cuerpo no es JSON válido o falta el Content-Type.
datos_invalidos422Algún campo no cumple el formato. Revisa "detalles".
documento_duplicado409Ya existe un trabajador con ese documento.
nombre_duplicado409Ya existe un trabajador con ese nombre completo exacto.
limite_plan409El plan contratado no admite más trabajadores.
sede_no_encontrada422La sede enviada no existe en la empresa.
motivo_no_encontrado422El motivo de falta no existe o está inactivo.
conflicto_fechas409La falta se cruza con otra ya registrada del mismo trabajador.
trabajador_no_encontrado404Ningún trabajador de la empresa coincide con los identificadores.
lote_demasiado_grande422El lote supera el máximo de 200 trabajadores.
error_interno500Error inesperado. Reintenta; si persiste, contacta a soporte.

1. Agregar un trabajador

Crea un trabajador con sus datos laborales y, si los tienes, los datos de nómina electrónica.

POST/api/v1/trabajadores

El trabajador queda sin foto

La API crea al trabajador sin foto biométrica (photo_status: "pendiente"). Para que pueda marcar con reconocimiento facial hay que capturarle el rostro una vez desde la cámara del panel; ese paso es el que registra la cara. Mientras no se haga, el trabajador existe, aparece en los listados y puede tener faltas, pero no marca asistencia.

Ejemplo mínimo

Sólo cuatro campos son obligatorios:

curl
curl -X POST https://marcayliquida.com/api/v1/trabajadores \
  -H "Authorization: Bearer $MARCAYLIQUIDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "numero_documento": "1098765432",
    "primer_nombre": "Laura",
    "primer_apellido": "Gómez",
    "salario_base": 1600000
  }'

Respuesta 201

201 Created
{
  "ok": true,
  "trabajador": {
    "estado": "creado",
    "id_trabajador": 481,
    "info_id": 92,
    "nombre_completo": "LAURA GÓMEZ",
    "tipo_documento": "CC",
    "numero_documento": "1098765432",
    "cargo": null,
    "sede_id": null,
    "dias_dominicales": ["Domingo"],
    "foto_pendiente": true,
    "advertencias": [
      "El trabajador queda sin foto biométrica: captúrala desde la cámara del panel para que pueda marcar por reconocimiento facial.",
      "Sin cargo asignado: las horas no se valorizan hasta que el trabajador tenga un cargo con tarifas configuradas."
    ]
  }
}

Guarda id_trabajador: es el identificador que puedes usar después en /api/v1/faltas (aunque también sirve el número de documento).

Ejemplo completo

request completo
{
  "tipo_documento": "CC",
  "numero_documento": "1098765432",
  "primer_nombre": "Laura",
  "segundo_nombre": "Andrea",
  "primer_apellido": "Gómez",
  "segundo_apellido": "Ríos",

  "cargo": "Operario",
  "sede_nombre": "Planta Norte",
  "salario_base": 1600000,
  "valor_hora_ordinaria": 6667,
  "fecha_ingreso": "2026-08-01",
  "tipo_contrato": "INDEFINIDO",
  "dias_dominicales": ["Domingo", "Sábado"],

  "fecha_nacimiento": "1995-03-12",
  "sexo": "F",
  "email": "laura.gomez@empresa.com",
  "celular": "3001234567",
  "departamento_residencia": "Santander",
  "municipio_residencia": "Bucaramanga",
  "direccion_residencia": "Calle 45 # 12-30",
  "departamento_trabajo": "Santander",
  "municipio_trabajo": "Girón",

  "periodicidad_pago": "MENSUAL",
  "forma_pago": "TRANSFERENCIA",
  "auxilio_transporte_aplica": true,
  "salario_integral": false,
  "alto_riesgo": false,
  "subtipo_cotizante": "01",
  "eps_codigo": "EPS037",
  "afp_codigo": "230301",
  "arl_codigo": "14-1",
  "arl_nivel_riesgo": 3,
  "caja_compensacion_codigo": "CCF23",
  "fondo_cesantias_codigo": "230301",
  "banco": "Bancolombia",
  "tipo_cuenta": "AHORROS",
  "numero_cuenta": "12345678901",
  "estado": "ACTIVO",

  "actualizar_si_existe": false
}

Identificación

CampoTipoPor defectoDescripción
numero_documentoobligatoriostringNúmero de documento. Letras, números, puntos y guiones (3 a 20 caracteres). Identifica al trabajador en el resto de la API.
primer_nombreobligatoriostringPrimer nombre.
primer_apellidoobligatoriostringPrimer apellido.
tipo_documentoenum"CC"CC, CE, PA, PEP, PPT, TI, RC o NIT.
segundo_nombrestringSegundo nombre.
segundo_apellidostringSegundo apellido.

Datos laborales

CampoTipoPor defectoDescripción
salario_baseobligatorionumberSalario base mensual en pesos. Debe ser mayor que cero.
cargostringCargo del trabajador. Es el que conecta con las tarifas de Configuración de horas: si el cargo no tiene tarifas, las horas no se valorizan (se avisa en "advertencias").
valor_hora_ordinarianumberValor de la hora ordinaria, si se liquida por hora en lugar de por cargo.
fecha_ingresodatehoyFecha de ingreso a la empresa.
tipo_contratoenum"INDEFINIDO"INDEFINIDO, FIJO, OBRA_LABOR, APRENDIZAJE_LECTIVA, APRENDIZAJE_PRODUCTIVA, PRACTICANTE_SENA o PRESTACION_SERVICIOS.
dias_dominicalesstring[]["Domingo"]Días con recargo dominical (75 % en Colombia). Se aceptan con o sin tilde: "Sabado" o "Sábado". Envía [] si no aplica ninguno.
sede_idnumberId de una sede de la empresa. Excluyente con sede_nombre.
sede_nombrestringNombre de la sede, tal como está registrada en el panel (sin distinguir mayúsculas). Útil cuando tu sistema no conoce los ids.
estadoenum"ACTIVO"ACTIVO, SUSPENDIDO, LICENCIA o RETIRADO.
fecha_retirodateFecha de retiro. No puede ser anterior a fecha_ingreso.
motivo_retirostringMotivo del retiro.

Datos personales y ubicación

CampoTipoPor defectoDescripción
fecha_nacimientodateFecha de nacimiento.
sexoenumM, F o X.
emailstringCorreo del trabajador.
celularstringCelular de contacto.
pais_residenciastring(2)"CO"Código ISO del país de residencia.
departamento_residenciastringDepartamento de residencia.
municipio_residenciastringMunicipio de residencia.
direccion_residenciastringDirección de residencia.
departamento_trabajostringDepartamento donde labora (obligatorio para nómina electrónica).
municipio_trabajostringMunicipio donde labora (obligatorio para nómina electrónica).
direccion_trabajostringDirección del lugar de trabajo.

Nómina, seguridad social y banco

Son opcionales para crear el trabajador, pero la nómina electrónica los exige: si los tienes en tu sistema, mándalos y te ahorras completarlos a mano en el panel.

CampoTipoPor defectoDescripción
periodicidad_pagoenum"MENSUAL"SEMANAL, DECADAL, QUINCENAL o MENSUAL.
forma_pagoenum"TRANSFERENCIA"EFECTIVO, TRANSFERENCIA o CHEQUE.
salario_integralbooleanfalseMarca si el salario es integral.
auxilio_transporte_aplicabooleantrueSi el trabajador tiene derecho a auxilio de transporte.
alto_riesgobooleanfalseActividad de alto riesgo para pensión.
subtipo_cotizantestringSubtipo de cotizante PILA (01, 02, 12, 19, 30, 31, 51, 58…).
eps_codigostringCódigo de la EPS.
afp_codigostringCódigo del fondo de pensiones.
arl_codigostringCódigo de la ARL.
arl_nivel_riesgonumberNivel de riesgo ARL, entero de 1 a 5.
caja_compensacion_codigostringCódigo de la caja de compensación.
fondo_cesantias_codigostringCódigo del fondo de cesantías.
bancostringBanco donde se consigna el pago.
tipo_cuentaenumAHORROS o CORRIENTE.
numero_cuentastringNúmero de cuenta bancaria.

Opciones

CampoTipoPor defectoDescripción
actualizar_si_existebooleanfalseSi el trabajador ya existe (mismo tipo y número de documento en la empresa), actualiza sus datos en lugar de responder 409. La foto biométrica ya capturada no se toca.
validar_solobooleanfalseValida el cuerpo y responde sin crear nada.

Errores frecuentes

  • documento_duplicado (409): ya hay un trabajador con ese documento. Reenvía con "actualizar_si_existe": true si quieres actualizarlo.
  • nombre_duplicado (409): otro trabajador tiene exactamente el mismo nombre completo. Agrega el segundo nombre o el segundo apellido para diferenciarlos.
  • limite_plan (409): el plan contratado no admite más trabajadores.
  • sede_no_encontrada (422): la sede debe existir antes; créala en el panel, en Información de la empresa.

2. Agregar varios trabajadores

Carga masiva: hasta 200 trabajadores por petición, con reporte fila por fila.

POST/api/v1/trabajadores/lote

Cada elemento de trabajadores acepta los mismos campos que POST /api/v1/trabajadores. Las filas se procesan en orden y la respuesta trae un indice que apunta a la posición en el arreglo que enviaste, para que puedas casar cada resultado con tu propia fuente de datos.

Cuerpo de la petición

CampoTipoPor defectoDescripción
trabajadoresobligatorioobjeto[]Lista de trabajadores. Cada elemento admite exactamente los mismos campos del endpoint individual. Entre 1 y 200 por petición.
modoenum"parcial""parcial" crea todo lo que pueda y reporta fila por fila. "estricto" exige que las 200 filas sean correctas: si una falla, no queda ninguna creada.
actualizar_si_existebooleanfalseAplica a todas las filas: actualiza los trabajadores que ya existan.
validar_solobooleanfalseValida las filas y responde el conteo de válidas e inválidas, sin escribir.

Ejemplo

curl
curl -X POST https://marcayliquida.com/api/v1/trabajadores/lote \
  -H "Authorization: Bearer $MARCAYLIQUIDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "modo": "parcial",
    "actualizar_si_existe": false,
    "trabajadores": [
      {
        "numero_documento": "1098765432",
        "primer_nombre": "Laura",
        "primer_apellido": "Gómez",
        "cargo": "Operario",
        "salario_base": 1600000,
        "sede_nombre": "Planta Norte"
      },
      {
        "numero_documento": "1102233445",
        "primer_nombre": "Carlos",
        "segundo_nombre": "Andrés",
        "primer_apellido": "Peña",
        "cargo": "Supervisor",
        "salario_base": 2400000,
        "dias_dominicales": ["Domingo", "Sábado"]
      }
    ]
  }'

Respuesta

201 si todas las filas se crearon; 200 si hubo errores, actualizaciones u omisiones. Revisa siempre resumen.errores: en modo parcial una respuesta 200 puede traer filas fallidas.

200 OK
{
  "ok": true,
  "resumen": { "total": 3, "creados": 2, "actualizados": 0, "errores": 1 },
  "resultados": [
    {
      "indice": 0,
      "estado": "creado",
      "id_trabajador": 481,
      "info_id": 92,
      "nombre_completo": "LAURA GÓMEZ",
      "tipo_documento": "CC",
      "numero_documento": "1098765432",
      "cargo": "Operario",
      "sede_id": 3,
      "dias_dominicales": ["Domingo"],
      "foto_pendiente": true,
      "advertencias": ["El trabajador queda sin foto biométrica: …"]
    },
    {
      "indice": 1,
      "estado": "creado",
      "id_trabajador": 482,
      "info_id": 93,
      "nombre_completo": "CARLOS ANDRÉS PEÑA",
      "tipo_documento": "CC",
      "numero_documento": "1102233445",
      "cargo": "Supervisor",
      "sede_id": null,
      "dias_dominicales": ["Domingo", "Sábado"],
      "foto_pendiente": true,
      "advertencias": []
    },
    {
      "indice": 2,
      "estado": "error",
      "numero_documento": "900",
      "codigo": "datos_invalidos",
      "mensaje": "Los datos del trabajador no son válidos.",
      "detalles": [
        { "campo": "salario_base", "mensaje": "El salario base debe ser mayor que cero." }
      ]
    }
  ]
}

Modos de procesamiento

  • parcial (por defecto): las filas correctas se crean y las que fallan se reportan con su codigo y mensaje. Útil para cargas grandes donde prefieres avanzar y corregir después.
  • estricto: todo o nada. Si alguna fila viene mal formada, responde 422 sin escribir nada; si una escritura falla a mitad de camino, se deshacen las altas de esa misma petición. Útil para sincronizar contra tu sistema de nómina sin dejar estados intermedios.

Documentos repetidos en el mismo lote

Si dos filas traen el mismo tipo y número de documento, la primera se procesa y las siguientes se reportan con documento_duplicado indicando la posición del duplicado. El límite del plan se valida antes de escribir, contando sólo los documentos que aún no existen en la empresa.

3. Registrar un motivo de falta por rango de fechas

Justifica la ausencia de uno o varios trabajadores desde una fecha hasta otra: incapacidades, vacaciones, licencias o permisos.

POST/api/v1/faltas

Una falta cubre el rango completo [fecha_inicio, fecha_fin], ambos días incluidos: no hace falta enviar un registro por día. En el panel, los días cubiertos pasan de Sin justificar a Justificada.

A quién se aplica

Envía una de estas cuatro formas de identificación. Si combinas varias, se unen sin duplicar trabajadores.

CampoTipoDescripción
documentostringNúmero de documento del trabajador. La forma más común.
documentosstring[]Varios documentos: aplica la misma falta y el mismo rango a todos (hasta 200).
id_trabajadornumberId devuelto al crear el trabajador (campo id_trabajador).
id_trabajadoresnumber[]Varios ids.

Datos de la falta

CampoTipoPor defectoDescripción
motivostringCódigo del motivo, por ejemplo INCAPACIDAD_EPS. No distingue mayúsculas. Obligatorio si no envías tipo_falta_id.
tipo_falta_idnumberAlternativa a motivo: el id del tipo de falta.
fecha_inicioobligatoriodatePrimer día de la ausencia.
fecha_findatefecha_inicioÚltimo día de la ausencia, incluido. Si se omite, la falta cubre un solo día. Máximo 366 días de rango.
observacionesstringNota libre (hasta 1000 caracteres): número de incapacidad, quién autorizó, etc.
en_conflictoenum"error""error" falla si ya hay una falta que se cruza; "omitir" deja la existente intacta; "reemplazar" borra las faltas cruzadas (y sus comprobantes) y crea la nueva.
validar_solobooleanfalseValida y responde a quién se aplicaría, sin registrar nada.

Ejemplo: incapacidad de un trabajador

curl
curl -X POST https://marcayliquida.com/api/v1/faltas \
  -H "Authorization: Bearer $MARCAYLIQUIDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documento": "1098765432",
    "motivo": "INCAPACIDAD_EPS",
    "fecha_inicio": "2026-08-10",
    "fecha_fin": "2026-08-15",
    "observaciones": "Incapacidad 4471 - Sanitas"
  }'
201 Created
{
  "ok": true,
  "motivo": {
    "id": 1,
    "codigo": "INCAPACIDAD_EPS",
    "nombre": "Incapacidad EPS",
    "remunerada": true,
    "requiere_soporte": true
  },
  "periodo": { "fecha_inicio": "2026-08-10", "fecha_fin": "2026-08-15", "dias": 6 },
  "resumen": {
    "total": 1,
    "creadas": 1,
    "reemplazadas": 0,
    "omitidas": 0,
    "errores": 0,
    "no_encontrados": 0
  },
  "resultados": [
    {
      "estado": "creada",
      "id_trabajador": 481,
      "nombre": "LAURA GÓMEZ",
      "documento": "1098765432",
      "falta_id": 55
    }
  ],
  "advertencias": [
    "El motivo \"Incapacidad EPS\" requiere comprobante: súbelo desde Faltas en el panel para que la ausencia quede soportada."
  ]
}

Ejemplo: vacaciones colectivas de varios trabajadores

request
{
  "documentos": ["1098765432", "1102233445", "1103344556"],
  "motivo": "VACACIONES",
  "fecha_inicio": "2026-12-20",
  "fecha_fin": "2027-01-05",
  "observaciones": "Cierre de fin de año",
  "en_conflicto": "omitir"
}

Con varios trabajadores la respuesta trae una entrada por persona en resultados, con su estado (creada, reemplazada, omitida o error). Los documentos que no existen en la empresa no rompen la petición: se listan en advertencias y se cuentan en resumen.no_encontrados. Si ninguno de los identificadores corresponde a un trabajador de la empresa, la respuesta es 404.

Cruces de fechas

Un trabajador no puede tener dos faltas que se crucen. Cuando el rango que envías se solapa con una falta existente, en_conflicto decide el comportamiento:

  • error (por defecto): responde 409 conflicto_fechas indicando los rangos que estorban. Nada se modifica.
  • omitir: respeta la falta existente y marca a esa persona como omitida. Es lo recomendable en procesos que se reejecutan (idempotencia).
  • reemplazar: borra las faltas cruzadas junto con sus comprobantes y crea la nueva. Devuelve los ids borrados en faltas_reemplazadas.

reemplazar borra comprobantes

Los archivos de soporte de las faltas reemplazadas se eliminan del almacenamiento y no se pueden recuperar. Úsalo sólo cuando tu sistema sea la fuente de verdad de las ausencias.

Motivos disponibles

Envía el codigo tal cual aparece en esta tabla. Si tu empresa tiene motivos propios configurados en el panel, también funcionan con su código.

Código (motivo)NombreRemuneradaRequiere comprobante
AUSENCIA_INJUSTIFICADAAusencia injustificadaNoNo
CALAMIDAD_DOMESTICACalamidad domésticaNo
INCAPACIDAD_ARLIncapacidad ARL
INCAPACIDAD_EPSIncapacidad EPS
LICENCIA_LUTOLicencia por luto
LICENCIA_MATERNIDADLicencia de maternidad
LICENCIA_PATERNIDADLicencia de paternidad
PERMISO_NO_REMUNERADOPermiso no remuneradoNoNo
PERMISO_REMUNERADOPermiso remuneradoNo
SUSPENSIONSuspensiónNo
VACACIONESVacacionesNo

Comprobantes

Los motivos con requiere_soporte necesitan un archivo de respaldo (incapacidad, licencia…). La API registra la falta, pero el comprobante se sube desde la sección Faltas del panel: mientras no exista, la falta aparece marcada como sin soporte.

Ejemplos de integración

Dos formas típicas de llamar la API desde tu servidor.

Node.js

node
const API = 'https://marcayliquida.com/api/v1';
const CLAVE = process.env.MARCAYLIQUIDA_API_KEY;

async function llamar(ruta, cuerpo) {
  const respuesta = await fetch(API + ruta, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${CLAVE}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(cuerpo),
  });

  const datos = await respuesta.json();
  if (!respuesta.ok) {
    // datos.error.codigo es estable; datos.error.detalles dice qué campo falló.
    throw new Error(`${datos.error.codigo}: ${datos.error.mensaje}`);
  }
  return datos;
}

// Alta de un trabajador
const { trabajador } = await llamar('/trabajadores', {
  numero_documento: '1098765432',
  primer_nombre: 'Laura',
  primer_apellido: 'Gómez',
  cargo: 'Operario',
  salario_base: 1600000,
});

// Vacaciones de ese trabajador
await llamar('/faltas', {
  id_trabajador: trabajador.id_trabajador,
  motivo: 'VACACIONES',
  fecha_inicio: '2026-12-20',
  fecha_fin: '2027-01-05',
  en_conflicto: 'omitir',
});

Python

python
import os
import requests

API = "https://marcayliquida.com/api/v1"
CABECERAS = {
    "Authorization": f"Bearer {os.environ['MARCAYLIQUIDA_API_KEY']}",
    "Content-Type": "application/json",
}

def llamar(ruta: str, cuerpo: dict) -> dict:
    respuesta = requests.post(API + ruta, json=cuerpo, headers=CABECERAS, timeout=30)
    datos = respuesta.json()
    if not respuesta.ok:
        error = datos["error"]
        raise RuntimeError(f"{error['codigo']}: {error['mensaje']}")
    return datos

# Carga masiva desde tu nómina, en bloques de 200
def cargar(trabajadores: list[dict]) -> None:
    for inicio in range(0, len(trabajadores), 200):
        bloque = trabajadores[inicio : inicio + 200]
        datos = llamar(
            "/trabajadores/lote",
            {"trabajadores": bloque, "modo": "parcial", "actualizar_si_existe": True},
        )
        for fila in datos["resultados"]:
            if fila["estado"] == "error":
                print(inicio + fila["indice"], fila["codigo"], fila["mensaje"])

Recomendaciones

  • Prueba primero con "validar_solo": true y revisa la respuesta antes de escribir datos reales.
  • Para procesos que se reejecutan (sincronizaciones nocturnas), usa "actualizar_si_existe": true en trabajadores y "en_conflicto": "omitir" en faltas: así repetir la petición no duplica ni rompe nada.
  • Guarda el id_trabajador que devuelve la API junto al identificador de tu sistema: simplifica las llamadas posteriores.
  • Ante un 500 o un corte de red, reintenta con espera progresiva; los errores 4xx no se resuelven reintentando, hay que corregir el cuerpo.

Soporte

¿Necesitas una clave, un motivo de falta propio o un endpoint que no está aquí? Escríbenos desde la página de contacto indicando el nombre de tu empresa y lo que quieres automatizar.