Emitir CFDI
Nómina
Dar de alta empleados y timbrar recibos de nómina con el complemento 1.2.
Un recibo de nómina es un CFDI de tipo nómina: un comprobante con el complemento de Nómina 1.2 del SAT. Se emite por empleado y por periodo de pago.
Scopes: invoices.create para dar de alta empleados y timbrar; invoices.read
para consultarlos.
Paso 1 — da de alta al empleado
POST /api/authenticated/{org}/empleados
| Campo | Requerido | Reglas |
|---|---|---|
rfc | sí | 13 caracteres (persona física). |
curp | sí | 18 caracteres. |
nombre | sí | Solo el nombre de pila. |
primerApellido | sí | |
segundoApellido | no | |
cp | sí | 5 dígitos. |
tipoContrato | sí | Clave de c_TipoContrato. |
tipoRegimen | sí | Clave de c_TipoRegimen (02 sueldos y salarios es lo común). |
salarioDiarioIntegrado | sí | SDI. |
periodicidadPago | recomendado | Clave de c_PeriodicidadPago. |
entidadFederativa | recomendado | Clave de c_Estado. |
riesgoPuesto | recomendado | Clave de c_RiesgoPuesto. |
salarioBaseCotizacion | recomendado | SBC. |
nss | no | 11 dígitos. |
clabe | no | 18 dígitos. |
banco, numEmpleado, departamento, puesto, numContrato, tipoJornada | no |
curl -s -X POST "$FACTY_HOST/api/authenticated/$FACTY_ORG/empleados" \
-H "X-API-Key: $FACTY_KEY" -H "Content-Type: application/json" \
-d '{
"rfc": "PEGJ850101HDF",
"curp": "PEGJ850101HDFRRN04",
"nombre": "JUAN",
"primerApellido": "PEREZ",
"segundoApellido": "GARCIA",
"cp": "06600",
"tipoContrato": "01",
"tipoRegimen": "02",
"periodicidadPago": "04",
"entidadFederativa": "DIF",
"riesgoPuesto": "1",
"salarioDiarioIntegrado": 850.50,
"salarioBaseCotizacion": 820.00,
"nss": "12345678901",
"numEmpleado": "E-0042",
"puesto": "Desarrollador"
}' | jq
Consultar y buscar (?q= cruza RFC, nombre, apellidos y número de empleado):
GET /api/authenticated/{org}/empleados?q=perez
Paso 2 — timbra el recibo
POST /api/authenticated/{org}/invoices
{
"type": "nomina",
"empleadoId": "clxem1…",
"tipoNomina": "O",
"fechaInicioPeriodo": "2026-08-01",
"fechaFinPeriodo": "2026-08-15",
"fechaPago": "2026-08-15",
"diasPagados": 15,
"idempotencyKey": "rh:prod:nomina:2026-08-q1:E-0042",
"percepciones": [
{
"tipoPercepcion": "001",
"descripcion": "Sueldos, salarios rayas y jornales",
"importeGravado": 12750.00,
"importeExento": 0
}
],
"deducciones": [
{ "tipoDeduccion": "002", "descripcion": "ISR", "importe": 1840.25 },
{ "tipoDeduccion": "001", "descripcion": "Seguridad social", "importe": 412.10 }
]
}
Campos
| Campo | Requerido | Descripción |
|---|---|---|
type | sí | "nomina" |
empleadoId | sí | cuid del empleado. |
tipoNomina | sí | "O" ordinaria, "E" extraordinaria (finiquito, aguinaldo, PTU). |
fechaInicioPeriodo, fechaFinPeriodo, fechaPago | sí | "YYYY-MM-DD". |
diasPagados | sí | Mayor a 0, máximo 366. |
percepciones | sí | Al menos una. |
deducciones | sí | Puede ir vacío ([]), pero el campo debe existir. |
idempotencyKey | sí | Deriva de periodo + empleado. |
otrosPagos | no | Subsidio al empleo, reembolsos. |
incapacidades | no | |
serie | no | Sobrescribe la serie de nómina. |
formaPago | no | Default "03" (transferencia). |
nss, clabe, banco, numContrato, tipoJornada, anoAntiguedad, fechaInicioRel, sindicato | no | Sobrescriben lo registrado en el empleado. |
periodicidadPago, entidadFederativa, riesgoPuesto, salarioBaseCotizacion, registroPatronal | no | Igual: si el empleado ya los trae, no hace falta repetirlos. |
Percepciones
{
"tipoPercepcion": "001",
"descripcion": "Sueldos, salarios rayas y jornales",
"importeGravado": 12750.00,
"importeExento": 0
}
tipoPercepcion es una clave de c_TipoPercepcion. La separación
gravado/exento la determina la ley (aguinaldo exento hasta 30 UMA, prima
vacacional hasta 15 UMA, etc.); es un cálculo de nómina, no algo que la API
deduzca por ti.
Deducciones
{ "tipoDeduccion": "002", "descripcion": "ISR", "importe": 1840.25 }
Clave de c_TipoDeduccion. Las más usadas: 001 seguridad social, 002 ISR,
004 otros, 006 descuento por incapacidad.
Otros pagos
{
"tipoOtroPago": "002",
"descripcion": "Subsidio para el empleo",
"importe": 0,
"subsidioCausado": 0
}
Cuando tipoOtroPago es "002" (subsidio para el empleo), subsidioCausado es
obligatorio para el SAT — incluso valiendo cero.
Incapacidades
{ "tipoIncapacidad": "02", "diasIncapacidad": 3, "importeMonetario": 1275.00 }
Respuesta
Igual que cualquier otro timbrado:
{
"id": "clxn8…",
"uuid": "C3D4E5F6-…",
"serie": "N",
"folio": 341,
"status": "stamped",
"total": 10497.65,
"moneda": "MXN",
"stampedAt": "2026-08-15T22:10:04.000Z",
"timbresRemaining": 1180
}
Timbrar un periodo completo
Un recibo por empleado, un timbre por recibo. Recorre tu nómina en serie o con poca concurrencia — recuerda el límite de 60 escrituras por minuto por llave.
for emp in empleados_activos:
key = f"rh:prod:nomina:{periodo}:{emp.num_empleado}"
post("/invoices", body={
"type": "nomina",
"empleadoId": emp.facty_id,
"tipoNomina": "O",
"fechaInicioPeriodo": periodo.inicio,
"fechaFinPeriodo": periodo.fin,
"fechaPago": periodo.pago,
"diasPagados": periodo.dias,
"idempotencyKey": key,
"percepciones": emp.percepciones,
"deducciones": emp.deducciones,
})
Con la llave derivada de periodo + empleado, volver a correr el job tras una interrupción no timbra a nadie dos veces. Ver Idempotencia.
Consultar recibos emitidos
GET /api/authenticated/{org}/invoices?type=nomina&dateFrom=2026-08-01&dateTo=2026-08-31