Timbrar un complemento de pago (REP 2.0)
POSThttps://api.cfdi.express/v1/invoices/{id}/paymentsAPI keyIdempotency-Key
Registra un pago recibido contra una factura PPD y timbra su complemento de pago (Pagos 2.0).
El servidor calcula saldoAnterior, saldoInsoluto y la parcialidad. Si omites numParcialidad se asigna sola; si la mandas, se valida contra la secuencia.
Los sobrepagos, los pagos fuera de secuencia y los pagos contra facturas ya liquidadas o no-PPD se rechazan con 400 antes de gastar un timbre.
Autenticación
Authorization: Bearer sk_test_... | sk_live_...Parámetros
Parámetros de ruta
| Campo | Tipo | Descripción |
|---|---|---|
| idreq | string | Id de la factura PPD que se está pagando. |
Headers
| Campo | Tipo | Descripción |
|---|---|---|
| Idempotency-Keyreq | string | Llave única por pago, por ejemplo el id de la transacción en tu sistema. |
Cuerpo (JSON)
| Campo | Tipo | Descripción |
|---|---|---|
| montoreq | number | Importe del pago recibido. Mayor a cero y no mayor al saldo pendiente. |
| formaPagoreq | string | Clave de c_FormaPago con la que se recibió el pago (03 transferencia, 04 tarjeta…). |
| fechaPago | string | Fecha y hora del pago en formato AAAA-MM-DDTHH:MM:SS. Default: ahora. |
| numParcialidad | integer | Número de parcialidad. Se asigna automáticamente si lo omites. |
Ejemplos
cURL
curl -X POST https://api.cfdi.express/v1/invoices/inv7k3q9x2m4v1t8p6d0n5cb/payments \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: payment_88231" \
-H "Content-Type: application/json" \
-d '{
"monto": 2000.00,
"formaPago": "03",
"fechaPago": "2026-08-18T12:00:00"
}'Node.js
const res = await fetch(
`https://api.cfdi.express/v1/invoices/${invoiceId}/payments`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CFDI_API_KEY}`,
"Idempotency-Key": `payment_${transaction.id}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
monto: transaction.amount,
formaPago: "03",
}),
},
);
const rep = await res.json();
console.log("Saldo insoluto:", rep.saldoInsoluto);Respuesta
201 Created
{
"id": "pay9x2m6q4k1v3p8d7n5c0bt",
"object": "payment",
"livemode": false,
"status": "stamped",
"invoiceId": "inv7k3q9x2m4v1t8p6d0n5cb",
"merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz",
"uuid": "5c9e7f22-1a3b-4d8e-9f01-2b3c4d5e6f70",
"numParcialidad": 1,
"monto": 2000.00,
"formaPago": "03",
"fechaPago": "2026-08-18T12:00:00.000Z",
"saldoAnterior": 3670.00,
"saldoInsoluto": 1670.00,
"currency": "MXN",
"files": { "status": "ready" },
"cancellation": null,
"stampedAt": "2026-08-18T18:31:12.006Z",
"createdAt": "2026-08-18T18:31:10.554Z"
}Errores
| Status | code | Cuándo aparece |
|---|---|---|
| 400 | validation_error | Sobrepago, parcialidad fuera de secuencia, factura no PPD o ya liquidada. |
| 401 | unauthorized | Falta el header Authorization o la llave es inválida. |
| 402 | insufficient_credits | No hay saldo suficiente para el timbre. |
| 404 | not_found | La factura no existe en tu cuenta. |
| 422 | sat_rejected | El SAT rechazó el complemento. |
Notas
- Cuando el último pago deja el saldo en cero, la factura queda liquidada y ya no acepta más REPs.
- El REP también genera XML y PDF: consúltalos con GET /v1/payments/{id}.
