Skip to main content
POST
Request cancellation of a stamped CFDI
Requests cancellation of a stamped CFDI 4.0 before the SAT. Unlike Create Invoice and List Invoices (which resolve the organization from an API key or the X-Organization-Id header), this route resolves the organization from the invoice’s own uuid — its folio fiscal, globally unique across all organizations — combined with the caller’s own auth context (session or API key), so no organization identifier needs to be supplied separately at all.

Authentication

Accepts either:
  • A Supabase Bearer session (Authorization: Bearer <token>) — the authenticated user must be a member of the invoice’s organization.
  • An API key (X-API-Key: sk_...) with the write:invoices scope — the key’s own organization must match the invoice’s organization, or the request is rejected with 403 Forbidden.

Path Parameters

Request Body

* Required when motivo is 01 (sustitución). Must be the folio fiscal of an existing, vigente invoice belonging to the same organization, and cannot be the invoice being cancelled itself — an arbitrary or unverified UUID is rejected with a 400. It must also be a CFDI issued with relatedCfdis: [{ "type": "04", "uuids": ["<this invoice's UUID>"] }] and dated no earlier than the invoice being cancelled (see Substitution flow). Ignored for motivo 02, 03, and 04, even if sent.

motivo codes

Substitution flow (motivo 01)

The SAT only accepts motivo: "01" when the replacement CFDI relates the original with TipoRelacion 04 and is not dated before it. Timbrix checks both against the stamped XML before calling the PAC. Order matters: first stamp the replacement (B), then cancel the original (A).
  1. Issue B, relating A with relatedCfdis (see Create Invoice):
cURL
  1. Cancel A with motivo: "01" and folioSustitucion set to the UUID returned for B:
cURL
If B does not meet the rules, the request is rejected with a 400 before reaching the SAT. These messages replace the SAT rejections 312 and 208:
  • B does not relate A with TipoRelacion 04 (SAT code 312): La factura sustituta <serie>-<folio> no relaciona a esta factura como sustitución (TipoRelacion 04). El SAT rechazaría la cancelación (código 312). Emite la factura sustituta con "Sustituir factura".
  • B is dated earlier than A (SAT code 208): La factura sustituta no puede tener fecha anterior a la original (el SAT la rechazaría con el código 208)
Other 400/404 messages for folioSustitucion: La factura sustituta no puede ser la misma factura que se está cancelando, La factura sustituta debe estar vigente, La factura sustituta pertenece a otro ambiente (sandbox/producción), No se pudo leer el CFDI sustituto, and Factura sustituta <uuid> no encontrada (404).

How eligibility is determined

Timbrix computes, from the invoice’s own data, whether the cancellation applies directly or requires the receptor’s approval — this follows the SAT’s own rule and you never send it yourself; it’s returned in the response as requiresApproval. The cancellation is direct (no approval needed) when at least one of these is true:
  • tipoComprobante is E (Egreso) or T (Traslado)
  • total is ≤ $5,000 MXN
  • rfcReceptor is XAXX010101000 (público en general) or XEXX010101000 (residente extranjero)
  • The invoice was issued 3 business days ago or less (Mon–Fri, America/Mexico_City calendar)
Otherwise, the cancellation requires receptor approval: the SAT notifies the receptor, who has 3 business days to accept or reject it through the SAT portal.
Timbrix cannot poll the SAT in real time for the receptor’s response. A cancellation that requires approval comes back with cancellationStatus: "pendiente" and a respondBy deadline — the invoice stays vigente until you confirm the actual outcome yourself, after checking the SAT portal, via Resolve Cancellation.
A direct cancellation, by contrast, is resolved synchronously in the same request: the response comes back with cancellationStatus: "aceptada" and the invoice’s status flips to cancelado immediately.

Mass cancellation limit

Organizations on the Starter or Pro plan are limited to 50 cancellation requests per calendar day; the 51st request that day returns 400 Bad Request. Business and Enterprise plans have no daily limit.

Example Request

cURL
TypeScript
Sustitución (motivo: "01") — replacing this CFDI with an already-stamped corrected one:
cURL

Example Response

Direct cancellation — resolved immediately:
Cancellation requiring receptor approval — still pending:

Common Errors

400 Bad Request

motivo is invalid; folioSustitucion is missing when motivo is 01, is not a vigente invoice of the same organization, or is the same invoice being cancelled; the invoice is already cancelado; or the organization has hit the daily mass-cancellation limit (Starter/Pro plans).

401 Unauthorized

Missing or invalid Bearer token / API key.

403 Forbidden

The authenticated user is not a member of the invoice’s organization, or the API key does not have the write:invoices scope / belongs to a different organization than the one that owns the invoice.

404 Not Found

uuid does not match any invoice, or (when motivo is 01) folioSustitucion does not match any invoice belonging to the same organization.

409 Conflict

A cancellation is already pendiente for this invoice. Resolve it via Resolve Cancellation — or wait for the receptor to respond — before requesting another.

Authorizations

X-API-Key
string
header
required

API Key for authentication (format: sk_...)

Path Parameters

uuid
string
required

Body

application/json
motivo
enum<string>
required

01 = emitido con errores con relación (requiere folioSustitucion), 02 = emitido con errores sin relación, 03 = no se llevó a cabo la operación, 04 = operación nominativa relacionada en factura global

Available options:
01,
02,
03,
04
Example:

"02"

folioSustitucion
string

UUID fiscal del CFDI que sustituye a este. Requerido cuando motivo=01, debe ser una factura vigente de la misma organización.

Example:

"d3bfbc57-44af-4390-a064-f0afab85e5df"

Response

id
string
required
invoiceId
string
required
motivo
enum<string>
required
Available options:
01,
02,
03,
04
requiresApproval
boolean
required
cancellationStatus
enum<string>
required
Available options:
pendiente,
aceptada,
rechazada
createdAt
string<date-time>
required
folioSustitucion
object
respondBy
object
requestedBy
object
resolvedBy
object
resolvedAt
object