Ir al contenido

Nómina: consultas y borradores

La API permite consultar nómina, descargar recibos CFDI tipo N y crear o duplicar lotes como borradores. Revisa el borrador antes de timbrar.

Las consultas y descargas requieren payroll:read; crear y duplicar requieren payroll:write. Ambas necesitan acceso al módulo de nómina de Plan Miau. Un Despacho en modo de solo lectura conserva las consultas y descargas, pero no puede crear borradores. Los miembros sin permiso de escritura tampoco pueden crearlos. El Plan API no permite crear llaves con permisos payroll:*.

Las escrituras REST y MCP usan la misma habilitación de entorno: las credenciales live necesitan que esté habilitado MCP_LIVE_ENABLED. Las credenciales de prueba no habilitan escrituras de nómina contra el PAC de producción.

Autentica con X-API-Key. La llave consulta la cuenta de su propietario. Estas rutas también aceptan Authorization: Bearer <token> con el token OAuth de FactuCat usado por MCP. OAuth consulta el espacio seleccionado al autorizar la conexión y comprueba la membresía en cada solicitud. Un encabezado de tenant no cambia ese espacio.

Sin el permiso requerido, la respuesta es HTTP 403 con error.code: "missing_scope". Sin acceso al módulo, es HTTP 403 con error.code: "plan_required" y error.reason: "payroll_requires_miau".

La base es /api/v1. Usa el ID de FactuCat, no el UUID fiscal del recibo.

Método y ruta Filtros
GET /employees status: active o inactive; q: nombre, RFC o número de empleado
GET /employees/{id} Sin filtros
GET /payroll/batches status, period_from, period_to
GET /payroll/batches/{id} Sin filtros; incluye los recibos del lote
GET /payroll/receipts batch_id, employee_id, status, period_from, period_to, paid_from, paid_to
GET /payroll/receipts/{id} Sin filtros; incluye conceptos, importes y evidencia de cancelación
GET /payroll/receipts/{id}/xml Sin filtros
GET /payroll/receipts/{id}/pdf Sin filtros

Los estados de lote son draft, stamping, partially_stamped, stamped, cancelled y error. Los estados de recibo son draft, ready, stamping, stamped, error y cancelled.

Las fechas tienen formato AAAA-MM-DD. period_from limita el inicio del periodo y period_to limita su fin: el periodo completo debe quedar dentro del rango. paid_from y paid_to incluyen ambas fechas de pago. Los filtros se combinan; se aplican antes de paginar. Los recibos sin fecha de pago quedan fuera de un filtro de pago.

La respuesta usa el sobre estándar: data y meta.requestId. Los tres listados aceptan limit de 1 a 100, con 50 por defecto, y cursor. La primera solicitud omite el cursor.

{
"data": [],
"meta": {
"requestId": "solicitud-123",
"pagination": {
"limit": 50,
"offset": 0,
"hasMore": false,
"nextCursor": null
}
}
}

Cuando hasMore es true, envía nextCursor como cursor en la siguiente solicitud y conserva los filtros. El orden es por fecha de creación descendente y por ID descendente. Los cursores representan posiciones: los cambios en los datos entre solicitudes pueden desplazar las páginas.

CURP, NSS y cuenta bancaria muestran solo los últimos cuatro caracteres, por ejemplo ••••••8901. Los salarios y las percepciones siguen visibles. NSS y cuenta bancaria ausentes se devuelven como null.

Los archivos fiscales conservan sus datos completos. XML y PDF se entregan como JSON con data.fileName, data.mimeType y data.base64. Decodifica base64 para obtener el archivo.

POST /payroll/batches requiere Idempotency-Key y devuelve HTTP 201 con el lote y sus recibos, igual que GET /payroll/batches/{id}. El lote queda en draft. Un recibo puede quedar en ready si tiene los datos necesarios; eso no significa que ya esté timbrado.

Ventana de terminal
curl "$FACTUCAT_API_ORIGIN/api/v1/payroll/batches" \
-H "X-API-Key: $FACTUCAT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: nomina-2026-10-segunda-quincena" \
--data '{
"periodStart": "2026-10-16",
"periodEnd": "2026-10-31",
"paymentDate": "2026-10-30",
"payrollType": "ordinaria",
"employees": [{
"employeeId": "11111111-1111-4111-8111-111111111111",
"inputMode": "gross",
"amount": 9000,
"otherPerceptions": [{
"kind": "perception",
"humanCode": "BONO",
"amount": 500
}]
}]
}'

Sustituye employeeId por el ID de un empleado activo del espacio. Las tres fechas son obligatorias. Solo se admite payrollType: "ordinaria"; puedes omitirlo. title es opcional y, si lo omites, se genera con el periodo.

Cada empleado puede indicar inputMode: "net" o "gross". Por defecto se usa net; si omites amount, se toma su salario configurado para ese modo. Un empleado inactivo, de otro espacio o sin un importe válido produce validation_error. La operación guarda el lote completo o revierte sus cambios, incluidos los folios.

Para reutilizar el último recibo timbrado de cada empleado, envía copyFromPreviousReceipt: true en el lote o en ese empleado. Se copian el modo, el importe de entrada y los conceptos adicionales. Los valores que envíes explícitamente reemplazan los copiados; una lista vacía elimina los conceptos adicionales de ese tipo. Si no existe un recibo timbrado anterior, la operación falla con validation_error.

otherPerceptions, otherDeductions y otherPayments usan los mismos conceptos del editor de borradores: kind, humanCode y amount, con satCode, description, taxableAmount, exemptAmount y editable opcionales. El kind debe ser perception, deduction u other_payment, respectivamente. Salario base, ISR, IMSS y subsidio se calculan automáticamente; no se aceptan como conceptos adicionales. Para importes o conceptos distintos por empleado, usa el cuerpo JSON o --file en la CLI.

POST /payroll/batches/{id}/duplicate crea otro borrador con los empleados, modo, importes de entrada y conceptos adicionales de los recibos del lote de origen. Recalcula para las nuevas fechas con los datos actuales de los empleados y asigna folios nuevos.

{
"periodStart": "2026-10-16",
"periodEnd": "2026-10-31",
"paymentDate": "2026-10-30",
"employeeIds": ["11111111-1111-4111-8111-111111111111"]
}

Envía también Idempotency-Key. employeeIds es opcional: omítelo para copiar todos los recibos no cancelados o úsalo para duplicar solo algunos empleados, incluso uno de régimen 02. title también es opcional.

El origen puede estar timbrado o cancelado, pero siempre se excluyen sus recibos cancelados. Seleccionar explícitamente un empleado cuyo recibo está cancelado produce validation_error. También se rechazan lotes sin recibos elegibles, empleados que no pertenecen al origen y empleados que ya están inactivos. Un lote de otra cuenta devuelve not_found.

Crear y duplicar requieren una clave de idempotencia. Reutiliza la misma clave, ruta y contenido al reintentar: recibirás el mismo lote. Cambiar el contenido con esa clave devuelve HTTP 409, idempotency_conflict; si la operación sigue en curso, devuelve idempotency_in_progress. Guarda la clave antes de enviar la solicitud.

Revisa los totales, calculationWarnings, isLegallyCompleteForStamp y el status de cada recibo. Crear o duplicar no timbra ni envía recibos. El timbrado es un paso separado después de la revisión.

Las herramientas payroll_batch_create y payroll_batch_duplicate requieren payroll:write e idempotency_key. Usan los mismos campos de los cuerpos REST; duplicar añade sourceBatchId para identificar el origen.

{
"name": "payroll_batch_duplicate",
"arguments": {
"sourceBatchId": "22222222-2222-4222-8222-222222222222",
"periodStart": "2026-10-16",
"periodEnd": "2026-10-31",
"paymentDate": "2026-10-30",
"employeeIds": ["11111111-1111-4111-8111-111111111111"],
"idempotency_key": "duplicar-nomina-2026-10-segunda-quincena"
}
}

La respuesta contiene el lote, recibos, totales, advertencias, estados de preparación e identificadores sensibles enmascarados. Incluye una nota para que el usuario revise el borrador. Solo después de esa revisión llama payroll_batch_stamp, que conserva su propio paso de confirmación.

El cliente de la guía del SDK expone estos métodos:

const empleados = await factucat.employees.list({ status: 'active', limit: 20 })
const empleado = await factucat.employees.get(employeeId)
const lotes = await factucat.payroll.batches.list({ period_from: '2026-10-01' })
const lote = await factucat.payroll.batches.get(batchId)
const borrador = await factucat.payroll.batches.create({
periodStart: '2026-10-16',
periodEnd: '2026-10-31',
paymentDate: '2026-10-30',
employees: [{ employeeId, inputMode: 'gross', amount: 9000 }],
}, { idempotencyKey: 'crear-quincena-octubre-2' })
const copia = await factucat.payroll.batches.duplicate(batchId, {
periodStart: '2026-10-16',
periodEnd: '2026-10-31',
paymentDate: '2026-10-30',
employeeIds: [employeeId],
}, { idempotencyKey: 'copiar-quincena-octubre-2' })
const recibos = await factucat.payroll.receipts.list({
batch_id: batchId,
status: 'stamped',
paid_from: '2026-10-01',
paid_to: '2026-10-31',
})
const recibo = await factucat.payroll.receipts.get(receiptId)
const xml = await factucat.payroll.receipts.getXml(receiptId)
const pdf = await factucat.payroll.receipts.getPdf(receiptId)

Los listados de nómina del SDK devuelven { data, pagination }. Por ejemplo, recibos.pagination.nextCursor permite solicitar la siguiente página. Los métodos individuales devuelven el registro o archivo. El SDK genera una clave de idempotencia si la omites y la conserva en sus reintentos de red. Para reintentar la misma operación en otra llamada, pasa una clave propia.

Ventana de terminal
factucat employees list --status active --q Lucía --limit 20 --json
factucat employees get <employeeId> --json
factucat payroll batches list --period-from 2026-10-01 --json
factucat payroll batches get <batchId> --json
factucat payroll batches create --period-start 2026-10-16 --period-end 2026-10-31 --payment-date 2026-10-30 --employee <employeeId> --amount 9000 --input-mode gross --json
factucat payroll batches create --period-start 2026-10-16 --period-end 2026-10-31 --payment-date 2026-10-30 --employee <employeeId> --copy-previous --json
factucat payroll batches create --file batch.json --idempotency-key crear-quincena-octubre-2 --json
factucat payroll batches duplicate <batchId> --period-start 2026-10-16 --period-end 2026-10-31 --payment-date 2026-10-30 --employee <employeeId> --json
factucat payroll receipts list --batch-id <batchId> --status stamped --json
factucat payroll receipts list --paid-from 2026-10-01 --paid-to 2026-10-31 --cursor 20 --limit 20 --json
factucat payroll receipts get <receiptId> --json
factucat payroll receipts xml <receiptId> --output recibo.xml
factucat payroll receipts pdf <receiptId> --output recibo.pdf

--json devuelve los datos completos de la respuesta del SDK. En descargas, --json sin --output devuelve el contenido base64 sin guardar un archivo. Con ambas opciones, guarda el archivo y añade savedPath al JSON. Sin estas opciones, guarda el documento en el directorio actual.

En creación y duplicación, --employee se puede repetir. --amount y --input-mode se aplican a todos los empleados seleccionados; --copy-previous usa sus últimos recibos timbrados. El archivo batch.json tiene el mismo cuerpo que el POST de creación y no se combina con flags de datos. Puedes añadir --title en ambos comandos.

La CLI genera --idempotency-key si no lo proporcionas y lo imprime como Idempotency-Key o como idempotencyKey en --json, incluso si falla la solicitud. Conserva ese valor para reintentar sin crear otro lote.