Skip to content

Facturas (CFDI) ​

Emite, consulta, cancela y descarga los CFDI 4.0 de tus ventas. Es lo que hace que la columna «Factura» de tu portal deje de decir Próximamente: sin esto, la factura existe dentro de Finova y tu cliente no puede verla sin entrar a la aplicación.

Requiere el módulo Facturación

invoicing es opt-in por empresa, y además hace falta tener dados de alta los datos fiscales y los sellos digitales (CSD). Sin eso, estos endpoints responden 403 module_not_enabled o 422 no_emitter.

Se factura la VENTA, no el cobro ​

Una orden pagada en tres parcialidades se factura una vez. Por eso el CFDI cuelga de /sales/:id y no de un ingreso.

El esquema de pago lo decide Finova y no es opcional ante el SAT:

EsquemaCuándoQué implica
PUELa venta se liquidó en una sola exhibiciónUn CFDI y nada más.
PPDHay crédito o varios cobrosUn CFDI + un complemento de pago por cada cobro.

El objeto Invoice ​

json
{
  "object":         "invoice",
  "id":             "65c2b1f1234567890abcdef0",
  "sale_id":        "65c2a1f1234567890abcdef0",
  "uuid":           "A1B2C3D4-E5F6-7890-ABCD-EF1234567890",
  "status":         "stamped",
  "cfdi_type":      "I",
  "payment_method": "PPD",
  "payment_form":   "99",
  "use":            "G03",
  "total_cents":    1160000,
  "total":          11600.0,
  "currency":       "mxn",
  "stamped_at":     "2026-09-17T18:20:04Z",
  "canceled_at":    null,
  "cancellation_motive": null,
  "substitutes_uuid":    null,
  "taxes":          [{ "type": "IVA", "rate": 0.16, "base": 10000.0, "amount": 1600.0 }],
  "error":          null,
  "attempts":       1,
  "payment_complements": [
    {
      "object":       "payment_complement",
      "id":           "65c2c1f1234567890abcdef0",
      "uuid":         "B2C3D4E5-...",
      "status":       "stamped",
      "amount":       4000.0,
      "currency":     "mxn",
      "paid_at":      "2026-09-17T18:04:10Z",
      "payment_form": "03",
      "error":        null,
      "attempts":     1
    }
  ]
}
statusQué significa
pendingEncolada, todavía no timbrada.
stampedTimbrada ante el SAT. uuid es el folio fiscal.
failedEl timbrado falló. error dice por qué.
canceledCancelada ante el SAT.

No se publican el id interno del proveedor de timbrado ni su llave: son credenciales. El PDF y el XML se piden por sus propios endpoints.


Listar facturas ​

GET/api/v1/invoicesread-invoices

El libro de facturas de la empresa. Filtros: status, customer_id, uuid.


Ver la factura de una venta ​

GET/api/v1/sales/:sale_id/invoiceread-invoices

data es null si la venta todavía no se ha facturado. El meta dice por qué se puede o no:

json
{
  "data": null,
  "meta": {
    "can_emit":      true,
    "invoiceable":   true,
    "forced_ppd":    true,
    "emitter_ready": true
  }
}

Emitir ​

POST/api/v1/sales/:sale_id/invoicecreate-invoices

Parámetros opcionales: payment_method (PUE / PPD), payment_form (clave SAT de forma de pago), uso (uso del CFDI del receptor).

Responde 202, no 201

Timbrar llama al proveedor ante el SAT, que tarda segundos y a veces más. Bloquear tu petición hasta que conteste convierte cada pico suyo en un timeout tuyo, así que la respuesta es inmediata con status: "pending".

Espera el webhook invoice.emitted en lugar de sondear. Si prefieres sondear, GET /sales/:id/invoice te da el estado.

bash
curl -X POST https://developers.fi-nova.com/api/v1/sales/65c2.../invoice \
  -H 'Authorization: Bearer finova_sk_TU_SECRETO' \
  -H 'Content-Type: application/json' \
  -d '{ "payment_form": "03", "uso": "G03" }'

Por qué te puede rechazar ​

CódigoQué pasó
already_invoicedYa está timbrada. Para corregirla, usa sustituir.
sale_without_taxLa venta tiene conceptos sin IVA, que un CFDI no admite salvo exentos o tasa 0%.
zero_totalLa venta es de $0.
no_emitterFaltan datos fiscales o sellos digitales de la empresa.
customer_no_rfcEl cliente de la venta no tiene RFC.

Sin cliente, la venta se timbra a público en general (RFC genérico).


Cancelar ​

POST/api/v1/sales/:sale_id/invoice/canceldelete-invoices
ParámetroDescripción
motiveClave de cancelación del SAT. Por defecto 02.
substitution_idFolio del CFDI que la sustituye. Obligatorio con motivo 01.

Pide delete-invoices, no create-invoices

Cancelar un CFDI es irreversible ante el SAT. No debería caber dentro de la misma llave que emite: una llave de tu sitio web puede necesitar facturar y no tiene por qué poder cancelar.

Emite invoice.cancelled.


Sustituir ​

POST/api/v1/sales/:sale_id/invoice/substitutecreate-invoices

Emite una factura nueva relacionada (04) con la anterior y cancela la vieja con motivo 01. Es la forma de corregir una factura ya timbrada — un CFDI no se edita.

Acepta los mismos payment_method, payment_form y uso.

Emite invoice.emitted y invoice.substituted (este último con substituted_uuid).


Complemento de pago ​

POST/api/v1/sales/:sale_id/invoice/payment-complementcreate-invoices

En una venta PPD, cada cobro se documenta ante el SAT con su complemento.

ParámetroDescripción
amount_centsImporte del cobro.
paid_atCuándo entró (ISO 8601).
payment_formClave SAT de forma de pago.
complement_idPara reintentar uno que falló.

Reintentar: manda complement_id

Sin él, el reintento añade un complemento nuevo en vez de reusar el que falló, y dos complementos por el mismo dinero es un problema fiscal, no un duplicado cosmético.

Emite invoice.payment_complement_added.


Descargar el PDF y el XML ​

GET/api/v1/sales/:sale_id/invoice/pdfread-invoices
GET/api/v1/sales/:sale_id/invoice/xmlread-invoices

Devuelven el documento como descarga (application/pdf / application/xml). Con ?complement_id=... bajas el complemento de pago en vez del CFDI de la venta: ante el proveedor es otra factura, con su propio folio.

bash
curl -L 'https://developers.fi-nova.com/api/v1/sales/65c2.../invoice/pdf' \
  -H 'Authorization: Bearer finova_sk_TU_SECRETO' \
  -o factura.pdf

Los bytes pasan por Finova en vez de redirigirte al proveedor. Es a propósito: la llave de timbrado es una credencial de la empresa y una URL firmada caduca — así recibes siempre el documento y nunca una credencial.


Enterarte sin sondear ​

Los cuatro eventos de factura cubren todo el ciclo:

json
{ "enabled_events": ["invoice.*"] }

Ver webhooks → facturación.

Hecho con cuidado por Finova.