Cancelaciones de CFDI
Esta guía explica cómo funciona la cancelación de facturas en Facturapi, qué significa cada estado y qué debes esperar después de solicitar la cancelación.
Requisitos y reglas
- Solo puedes cancelar facturas con
status: "valid". - Si la factura está en
draft, el método de cancelar la elimina. - Si la factura ya está
canceledo tiene una cancelación en curso (cancellation_status: "pending"o"verifying"), el método regresará error.
Motivos de cancelación (SAT) y sustitución
El SAT define Motivos de cancelación y Facturapi los expone con el parámetro motive. La cancelación usa el nuevo esquema de cancelación del SAT (vigente desde 2022).
01: Comprobante emitido con errores con relación. Requieresubstitutioncon el UUID (o ID de Facturapi) de la factura que sustituye a la anterior.02: Comprobante emitido con errores sin relación.03: No se llevó a cabo la operación.04: Operación nominativa relacionada en una factura global.
El SAT exige que el motivo sea coherente con el caso de uso; si no, la cancelación puede fallar.
Notas:
- Si eliges el Motivo
01, debes enviarsubstitutiony esa factura debe existir. - Si el SAT considera que tu CFDI no es cancelable (por su estatus o relaciones), la solicitud será rechazada aunque el Motivo sea válido.
Flujo de cancelación y estados
Para cancelar una factura, utiliza cancelInvoice con el Motivo de cancelación:
- cURL
- Node.js
- Python
- C#
curl "https://www.facturapi.io/v2/invoices/58e93bd8e86eb318b019743d?motive=02" \
-H "Authorization: Bearer sk_test_API_KEY" \
-X DELETE
import Facturapi from 'facturapi';
const facturapi = new Facturapi('sk_test_API_KEY');
const invoice = await facturapi.invoices.cancel(
'58e93bd8e86eb318b019743d',
{ motive: '02' }
);
from facturapi import Facturapi
facturapi = Facturapi('sk_test_API_KEY')
invoice = facturapi.invoices.cancel('58e93bd8e86eb318b019743d', {
'motive': '02'
})
var facturapi = new FacturapiClient("sk_test_API_KEY");
var invoice = await facturapi.Invoice.CancelAsync(
"58e93bd8e86eb318b019743d",
new Dictionary<string, object>
{
["motive"] = "02"
}
);
Después de llamar a cancelInvoice, puede ocurrir:
- Respuesta exitosa con
status: "canceled"ycancellation_status: "accepted"(cancelación finalizada).
{
"id": "58e93bd8e86eb318b019743d",
"status": "canceled",
"cancellation_status": "accepted",
"uuid": "39c85a3f-275b-4341-b259-e8971d9f8a94"
}
- Respuesta exitosa con
status: "valid"ycancellation_status: "pending"(requiere aceptación del receptor).
{
"id": "58e93bd8e86eb318b019743d",
"status": "valid",
"cancellation_status": "pending",
"uuid": "39c85a3f-275b-4341-b259-e8971d9f8a94"
}
- Respuesta exitosa con
status: "valid"ycancellation_status: "verifying"(el SAT recibió la solicitud y la está validando).
{
"id": "58e93bd8e86eb318b019743d",
"status": "valid",
"cancellation_status": "verifying",
"uuid": "39c85a3f-275b-4341-b259-e8971d9f8a94"
}
- Error con la explicación de por qué no se pudo cancelar.
Significado de cancellation_status:
| Estado | Significado | Qué hacer |
|---|---|---|
none | No hay solicitud registrada por el SAT. | Puedes solicitar la cancelación si aplica. |
verifying | El SAT recibió la solicitud y la está validando. | Espera la actualización automática o consulta el invoice. |
pending | Requiere aceptación del receptor. | Espera aceptación/rechazo o expiración. |
accepted | La cancelación fue aceptada. | La factura queda en status: "canceled". |
rejected | La cancelación fue rechazada. | Revisa el motivo y vuelve a intentar si aplica. |
expired | El receptor no respondió a tiempo. | Puedes intentar cancelar de nuevo si aplica. |
Actualización automática del estado
Facturapi consulta periódicamente el SAT y actualiza cancellation_status. Puedes:
- Consultar la factura con Obtener Factura.
- Suscribirte al webhook
invoice.cancellation_status_updatedpara recibir cambios cuando el estado deje de estarpendingoverifying.
Acuse de cancelación y verificación
Cuando la cancelación es aceptada, puedes descargar el acuse en XML o PDF desde el endpoint de Cancellation receipt. También puedes validar el CFDI con verification_url.
- cURL
- Node.js
- Python
- C#
# Acuse en XML
curl "https://www.facturapi.io/v2/invoices/58e93bd8e86eb318b019743d/cancellation_receipt/xml" \
-H "Authorization: Bearer sk_test_API_KEY" \
-X GET \
-o "acuse_cancelacion.xml"
# Acuse en PDF
curl "https://www.facturapi.io/v2/invoices/58e93bd8e86eb318b019743d/cancellation_receipt/pdf" \
-H "Authorization: Bearer sk_test_API_KEY" \
-X GET \
-o "acuse_cancelacion.pdf"
import Facturapi from 'facturapi';
import fs from 'node:fs';
const facturapi = new Facturapi('sk_test_API_KEY');
const xmlStream = await facturapi.invoices.downloadCancellationReceiptXml('58e93bd8e86eb318b019743d');
xmlStream.pipe(fs.createWriteStream('acuse_cancelacion.xml'));
const pdfStream = await facturapi.invoices.downloadCancellationReceiptPdf('58e93bd8e86eb318b019743d');
pdfStream.pipe(fs.createWriteStream('acuse_cancelacion.pdf'));
from facturapi import Facturapi
facturapi = Facturapi('sk_test_API_KEY')
xml_stream = facturapi.invoices.download_cancellation_receipt_xml('58e93bd8e86eb318b019743d')
with open('acuse_cancelacion.xml', 'wb') as file:
file.write(xml_stream.read())
pdf_stream = facturapi.invoices.download_cancellation_receipt_pdf('58e93bd8e86eb318b019743d')
with open('acuse_cancelacion.pdf', 'wb') as file:
file.write(pdf_stream.read())
var facturapi = new FacturapiClient("sk_test_API_KEY");
var xmlStream = await facturapi.Invoice.DownloadCancellationReceiptXmlAsync("58e93bd8e86eb318b019743d");
var xmlFile = new System.IO.FileStream("C:\\route\\to\\save\\acuse_cancelacion.xml", FileMode.Create);
xmlStream.CopyTo(xmlFile);
xmlFile.Close();
var pdfStream = await facturapi.Invoice.DownloadCancellationReceiptPdfAsync("58e93bd8e86eb318b019743d");
var pdfFile = new System.IO.FileStream("C:\\route\\to\\save\\acuse_cancelacion.pdf", FileMode.Create);
pdfStream.CopyTo(pdfFile);
pdfFile.Close();
Errores comunes
409 Conflict: la factura no está envalido ya tiene una cancelación en curso.400 Bad Request: motivo inválido o faltasubstitutioncuando el SAT lo requiere.404 Not Found: el folio o UUID no existe en el SAT.