Timbrar una factura (CFDI 4.0 de ingreso)
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.
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} 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.
Authorization: Bearer sk_test_... | sk_live_...Parámetros
Headers
| Campo | Tipo | Descripción |
|---|---|---|
| Idempotency-Keyreq | string | Llave única por operación lógica, 1 a 255 caracteres. Las respuestas repetidas traen Idempotency-Replayed: true. |
Cuerpo (JSON)
| Campo | Tipo | Descripción |
|---|---|---|
| merchantIdreq | string | Id del emisor que factura. |
| metodoPago | string | PUE o PPD. Default PUE. |
| formaPagoreq | string | Clave de c_FormaPago (03 transferencia, 04 tarjeta de crédito…). Con PPD debe ser 99. |
| usoCfdireq | string | Clave de c_UsoCFDI (G03 gastos en general, S01 sin efectos fiscales…). |
| folio | string | Folio visible que se imprime en el CFDI, por ejemplo el nombre del pedido (#1001). El folio numérico interno se sigue asignando. |
| receiver.rfcreq | string | RFC del receptor. Para facturas globales, XAXX010101000. |
| receiver.namereq | string | Razón social exacta de la Constancia de Situación Fiscal, en mayúsculas. |
| receiver.zipreq | string | Código postal del domicilio fiscal del receptor. |
| receiver.regimenFiscalreq | string | Régimen fiscal del receptor (clave de 3 dígitos). |
| items[]req | array | De 1 a 500 conceptos. |
| items[].productCodereq | string | Clave del producto/servicio del SAT, 8 dígitos. |
| items[].unitCodereq | string | Clave de unidad del SAT (E48, H87, KGM…). |
| items[].descriptionreq | string | Descripción del concepto, hasta 1000 caracteres. |
| items[].quantityreq | number | Cantidad, mayor a cero. |
| items[].unitPricereq | number | Precio unitario, mayor a cero. |
| items[].unit | string | Unidad en texto libre (Pieza, Servicio…). |
| items[].sku | string | Tu identificador interno del producto. |
| items[].taxable | boolean | Marca el concepto como exento cuando es false. |
| items[].ivaRate | number | 0, 0.08 o 0.16. Default 0.16. |
| items[].iepsRate | number | Tasa de IEPS entre 0 y 3 cuando aplica. |
| shipping.amount | number | Costo de envío como concepto adicional. |
| shipping.ivaRate | number | 0, 0.08 o 0.16 para el envío. |
| shipping.productCode | string | Clave SAT del envío. Default la de servicios de flete. |
| shipping.unitCode | string | Clave de unidad del envío. |
| pricesIncludeTax | boolean | true (default) si tus precios ya traen impuestos incluidos, como en Shopify. |
| cartDiscount | number | Descuento global a prorratear entre los conceptos. |
| informacionGlobal.periodicidad | string | Sólo facturas globales: 01 diario, 02 semanal, 03 quincenal, 04 mensual, 05 bimestral. |
| informacionGlobal.meses | string | Sólo facturas globales: 01–12, o 13–18 para bimestres. |
| informacionGlobal.anio | integer | Sólo facturas globales: año del periodo. |
Ejemplos
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
}'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 -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
{
"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"
}{
"id": "inv7k3q9x2m4v1t8p6d0n5cb",
"object": "invoice",
"livemode": false,
"status": "stamping",
"kind": "nominal",
"uuid": null,
"files": { "status": "pending" },
"stampedAt": null,
"createdAt": "2026-08-18T17:42:08.907Z"
}{
"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.statusesready, GET /v1/invoices/{id} incluyefiles.xmlUrl,files.pdfUrlyfiles.zipUrlfirmadas por 15 minutos. - Ante una falla ambigua con el PAC nunca reintentamos a ciegas: el documento se marca con
needsReconciliation: truey tu saldo queda reservado hasta resolverlo.
