Documentación / API / Facturas

Timbrar una factura (CFDI 4.0 de ingreso)

POSThttps://api.cfdi.express/v1/invoicesAPI keyIdempotency-Key

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.

Autenticación
Authorization: Bearer sk_test_... | sk_live_...

Parámetros

Headers

CampoTipoDescripción
Idempotency-KeyreqstringLlave única por operación lógica, 1 a 255 caracteres. Las respuestas repetidas traen Idempotency-Replayed: true.

Cuerpo (JSON)

CampoTipoDescripción
merchantIdreqstringId del emisor que factura.
metodoPagostringPUE o PPD. Default PUE.
formaPagoreqstringClave de c_FormaPago (03 transferencia, 04 tarjeta de crédito…). Con PPD debe ser 99.
usoCfdireqstringClave de c_UsoCFDI (G03 gastos en general, S01 sin efectos fiscales…).
foliostringFolio visible que se imprime en el CFDI, por ejemplo el nombre del pedido (#1001). El folio numérico interno se sigue asignando.
receiver.rfcreqstringRFC del receptor. Para facturas globales, XAXX010101000.
receiver.namereqstringRazón social exacta de la Constancia de Situación Fiscal, en mayúsculas.
receiver.zipreqstringCódigo postal del domicilio fiscal del receptor.
receiver.regimenFiscalreqstringRégimen fiscal del receptor (clave de 3 dígitos).
items[]reqarrayDe 1 a 500 conceptos.
items[].productCodereqstringClave del producto/servicio del SAT, 8 dígitos.
items[].unitCodereqstringClave de unidad del SAT (E48, H87, KGM…).
items[].descriptionreqstringDescripción del concepto, hasta 1000 caracteres.
items[].quantityreqnumberCantidad, mayor a cero.
items[].unitPricereqnumberPrecio unitario, mayor a cero.
items[].unitstringUnidad en texto libre (Pieza, Servicio…).
items[].skustringTu identificador interno del producto.
items[].taxablebooleanMarca el concepto como exento cuando es false.
items[].ivaRatenumber0, 0.08 o 0.16. Default 0.16.
items[].iepsRatenumberTasa de IEPS entre 0 y 3 cuando aplica.
shipping.amountnumberCosto de envío como concepto adicional.
shipping.ivaRatenumber0, 0.08 o 0.16 para el envío.
shipping.productCodestringClave SAT del envío. Default la de servicios de flete.
shipping.unitCodestringClave de unidad del envío.
pricesIncludeTaxbooleantrue (default) si tus precios ya traen impuestos incluidos, como en Shopify.
cartDiscountnumberDescuento global a prorratear entre los conceptos.
informacionGlobal.periodicidadstringSólo facturas globales: 01 diario, 02 semanal, 03 quincenal, 04 mensual, 05 bimestral.
informacionGlobal.mesesstringSólo facturas globales: 01–12, o 13–18 para bimestres.
informacionGlobal.aniointegerSólo facturas globales: año del periodo.

Ejemplos

cURL — factura nominativa PUE
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
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
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
{
  "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
{
  "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
{
  "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

StatuscodeCuándo aparece
400validation_errorDatos inválidos. El arreglo errors trae el path de cada campo.
401unauthorizedFalta el header Authorization o la llave es inválida.
402insufficient_creditsNo hay saldo suficiente para el timbre. Recarga y reintenta.
409request_in_flightOtra petición con el mismo Idempotency-Key está en proceso.
422idempotency_key_reuseReusaste la llave de idempotencia con un cuerpo distinto.
422sat_rejectedEl SAT rechazó el comprobante. El detalle trae el código del SAT.
503pac_unavailableEl 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} 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.

Continuar