Cancelar CFDI con motivo 01: factura sustituta paso a paso
URL de esta página: https://cfdi.express/docs/cancelar-factura-sustitucion
Disponible a partir de la versión v1.50.0 de la API. Desde la v1.50.1 los UUID se aceptan en minúsculas.
El motivo 01 (comprobante emitido con errores con relación) se usa cuando ya timbraste la factura correcta y vas a cancelar la que tenía el error. El SAT pide dos cosas: que la nueva relacione a la anterior con TipoRelacion 04 (sustitución de los CFDI previos) y que, al cancelar, indiques el UUID de esa sustituta.
En la API son dos llamadas, en este orden:
POST /v1/invoices— timbras la sustituta concfdiRelacionados.POST /v1/invoices/{id}/cancel— cancelas la original conmotivo01yfolioSustitucion.
En la sustituta el tipo de relación es 04. En la cancelación el motivo es 01.
Timbrar la sustituta cuesta un timbre. Cancelar la original cuesta otro. Si la sustituta no está relacionada con la original, la cancelación responde 400 con code validation_error y subcode substitute_not_related, y no gasta ese timbre. El detalle está más abajo.
Esta guía es de la API. En la app de Shopify el admin cancela por ahora con motivo 02. Ese flujo está en Cancelación y acuse.
Autenticación: Authorization: Bearer sk_test_… o sk_live_…. En un workspace, el header x-account-id indica la cuenta. Las dos llamadas piden Idempotency-Key (1 a 255 caracteres).
1. Emitir la factura sustituta
POST https://api.cfdi.express/v1/invoices
El cuerpo es el de Timbrar una factura, más cfdiRelacionados:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
cfdiRelacionados |
object | sí, en este flujo | CFDIs relacionados (Anexo 20, nodo CfdiRelacionados). |
cfdiRelacionados.tipoRelacion |
string | sí | Catálogo c_TipoRelacion. En facturas de ingreso solo se acepta "04" (sustitución de los CFDI previos). |
cfdiRelacionados.uuids |
string[] | sí | Folios fiscales de los CFDI que esta factura sustituye. De 1 a 100. Pueden estar timbrados en esta API o en otro sistema. |
uuids lleva el UUID (folio fiscal) de la factura original. El id (inv…) de esa misma factura es el que va en la ruta del paso 2.
La petición espera hasta 20 segundos. 201 trae la factura timbrada: copia uuid, que es el folio fiscal de la sustituta. Bajo carga extrema responde 202 con status stamping. En ese caso consultas GET /v1/invoices/{id} hasta que pase a stamped y tomas el uuid. Cancela la original cuando ya tengas ese UUID.
cURL
El UUID de la original en este ejemplo es el del esquema publicado. merchantId, receptor y concepto son los de la referencia de timbrado: sirven para ver la forma del JSON.
curl -X POST https://api.cfdi.express/v1/invoices \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: order_1001_sustituta" \
-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%",
"quantity": 2,
"unitPrice": 1740.00,
"ivaRate": 0.16
}
],
"pricesIncludeTax": true,
"cfdiRelacionados": {
"tipoRelacion": "04",
"uuids": ["119038DB-1B9F-4BC5-8A71-712E95AED2C9"]
}
}'
En la respuesta 201, el campo que vas a usar en el paso 2 es uuid. El id de esa misma respuesta es el de la sustituta. La cancelación del paso 2 va sobre el id de la original.
2. Cancelar la original con motivo 01
POST https://api.cfdi.express/v1/invoices/{id}/cancel
{id} es el id de la factura original (inv…). El UUID de la original ya fue en cfdiRelacionados.uuids del paso 1. En esta ruta entra el id de la original; el id de la sustituta se queda en el recurso del paso 1. La referencia general de esta operación está en Cancelar una factura.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
motivo |
string | sí | "01" para sustitución. |
folioSustitucion |
string (uuid) | sí, con 01 |
UUID de la sustituta (uuid del paso 1). Sin este campo, motivo 01 responde 400 validation_error. |
cURL
folioSustitucion es el uuid que devolvió el timbrado de la sustituta. El de abajo es un ejemplo; sustitúyelo por el de tu 201.
curl -X POST https://api.cfdi.express/v1/invoices/inv7k3q9x2m4v1t8p6d0n5cb/cancel \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: cancel_order_1001_sustitucion" \
-H "Content-Type: application/json" \
-d '{
"motivo": "01",
"folioSustitucion": "7a2c8e10-55b4-4c31-9a12-1d0e6f3b8c44"
}'
La cancelación aceptada responde 202. El recurso queda con status cancelled y cancellation.motivo 01. cancellation.folioSustitucion es el UUID que enviaste. El acuse XML del SAT queda en cancellation.acuseUrl, URL firmada de 15 minutos. La forma completa de la respuesta está en Cancelar una factura.
Una factura PPD con complementos de pago vigentes se cancela después de cancelar esos REP con POST /v1/payments/{id}/cancel.
Error 400 substitute_not_related
substitute_not_related es el subcode. El code sigue siendo validation_error.
La API busca la sustituta por folioSustitucion entre las facturas timbradas en CFDI Express. Responde 400 cuando esa factura no trae cfdiRelacionados con tipoRelacion "04" que incluya el UUID de la original. La validación corre antes de enviar la cancelación al SAT y antes de gastar un timbre.
{
"type": "https://api.cfdi.express/docs/errors/validation_error",
"title": "Validation error",
"status": 400,
"code": "validation_error",
"subcode": "substitute_not_related"
}
El detail sugiere timbrar la sustituta con cfdiRelacionados { tipoRelacion: "04", uuids: [UUID de la original] } o cancelar con motivo 02.
Si folioSustitucion es el UUID de la misma factura que estás cancelando, la respuesta también es 400 validation_error.
Si omites folioSustitucion con motivo 01, o el motivo no es 01–04, la respuesta es 400 validation_error. Ese caso está en Cancelar una factura.
UUID en minúsculas (v1.50.1)
Desde la v1.50.1 un UUID se acepta en minúsculas, en mayúsculas o mezclado, en cfdiRelacionados.uuids y en folioSustitucion. El formato es 8-4-4-4-12 en hexadecimal.
119038db-1b9f-4bc5-8a71-712e95aed2c9 y 119038DB-1B9F-4BC5-8A71-712E95AED2C9 son el mismo folio fiscal.
Los otros motivos
Los motivos 02, 03 y 04 cubren los casos sin factura sustituta. El cuerpo lleva solo motivo:
| Motivo | Cuándo |
|---|---|
02 |
Comprobante emitido con errores sin relación. |
03 |
La operación no se llevó a cabo. |
04 |
Operación nominativa relacionada en una factura global. |
El cuerpo, los ejemplos y el resto de errores (402 sin saldo, 422 rechazo del SAT, 503 PAC no disponible) están en Cancelar una factura.
Relacionados
- Timbrar una factura —
POST /v1/invoices - Cancelar una factura — motivos 01–04 y acuse
- Acuse de cancelación — qué es el acuse y los cuatro motivos
- Cancelación y acuse — cancelar desde la app de Shopify
Referencia completa: https://cfdi.express/docs/api · OpenAPI: https://api.cfdi.express/openapi.json · Llaves: https://dash.cfdi.express
