# Timbrar una factura (CFDI 4.0 de ingreso) > `POST https://api.cfdi.express/v1/invoices` — Timbra un CFDI 4.0 de ingreso: nominativo PUE o PPD, o global al público en general. El motor de impuestos calcula IVA e IEPS por ti. URL de esta página: https://cfdi.express/docs/api/timbrar-factura Autenticación: requiere API key (`Authorization: Bearer sk_test_…` o `sk_live_…`) Idempotencia: requiere el header `Idempotency-Key`. El timbrado es síncrono pero mediado por cola: la petición espera hasta 20 segundos por el UUID del SAT y responde **201** con la factura timbrada. Bajo carga extrema puede degradar a **202**; en ese caso el recurso queda en `status: "stamping"` y consultas [GET /v1/invoices/{id}](https://cfdi.express/docs/api/obtener-factura) hasta que pase a `stamped`. El header `Idempotency-Key` es **obligatorio**. Usa un valor estable por operación lógica (el id de tu pedido, por ejemplo): si reintentas, recibes la factura original sin gastar otro timbre. Reglas del SAT que validamos por ti: PUE exige una forma de pago real (no `99`); PPD exige forma de pago `99` y deja la factura con `saldoPendiente` para sus complementos de pago. ### Headers | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `Idempotency-Key` | string | sí | Llave única por operación lógica, 1 a 255 caracteres. Las respuestas repetidas traen Idempotency-Replayed: true. | ### Cuerpo (JSON) | Campo | Tipo | Requerido | Descripción | |---|---|---|---| | `merchantId` | string | sí | Id del emisor que factura. | | `metodoPago` | string | no | PUE o PPD. Default PUE. | | `formaPago` | string | sí | Clave de c_FormaPago (03 transferencia, 04 tarjeta de crédito…). Con PPD debe ser 99. | | `usoCfdi` | string | sí | Clave de c_UsoCFDI (G03 gastos en general, S01 sin efectos fiscales…). | | `folio` | string | no | Folio visible que se imprime en el CFDI, por ejemplo el nombre del pedido (#1001). El folio numérico interno se sigue asignando. | | `receiver.rfc` | string | sí | RFC del receptor. Para facturas globales, XAXX010101000. | | `receiver.name` | string | sí | Razón social exacta de la Constancia de Situación Fiscal, en mayúsculas. | | `receiver.zip` | string | sí | Código postal del domicilio fiscal del receptor. | | `receiver.regimenFiscal` | string | sí | Régimen fiscal del receptor (clave de 3 dígitos). | | `items[]` | array | sí | De 1 a 500 conceptos. | | `items[].productCode` | string | sí | Clave del producto/servicio del SAT, 8 dígitos. | | `items[].unitCode` | string | sí | Clave de unidad del SAT (E48, H87, KGM…). | | `items[].description` | string | sí | Descripción del concepto, hasta 1000 caracteres. | | `items[].quantity` | number | sí | Cantidad, mayor a cero. | | `items[].unitPrice` | number | sí | Precio unitario, mayor a cero. | | `items[].unit` | string | no | Unidad en texto libre (Pieza, Servicio…). | | `items[].sku` | string | no | Tu identificador interno del producto. | | `items[].taxable` | boolean | no | Marca el concepto como exento cuando es false. | | `items[].ivaRate` | number | no | 0, 0.08 o 0.16. Default 0.16. | | `items[].iepsRate` | number | no | Tasa de IEPS entre 0 y 3 cuando aplica. | | `shipping.amount` | number | no | Costo de envío como concepto adicional. | | `shipping.ivaRate` | number | no | 0, 0.08 o 0.16 para el envío. | | `shipping.productCode` | string | no | Clave SAT del envío. Default la de servicios de flete. | | `shipping.unitCode` | string | no | Clave de unidad del envío. | | `pricesIncludeTax` | boolean | no | true (default) si tus precios ya traen impuestos incluidos, como en Shopify. | | `cartDiscount` | number | no | Descuento global a prorratear entre los conceptos. | | `informacionGlobal.periodicidad` | string | no | Sólo facturas globales: 01 diario, 02 semanal, 03 quincenal, 04 mensual, 05 bimestral. | | `informacionGlobal.meses` | string | no | Sólo facturas globales: 01–12, o 13–18 para bimestres. | | `informacionGlobal.anio` | integer | no | Sólo facturas globales: año del periodo. | ## Ejemplos ### cURL — factura nominativa PUE ```bash curl -X POST https://api.cfdi.express/v1/invoices \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: order_1001" \ -H "Content-Type: application/json" \ -d '{ "merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz", "metodoPago": "PUE", "formaPago": "03", "usoCfdi": "G03", "folio": "#1001", "receiver": { "rfc": "XIA190128J61", "name": "XENON INDUSTRIAL ARTICLES", "zip": "01160", "regimenFiscal": "601" }, "items": [ { "productCode": "43201808", "unitCode": "H87", "description": "Teclado mecánico 75%", "sku": "KB-75-BLK", "quantity": 2, "unitPrice": 1740.00, "ivaRate": 0.16 } ], "shipping": { "amount": 190.00 }, "pricesIncludeTax": true }' ``` ### Node.js ```javascript const res = await fetch("https://api.cfdi.express/v1/invoices", { method: "POST", headers: { Authorization: `Bearer ${process.env.CFDI_API_KEY}`, "Idempotency-Key": `order_${order.id}`, "Content-Type": "application/json", }, body: JSON.stringify({ merchantId, metodoPago: "PUE", formaPago: "03", usoCfdi: "G03", folio: order.name, receiver: { rfc: "XIA190128J61", name: "XENON INDUSTRIAL ARTICLES", zip: "01160", regimenFiscal: "601", }, items: order.lineItems.map((li) => ({ productCode: li.satCode ?? "01010101", unitCode: "H87", description: li.title, sku: li.sku, quantity: li.quantity, unitPrice: li.price, })), pricesIncludeTax: true, }), }); const invoice = await res.json(); if (invoice.status === "stamping") { // 202 bajo carga extrema: consulta hasta que quede "stamped" } ``` ### cURL — factura global ```bash curl -X POST https://api.cfdi.express/v1/invoices \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: global_2026_07" \ -H "Content-Type: application/json" \ -d '{ "merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz", "metodoPago": "PUE", "formaPago": "01", "usoCfdi": "S01", "receiver": { "rfc": "XAXX010101000", "name": "PUBLICO EN GENERAL", "zip": "42501", "regimenFiscal": "616" }, "items": [ { "productCode": "01010101", "unitCode": "ACT", "description": "Venta al público en general", "quantity": 1, "unitPrice": 128450.00 } ], "informacionGlobal": { "periodicidad": "04", "meses": "07", "anio": 2026 } }' ``` ## Respuesta ### 201 Created ```json { "id": "inv7k3q9x2m4v1t8p6d0n5cb", "object": "invoice", "livemode": false, "status": "stamped", "kind": "nominal", "uuid": "9f1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d", "serie": "A", "folio": 128, "folioText": "#1001", "metodoPago": "PUE", "formaPago": "03", "usoCfdi": "G03", "currency": "MXN", "subtotal": 3163.79, "discount": 0, "total": 3670.00, "saldoPendiente": null, "receiver": { "rfc": "XIA190128J61", "name": "XENON INDUSTRIAL ARTICLES", "zip": "01160", "regimenFiscal": "601" }, "merchantId": "mkq2f8v3x1t7p9d4n6c0b5rz", "files": { "status": "ready" }, "cancellation": null, "stampedAt": "2026-08-18T17:42:10.512Z", "createdAt": "2026-08-18T17:42:08.907Z" } ``` ### 202 Accepted ```json { "id": "inv7k3q9x2m4v1t8p6d0n5cb", "object": "invoice", "livemode": false, "status": "stamping", "kind": "nominal", "uuid": null, "files": { "status": "pending" }, "stampedAt": null, "createdAt": "2026-08-18T17:42:08.907Z" } ``` ### 422 — rechazo del SAT ```json { "type": "https://api.cfdi.express/docs/errors/sat_rejected", "title": "Rejected by SAT validation", "status": 422, "code": "sat_rejected", "detail": "El nombre del receptor no coincide con el registrado ante el SAT", "satCode": "CFDI40147" } ``` ## Errores | Status | code | Cuándo aparece | |---|---|---| | 400 | `validation_error` | Datos inválidos. El arreglo errors trae el path de cada campo. | | 401 | `unauthorized` | Falta el header Authorization o la llave es inválida. | | 402 | `insufficient_credits` | No hay saldo suficiente para el timbre. Recarga y reintenta. | | 409 | `request_in_flight` | Otra petición con el mismo Idempotency-Key está en proceso. | | 422 | `idempotency_key_reuse` | Reusaste la llave de idempotencia con un cuerpo distinto. | | 422 | `sat_rejected` | El SAT rechazó el comprobante. El detalle trae el código del SAT. | | 503 | `pac_unavailable` | El PAC no está disponible. Reintenta con la misma llave de idempotencia. | ## Notas - El XML, el PDF y el ZIP se generan justo después del timbrado. Cuando `files.status` es `ready`, [GET /v1/invoices/{id}](https://cfdi.express/docs/api/obtener-factura) incluye `files.xmlUrl`, `files.pdfUrl` y `files.zipUrl` firmadas por 15 minutos. - Ante una falla ambigua con el PAC nunca reintentamos a ciegas: el documento se marca con `needsReconciliation: true` y tu saldo queda reservado hasta resolverlo. ## Relacionados - [GET /v1/invoices/{id}](https://cfdi.express/docs/api/obtener-factura) — Obtener factura - [GET /v1/invoices](https://cfdi.express/docs/api/listar-facturas) — Listar facturas - [POST /v1/invoices/{id}/cancel](https://cfdi.express/docs/api/cancelar-factura) — Cancelar factura - [POST /v1/credit_notes](https://cfdi.express/docs/api/crear-nota-de-credito) — Timbrar nota de crédito --- Referencia completa: https://cfdi.express/docs/api · OpenAPI: https://api.cfdi.express/openapi.json · Llaves: https://dash.cfdi.express