Skip to main content
POST /cfdi/cancelar acepta exactamente uno de dos identificadores: Nunca los dos, nunca ninguno: ambos son UUID y nada en su forma los distingue, así que adivinar significaría cancelar el documento equivocado de vez en cuando. Enviar los dos, o ninguno, es 400 VALIDATION_ERROR. motivo es una de las claves del SAT: 01 (emitido con errores con relación), 02 (emitido con errores sin relación), 03 (no se llevó a cabo la operación) y 04 (operación nominativa relacionada en una factura global).
Un CFDI de otra cuenta responde 404 CFDI_NOT_FOUND, la misma respuesta que un id que nunca se emitió. Vale para cancelar y para las cinco rutas de consulta.

uuid_sustitucion

Sólo el motivo 01 lo usa: es el CFDI que sustituye al que estás cancelando. Si cancelas con motivo=01 y lo omites, la respuesta es 400 UUID_SUSTITUCION_REQUIRED.
Puedes enviar el folio fiscal del CFDI sustituto o su id de Ipsofactura. Con motivo=01 la API resuelve uuid_sustitucion primero como folio fiscal (el UUID del TimbreFiscalDigital, como lo define el SAT) y, si no corresponde a ninguno de tus CFDI, como id interno (el mismo que usarías en GET /cfdi/{id}). En ambos casos el documento tiene que estar en tu cuenta; si no, responde 404 CFDI_NOT_FOUND. El sustituto no puede ser el mismo CFDI que cancelas ni uno ya cancelado: ambos casos responden 400 VALIDATION_ERROR.
Cancelación con sustitución
Es el mismo identificador que usas en cfdi_relacionados al timbrar (folio fiscal), así que puedes reutilizar el uuid que te devolvió el timbrado del sustituto sin traducirlo.

Qué se valida y qué no

Un CFDI sustituto sin timbrar no es un caso posible: un CFDI sólo se registra en tu cuenta después de que el PAC lo timbra, así que todo id que exista tiene folio fiscal.
El CFDI sustituto debe ser tuyo y del mismo emisor que el CFDI cancelado. Hoy la API no verifica esa correspondencia sobre el sustituto (sí sobre el CFDI cancelado, con 400 RFC_MISMATCH si rfc_emisor no coincide): si envías el id equivocado, la inconsistencia la detecta el SAT y la cancelación se rechaza allí.

Refacturación: la cadena completa

Cancelar con motivo=01 es el segundo paso de una refacturación, no el primero:
  1. Timbra el CFDI correcto con POST /cfdi/timbrar, declarando el CFDI que va a sustituir en cfdi_relacionados con tipo_relacion 04 (Sustitución de los CFDI previos). Ahí los UUIDs son folios fiscales.
  2. Cancela el CFDI original con POST /cfdi/cancelar, motivo=01 y uuid_sustitucion = el uuid (folio fiscal) o el id que te devolvió el paso 1.
Paso 1 — el sustituto declara la relación 04
En cfdi_relacionados sí puedes relacionar un folio fiscal que no timbramos nosotros (uno de tu proveedor anterior, por ejemplo): la API lo acepta. uuid_sustitucion no: el sustituto tiene que ser un CFDI de tu cuenta, salvo cuando cancelas un comprobante externo (abajo). Para el caso de una factura global, ver Cancelación de una global con ventas ya facturadas.

Cancelar un CFDI timbrado con otro proveedor

Si migraste a Ipsofactura con CFDI vigentes que timbró tu proveedor anterior, puedes cancelarlos aquí: manda su folio_fiscal en lugar de id. No hace falta el XML original, ni el total, ni el receptor — el SAT sólo necesita el UUID, el RFC emisor y el motivo. Si el folio fiscal resulta ser de un CFDI que timbramos nosotros, se resuelve a ese comprobante y su cancelación es la de siempre: no se crea un registro aparte.
Cancelar por folio fiscal

Precondición: la empresa y su CSD

rfc_emisor debe ser una empresa activa de tu cuenta y con CSD vigente. Ese certificado es el que firma la solicitud de cancelación, así que sin él la petición no se puede ni formular. Si no se cumple, la respuesta es 400 EXTERNAL_CANCELLATION_NOT_ELIGIBLE. Es también lo que acota la ruta a tu cuenta: su único otro dato es un UUID que cualquiera podría citar.

rfc_receptor

Opcional, y sólo se toma en cuenta al cancelar por folio_fiscal: es el único dato del comprobante ajeno que podemos registrar, y queda en la fila para que aparezca en el webhook y en la consulta. Al cancelar por id se ignora — ahí el receptor que ya tenemos en el comprobante manda. En el sandbox es además lo que selecciona un escenario forzado: los mismos RFC reservados del timbrado, por ejemplo el que deja la cancelación en_proceso para que puedas ejercitar el webhook del desenlace.

uuid_sustitucion de un comprobante ajeno

Con motivo=01, uuid_sustitucion se resuelve primero contra tus CFDI (folio fiscal o id). Cuando estás cancelando un CFDI externo y ese valor no corresponde a ninguno, se toma tal cual como folio fiscal: el sustituto de un comprobante que timbró el proveedor anterior suele ser otro de los suyos. Cancelando un CFDI nuestro, un identificador desconocido sigue siendo 404 CFDI_NOT_FOUND.

Qué queda registrado de un CFDI externo

Cancelar por folio_fiscal deja un registro en tu cuenta para sostener el trámite: el acuse, la reconciliación de una cancelación en_proceso, GET /cfdi/{id}/estatus y los webhooks funcionan exactamente igual que con un CFDI que timbramos nosotros. Ese registro se declara como tal. GET /cfdi/{id} devuelve un campo origen: Una fila externo trae únicamente el folio fiscal, el emisor y el rastro de la cancelación. No hay conceptos, totales, receptor completo, sello, XML ni PDF: nunca vimos el comprobante, y rellenar esos campos con 0.00 o con un RFC genérico sería inventar datos fiscales. En consecuencia:
  • se excluye de GET /cfdi, para que un listado no mezcle comprobantes con registros;
  • GET /cfdi/{id}/xml responde 404 DOCUMENT_NOT_AVAILABLE;
  • GET /cfdi/{id}/pdf responde 400 VALIDATION_ERROR: sin sello del SAT no hay representación impresa que emitir;
  • GET /cfdi/{id}/acuse y GET /cfdi/{id}/estatus sí responden.
GET /cfdi/{id} de un CFDI externo (extracto)

Consultar un CFDI por su folio fiscal

GET /cfdi/{id} y sus cuatro rutas hermanas —/xml, /pdf, /estatus, /acuse— aceptan en el path cualquiera de los dos identificadores: nuestro id interno o el folio fiscal del SAT. Se prueba primero el id; si no resuelve a nada de tu cuenta, el mismo valor se reintenta como folio fiscal. Así, el UUID que te dio el SAT sirve para consultar sin tener que buscarlo antes en GET /cfdi. GET /cfdi/{id}/estatus devuelve en su campo id el identificador interno del CFDI, no lo que pusiste en el path.
POST /cfdi/cancelar es la excepción: ahí sigues declarando cuál de los dos envías, porque los trata distinto — un folio_fiscal desconocido registra un CFDI externo, y un id desconocido es 404.