# Timbrar un complemento de pago de varias facturas > `POST https://api.cfdi.express/v1/payments` — Registra un solo pago que cubre varias facturas PPD y timbra un solo complemento de pago, con un documento relacionado por factura. URL de esta página: https://cfdi.express/docs/api/timbrar-complemento-de-pago-varias-facturas Autenticación: requiere API key (`Authorization: Bearer sk_test_…` o `sk_live_…`) Header `x-account-id`: indica la cuenta. Si no eres miembro, la respuesta es 403. Idempotencia: requiere el header `Idempotency-Key`. Desde la v1.46.0. Si recibes un pago que liquida o abona varias facturas PPD del mismo cliente, el SAT permite un solo CFDI de pago con un `DoctoRelacionado` por factura. Este endpoint timbra ese CFDI: consume un timbre en lugar de uno por factura. En `documents` manda cada factura con el `importePagado` que le aplicas. Cada factura lleva su propia `numParcialidad`, su `saldoAnterior` y su `saldoInsoluto`, calculados en el servidor igual que en [POST /v1/invoices/{id}/payments](https://cfdi.express/docs/api/timbrar-complemento-de-pago). Los impuestos de cada documento se prorratean con los de su factura, y los totales del pago (`ImpuestosP` y `Totales`) son la suma exacta de los documentos. Todas las facturas tienen que ser PPD, estar timbradas y ser del mismo emisor, el mismo RFC receptor y la misma moneda. Los sobrepagos, las facturas repetidas y las parcialidades fuera de secuencia se rechazan con 400 antes de gastar un timbre. ### 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 | |---|---|---|---| | `formaPago` | string | sí | Clave de c_FormaPago con la que se recibió el pago (03 transferencia, 04 tarjeta…). No puede ser 99. | | `fechaPago` | string | no | Fecha y hora del pago en formato AAAA-MM-DDTHH:MM:SS, hora de Ciudad de México. Default: ahora. | | `monto` | number | no | Importe total recibido. Opcional: si lo mandas, tiene que ser igual a la suma de documents[].importePagado; si lo omites, es esa suma. | | `documents` | object[] | sí | De 1 a 100 facturas que liquida el pago. | | `documents[].invoiceId` | string | sí | Id de una factura PPD timbrada. Sin repetir. | | `documents[].importePagado` | number | sí | Parte del pago que se aplica a esa factura (ImpPagado). Mayor a cero y no mayor a su saldo pendiente. | | `documents[].numParcialidad` | integer | no | Parcialidad de esa factura. Se asigna automáticamente si la omites; si la mandas, se valida contra la secuencia. | ## Ejemplos ### cURL ```bash curl -X POST https://api.cfdi.express/v1/payments \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: payment_88240" \ -H "Content-Type: application/json" \ -d '{ "formaPago": "03", "fechaPago": "2026-10-30T12:00:00", "monto": 5.80, "documents": [ { "invoiceId": "inv7k3q9x2m4v1t8p6d0n5cb", "importePagado": 2.32 }, { "invoiceId": "inv2p8d4n6c0b5rzmkq2f8v3", "importePagado": 2.32 }, { "invoiceId": "inv5c0btpay9x2m6q4k1v3p8", "importePagado": 1.16 } ] }' ``` ### Node.js ```javascript const res = await fetch("https://api.cfdi.express/v1/payments", { method: "POST", headers: { Authorization: `Bearer ${process.env.CFDI_API_KEY}`, "Idempotency-Key": `payment_${transaction.id}`, "Content-Type": "application/json", }, body: JSON.stringify({ formaPago: "03", // Una entrada por factura que cubre el pago. documents: transaction.allocations.map((a) => ({ invoiceId: a.invoiceId, importePagado: a.amount, })), }), }); const rep = await res.json(); for (const doc of rep.documents) { console.log(doc.invoiceId, "saldo insoluto:", doc.saldoInsoluto); } ``` ## Respuesta ### 201 Created ```json { "id": "pay3v1t8p6d0n5cbinv7k3q9x", "object": "payment", "livemode": false, "status": "stamped", "invoiceId": "inv7k3q9x2m4v1t8p6d0n5cb", "merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz", "uuid": "33d7d158-5f9c-49ef-b1a1-b9c5557efea0", "numParcialidad": 1, "monto": 5.80, "formaPago": "03", "fechaPago": "2026-10-30T18:00:00.000Z", "saldoAnterior": 2.32, "saldoInsoluto": 0, "currency": "MXN", "documents": [ { "invoiceId": "inv7k3q9x2m4v1t8p6d0n5cb", "numParcialidad": 1, "importePagado": 2.32, "saldoAnterior": 2.32, "saldoInsoluto": 0 }, { "invoiceId": "inv2p8d4n6c0b5rzmkq2f8v3", "numParcialidad": 1, "importePagado": 2.32, "saldoAnterior": 2.32, "saldoInsoluto": 0 }, { "invoiceId": "inv5c0btpay9x2m6q4k1v3p8", "numParcialidad": 1, "importePagado": 1.16, "saldoAnterior": 1.16, "saldoInsoluto": 0 } ], "files": { "status": "ready" }, "cancellation": null, "stampedAt": "2026-10-30T18:05:12.006Z", "createdAt": "2026-10-30T18:05:10.554Z" } ``` ## Errores | Status | code | Cuándo aparece | |---|---|---| | 400 | `validation_error` | Alguna factura no es PPD, no está timbrada, ya está liquidada, recibe más que su saldo (subcode overpay, con invoiceId) o viene repetida. Las facturas son de otro emisor, otro RFC receptor u otra moneda. monto no es la suma de documents[].importePagado. Una parcialidad está fuera de secuencia. formaPago es 99 o no existe. | | 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` | Alguna factura no existe en tu cuenta. | | 422 | `sat_rejected` | El SAT rechazó el complemento. | ## Notas - `invoiceId`, `numParcialidad`, `saldoAnterior` y `saldoInsoluto` del recurso son los de la primera factura; `monto` es el total del pago. El detalle por factura está en `documents`. - El CFDI lleva los documentos con la primera factura de `documents` al inicio y el resto ordenado por id. - [GET /v1/payments?invoiceId=](https://cfdi.express/docs/api/listar-complementos-de-pago) encuentra el REP con cualquiera de sus facturas. Ninguna de ellas se puede cancelar mientras el REP esté vigente; [cancelar el REP](https://cfdi.express/docs/api/cancelar-complemento-de-pago) restaura el saldo de todas. - Por MCP, `create_payment` acepta el mismo arreglo `documents` en lugar de `invoiceId` y `monto`. ## Relacionados - [POST /v1/invoices/{id}/payments](https://cfdi.express/docs/api/timbrar-complemento-de-pago) — Timbrar REP - [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