# Timbrar un complemento de pago (REP 2.0) > `POST https://api.cfdi.express/v1/invoices/{id}/payments` — Registra un pago recibido contra una factura PPD y timbra su complemento de pago (Pagos 2.0). URL de esta página: https://cfdi.express/docs/api/timbrar-complemento-de-pago Autenticación: requiere API key (`Authorization: Bearer sk_test_…` o `sk_live_…`) Idempotencia: requiere el header `Idempotency-Key`. 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. ### Parámetros de ruta | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `id` | string | sí | Id de la factura PPD que se está pagando. | ### Headers | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `Idempotency-Key` | string | sí | Llave única por pago, por ejemplo el id de la transacción en tu sistema. | ### Cuerpo (JSON) | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `monto` | number | sí | Importe del pago recibido. Mayor a cero y no mayor al saldo pendiente. | | `formaPago` | string | sí | Clave de c_FormaPago con la que se recibió el pago (03 transferencia, 04 tarjeta…). | | `fechaPago` | string | no | Fecha y hora del pago en formato AAAA-MM-DDTHH:MM:SS. Default: ahora. | | `numParcialidad` | integer | no | Número de parcialidad. Se asigna automáticamente si lo omites. | ## Ejemplos ### cURL ```bash 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 ```javascript 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 ```json { "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}](https://cfdi.express/docs/api/obtener-complemento-de-pago). ## Relacionados - [GET /v1/payments/{id}](https://cfdi.express/docs/api/obtener-complemento-de-pago) — Obtener REP - [GET /v1/payments](https://cfdi.express/docs/api/listar-complementos-de-pago) — Listar REPs - [POST /v1/payments/{id}/cancel](https://cfdi.express/docs/api/cancelar-complemento-de-pago) — Cancelar REP --- Referencia completa: https://cfdi.express/docs/api · OpenAPI: https://api.cfdi.express/openapi.json · Llaves: https://dash.cfdi.express