Documentación / API / Complementos de pago (REP)

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

CampoTipoDescripción
idreqstringId de la factura PPD que se está pagando.

Headers

CampoTipoDescripción
Idempotency-KeyreqstringLlave única por pago, por ejemplo el id de la transacción en tu sistema.

Cuerpo (JSON)

CampoTipoDescripción
montoreqnumberImporte del pago recibido. Mayor a cero y no mayor al saldo pendiente.
formaPagoreqstringClave de c_FormaPago con la que se recibió el pago (03 transferencia, 04 tarjeta…).
fechaPagostringFecha y hora del pago en formato AAAA-MM-DDTHH:MM:SS. Default: ahora.
numParcialidadintegerNú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

StatuscodeCuándo aparece
400validation_errorSobrepago, parcialidad fuera de secuencia, factura no PPD o ya liquidada.
401unauthorizedFalta el header Authorization o la llave es inválida.
402insufficient_creditsNo hay saldo suficiente para el timbre.
404not_foundLa factura no existe en tu cuenta.
422sat_rejectedEl 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}.

Continuar