> ## Documentation Index
> Fetch the complete documentation index at: https://invoiceapi.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Cancellation

> Cancelling a CFDI is not a delete — it is a SAT-mediated request that may require the receptor's

Cancelling a CFDI is not a delete — it is a SAT-mediated request that may require the receptor's
acceptance and always produces an *acuse de cancelación*. This API models it as an async lifecycle
on the invoice.

## Request

```bash theme={null}
curl -s -X POST localhost:3000/v1/invoices/$INV_ID/cancel \
  -H "authorization: Bearer $API_KEY" -H 'content-type: application/json' \
  -d '{ "reason": "02" }' | jq '.cancellation'
```

Response is the invoice with an updated `cancellation` object:

```json theme={null}
{ "status": "cancel_pending", "reason": "02",
  "substitute_invoice_id": null, "acuse_available": false }
```

## Motivos de cancelación (SAT)

| `reason` | Meaning                                                                         | Substitute UUID?                                                       |
| :------: | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|   `01`   | Comprobante emitido con **errores con relación** — there is a replacement CFDI. | **Required** — pass `substitute_invoice_id` (the replacement invoice). |
|   `02`   | Comprobante emitido con **errores sin relación** — no replacement.              | No.                                                                    |
|   `03`   | **No se llevó a cabo** la operación.                                            | No.                                                                    |
|   `04`   | Operación **nominativa relacionada en una factura global**.                     | No.                                                                    |

For `01`, first stamp the corrected invoice, then cancel the original referencing it:

```bash theme={null}
curl -s -X POST localhost:3000/v1/invoices/$OLD_ID/cancel \
  -H "authorization: Bearer $API_KEY" -H 'content-type: application/json' \
  -d "{ \"reason\": \"01\", \"substitute_invoice_id\": \"$NEW_ID\" }" | jq '.cancellation'
```

Sending a substitute for 02/03/04 (or omitting it for 01) is rejected by the SAT — see the
`cancel.substitution_folio_*` and `cancel.motivo_*` codes in [`../errors.md`](/concepts/errors).

## The cancellation lifecycle

```
none ──cancel──▶ cancel_pending ──receptor accepts / 72h lapse──▶ cancelled
                        │
                        └── receptor rejects ─────────────────────▶ cancel_rejected
```

* **`cancelled` immediately** when the CFDI is *cancelable sin aceptación* — e.g. low-amount
  invoices, or a receptor of `XAXX010101000` (público en general). The SAT accepts on submission.
* **`cancel_pending`** when the CFDI is *cancelable con aceptación*. The receptor has **72 business
  hours** to accept or reject in their SAT buzón; if they do nothing it auto-cancels at the
  deadline. The API polls `consultarEstatusComprobante` on a job and finalizes the status.
* **`cancel_rejected`** when the receptor rejects, or the SAT returns a definitive negative verdict
  (e.g. the UUID is referenced by another live CFDI → `cancel.not_applicable` / `cancel.not_cancellable`).

### The receptor-acceptance flow (con aceptación)

1. You submit the cancellation → `cancel_pending`.
2. The receptor sees a *solicitud de cancelación* in their SAT portal and accepts or rejects it.
3. The worker's cancellation-poll job re-queries SAT status until it resolves, then updates the
   invoice and emits a webhook: `invoice.cancelled` or `invoice.cancel_rejected`.

Subscribe to those events instead of polling yourself:

```bash theme={null}
curl -s -X POST localhost:3000/v1/webhook_endpoints \
  -H "authorization: Bearer $API_KEY" -H 'content-type: application/json' \
  -d '{ "url": "https://you.example/hook", "enabled_events": ["invoice.cancelled","invoice.cancel_rejected"] }'
```

## The acuse

Once `status` is `cancelled`, `cancellation.acuse_available` becomes `true`. The acuse is the SAT's
XML receipt of the cancellation — retain it as proof for your accounting.

## Notes

* Only a **stamped** invoice can be cancelled; drafts have no UUID at the SAT.
* Cancellation is signed with the emitter's CSD (handled server-side from the fiscal profile).
* Time-window rules apply: cancellations past the period's declaration date, or past the
  global-invoice deadline, are refused (`cancel.date_after_declaration`, `cancel.global_invoice_deadline`).

Under the **simulated PAC**, whether a cancellation resolves immediately or stays pending depends
on the receptor RFC (a público-en-general receptor cancels sin aceptación); pending cancellations
are advanced by the poll job when a shared PAC is wired (see the integration notes).
