Generación de CFDI 4.0
Generar un CFDI sellado
La generación se separa en cuatro endpoints, uno por tipo de comprobante, cada uno con su propia estructura JSON anidada (emisor, receptor, documento y movimientos o pagos):
POST /api/v1/cfdi/generar/ingreso (tipo I) POST /api/v1/cfdi/generar/egreso (tipo E) POST /api/v1/cfdi/generar/traslado (tipo T) POST /api/v1/cfdi/generar/pago (tipo P, REP con complemento Pagos 2.0) Authorization: Bearer <token> Content-Type: application/jsonGenera un CFDI 4.0 sellado con el CSD de la empresa registrada en el panel de empresas: el emisor solo lleva su rfc (el nombre, régimen y sellos salen del registro). El comprobante se valida con el mismo pipeline de validación antes de sellarse; si no pasa, responde 422 con los errores y no se sella. La decodificación es estricta: cualquier campo no admitido por el tipo (incluidos los que el generador deriva) responde 400.
Ingreso y egreso comparten estructura (el tipo lo fija el endpoint): el documento lleva los datos del comprobante incluyendo sub_total, descuento, total e impuestos globales (los envía el cliente y deben cuadrar con los movimientos: si no, 422), y cada movimiento es un concepto con sus impuestos. En egreso, una nota de crédito se liga con cfdi_relacionados tipo 01.
{
"emisor": {"rfc": "EKU9003173C9"},
"receptor": {
"rfc": "XAXX010101000",
"nombre": "PUBLICO EN GENERAL",
"domicilio_fiscal_receptor": "01000",
"regimen_fiscal_receptor": "616",
"uso_cfdi": "S01"
},
"documento": {
"serie": "A", "folio": "1",
"moneda": "MXN",
"forma_pago": "01", "metodo_pago": "PUE",
"exportacion": "01", "lugar_expedicion": "01000",
"sub_total": 1000.0,
"total": 1160.0,
"impuestos": {
"total_impuestos_trasladados": 160.0,
"traslados": [
{"base": 1000.0, "impuesto": "002", "tipo_factor": "Tasa", "tasa_o_cuota": 0.16, "importe": 160.0}
]
}
},
"movimientos": [
{
"clave_prod_serv": "01010101",
"cantidad": 1, "clave_unidad": "H87",
"descripcion": "Producto",
"valor_unitario": 1000.0, "importe": 1000.0,
"objeto_imp": "02",
"impuestos": {
"traslados": [
{"base": 1000.0, "impuesto": "002", "tipo_factor": "Tasa", "tasa_o_cuota": 0.16, "importe": 160.0}
]
}
}
]
}Traslado deriva la forma canónica (no se pide en el input): Moneda="XXX", SubTotal/ Total 0, ObjetoImp="01" en cada movimiento y sin forma/método de pago, descuento ni impuestos; el documento solo admite serie, folio, fecha, exportacion y lugar_expedicion, y cada movimiento solo clave_prod_serv, cantidad, clave_unidad, unidad, descripcion, valor_unitario e importe.
Pago genera un recibo de pago (REP) con el complemento Pagos 2.0: el generador deriva la forma canónica del comprobante (Moneda="XXX", SubTotal/Total 0, concepto único 84111506/ACT sin impuestos y sin forma/método de pago) y el documento admite los mismos campos que traslado. El body incluye pagos con uno o más pagos, cada uno con su forma_de_pago_p, moneda_p, monto y uno o más doctos_relacionados (UUID del documento, parcialidad y saldos). El generador calcula Totales.MontoTotalPagos (suma de los pagos convertidos con su tipo_cambio_p). Los impuestos del complemento son opcionales y se declaran solo por documento relacionado (campo impuestos con traslados y retenciones: base, impuesto, tipo_factor y tasa_o_cuota); el generador deriva el ObjetoImpDR (02 con impuestos, 01 sin ellos), calcula cada ImporteDR (base × tasa) y agrega por su cuenta los ImpuestosP de cada pago (convertidos a la MonedaP) y los atributos Total* del nodo Totales (en pesos).
{
"emisor": {"rfc": "EKU9003173C9"},
"receptor": {
"rfc": "XAXX010101000",
"nombre": "PUBLICO EN GENERAL",
"domicilio_fiscal_receptor": "01000",
"regimen_fiscal_receptor": "616",
"uso_cfdi": "CP01"
},
"documento": {"exportacion": "01", "lugar_expedicion": "01000"},
"pagos": [
{
"forma_de_pago_p": "03",
"moneda_p": "USD",
"tipo_cambio_p": 17.5,
"monto": 60.0,
"doctos_relacionados": [
{
"id_documento": "F1A2B3C4-1111-4222-8333-944455556666",
"moneda_dr": "MXN",
"equivalencia_dr": 17.5,
"num_parcialidad": 1,
"imp_saldo_ant": 1000.0,
"imp_pagado": 875.0,
"imp_saldo_insoluto": 125.0
},
{
"id_documento": "E5F6A7B8-9999-4888-B777-C66655544433",
"moneda_dr": "EUR",
"equivalencia_dr": 0.92,
"num_parcialidad": 1,
"imp_saldo_ant": 9.2,
"imp_pagado": 9.2,
"imp_saldo_insoluto": 0.0
}
]
}
]
}// Un docto_relacionado con impuestos (el generador deriva el resto):
{
"id_documento": "F1A2B3C4-1111-4222-8333-944455556666",
"moneda_dr": "MXN",
"num_parcialidad": 1,
"imp_saldo_ant": 1160.0,
"imp_pagado": 1160.0,
"imp_saldo_insoluto": 0.0,
"impuestos": {
"traslados": [
{"base": 1000.0, "impuesto": "002", "tipo_factor": "Tasa", "tasa_o_cuota": 0.16}
],
"retenciones": [
{"base": 1000.0, "impuesto": "001", "tipo_factor": "Tasa", "tasa_o_cuota": 0.10}
]
}
}- Pago sin
pagos(o con pagos sin documentos relacionados) responde 400; enviar campos que el generador deriva (objeto_imp,importede un impuesto,impuestos_p,totales) o incompatibles con la forma canónica también responde 400 por campo no admitido: la forma canónica y los agregados se derivan, no se pisan en silencio. - La congruencia del complemento (verificación 16) es la red de seguridad: si los montos o los impuestos no cuadran (p. ej.
montodistinto de la suma deimp_pagado/equivalencia_dr), responde 422 y no se sella. - En los impuestos, el
tipo_factorExentono llevatasa_o_cuotay los factoresTasa/Cuotala exigen (400 en otro caso);fecha_pagoes opcional (default: ahora) ytipo_cambio_p/equivalencia_drse exigen solo cuando las monedas lo requieren. - En
/validar, la verificación 11 "Congruencia del comprobante de traslado" (forma canónica:SubTotal/Total0, sin FormaPago/MetodoPago ni impuestos globales) aplica por igual a los tipos T y P.
Timbrar el CFDI generado
Los cuatro endpoints de generación aceptan el parámetro opt-in ?timbrar=true: tras sellar el CFDI, la app lo envía al proveedor de timbrado configurado en el servidor (primero: CONTPAQi Factura Electrónica) y responde con el XML ya timbrado. Detalle completo en Timbrado de CFDI.