Appearance
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:
| Esquema | Cuándo | Qué implica |
|---|---|---|
| PUE | La venta se liquidó en una sola exhibición | Un CFDI y nada más. |
| PPD | Hay crédito o varios cobros | Un 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
}
]
}status | Qué significa |
|---|---|
pending | Encolada, todavía no timbrada. |
stamped | Timbrada ante el SAT. uuid es el folio fiscal. |
failed | El timbrado falló. error dice por qué. |
canceled | Cancelada 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-invoicesEl libro de facturas de la empresa. Filtros: status, customer_id, uuid.
Ver la factura de una venta
GET
/api/v1/sales/:sale_id/invoiceread-invoicesdata 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-invoicesPará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ódigo | Qué pasó |
|---|---|
already_invoiced | Ya está timbrada. Para corregirla, usa sustituir. |
sale_without_tax | La venta tiene conceptos sin IVA, que un CFDI no admite salvo exentos o tasa 0%. |
zero_total | La venta es de $0. |
no_emitter | Faltan datos fiscales o sellos digitales de la empresa. |
customer_no_rfc | El 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ámetro | Descripción |
|---|---|
motive | Clave de cancelación del SAT. Por defecto 02. |
substitution_id | Folio 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-invoicesEmite 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-invoicesEn una venta PPD, cada cobro se documenta ante el SAT con su complemento.
| Parámetro | Descripción |
|---|---|
amount_cents | Importe del cobro. |
paid_at | Cuándo entró (ISO 8601). |
payment_form | Clave SAT de forma de pago. |
complement_id | Para 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-invoicesGET
/api/v1/sales/:sale_id/invoice/xmlread-invoicesDevuelven 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.pdfLos 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.*"] }