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

# Cancel Invoice

> Authenticate with either a Supabase session (member of the invoice's organization) or an API key belonging to that same organization. Determines automatically whether the cancellation is direct or requires receptor approval per SAT rules — Timbrix cannot confirm receptor approval in real time, so a pending cancellation must be resolved manually via the resolve endpoint. This is a FLAT route — no `organizationId` in the URL, unlike create/list above; the organization is resolved from the invoice's globally-unique `uuid`.

Requests cancellation of a stamped CFDI 4.0 before the SAT. Unlike [Create Invoice](/api-reference/invoices/create) and [List Invoices](/api-reference/invoices/list), this is a **flat** route — it does not carry `/organizations/{organizationId}/` in the URL. The invoice's `uuid` (its folio fiscal, globally unique across all organizations) is enough to resolve the owning organization server-side, combined with the caller's own auth context (session or API key).

## 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

| Parameter | Type          | Required | Description                                                                                                |
| --------- | ------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `uuid`    | string (UUID) | Yes      | Folio fiscal UUID (`uuidFiscal`) of the invoice to cancel — globally unique, not scoped to an organization |

## Request Body

| Field              | Type   | Required      | Description                                                                                                |
| ------------------ | ------ | ------------- | ---------------------------------------------------------------------------------------------------------- |
| `motivo`           | string | Yes           | SAT cancellation reason code — `01`, `02`, `03`, or `04` (see below)                                       |
| `folioSustitucion` | string | Conditional\* | Folio fiscal UUID of the CFDI that replaces this one. Required, and only meaningful, when `motivo` is `01` |

\* 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`. Ignored for `motivo` `02`, `03`, and `04`, even if sent.

### `motivo` codes

| Code | Description                                        | `folioSustitucion` |
| ---- | -------------------------------------------------- | ------------------ |
| `01` | Emitido con errores **con** relación (sustitución) | Required           |
| `02` | Emitido con errores **sin** relación               | Not applicable     |
| `03` | No se llevó a cabo la operación                    | Not applicable     |
| `04` | Operación nominativa relacionada en factura global | Not applicable     |

## 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.

<Warning>
  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](/api-reference/invoices/resolve-cancellation).
</Warning>

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

```bash cURL theme={null}
curl -X POST https://api.timbrix.mx/invoices/d3bfbc57-44af-4390-a064-f0afab85e5df/cancel \
  -H "Authorization: Bearer <your_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "motivo": "02"
  }'
```

```typescript TypeScript theme={null}
// Not yet available in @timbrix/sdk — call the REST endpoint directly
// until SDK support for cancellations ships.
const response = await fetch(
  "https://api.timbrix.mx/invoices/d3bfbc57-44af-4390-a064-f0afab85e5df/cancel",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer <your_token>",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ motivo: "02" }),
  }
)
const cancellation = await response.json()
console.log(cancellation.cancellationStatus)
```

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

```bash cURL theme={null}
curl -X POST https://api.timbrix.mx/invoices/d3bfbc57-44af-4390-a064-f0afab85e5df/cancel \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "motivo": "01",
    "folioSustitucion": "a1b2c3d4-5e6f-4890-9abc-def012345678"
  }'
```

## Example Response

Direct cancellation — resolved immediately:

```json theme={null}
{
  "id": "6a1b2c3d-4e5f-4890-9abc-def012345678",
  "invoiceId": "0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab",
  "motivo": "02",
  "folioSustitucion": null,
  "requiresApproval": false,
  "respondBy": null,
  "cancellationStatus": "aceptada",
  "requestedBy": "9f8e7d6c-5b4a-3210-fedc-ba9876543210",
  "resolvedBy": null,
  "resolvedAt": "2026-08-09T15:50:03.412Z",
  "createdAt": "2026-08-09T15:50:03.412Z"
}
```

Cancellation requiring receptor approval — still pending:

```json theme={null}
{
  "id": "6a1b2c3d-4e5f-4890-9abc-def012345678",
  "invoiceId": "0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab",
  "motivo": "02",
  "folioSustitucion": null,
  "requiresApproval": true,
  "respondBy": "2026-08-12T15:50:03.412Z",
  "cancellationStatus": "pendiente",
  "requestedBy": "9f8e7d6c-5b4a-3210-fedc-ba9876543210",
  "resolvedBy": null,
  "resolvedAt": null,
  "createdAt": "2026-08-09T15:50:03.412Z"
}
```

| Field                | Type           | Description                                                                                                                                 |
| -------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | string         | Cancellation request ID                                                                                                                     |
| `invoiceId`          | string         | Timbrix invoice record ID (not the folio fiscal UUID)                                                                                       |
| `motivo`             | string         | The cancellation reason code sent in the request                                                                                            |
| `folioSustitucion`   | string \| null | Folio fiscal UUID of the replacement CFDI — only set for `motivo: "01"`                                                                     |
| `requiresApproval`   | boolean        | Whether this cancellation needed receptor approval, computed server-side                                                                    |
| `respondBy`          | string \| null | Deadline (ISO 8601) for the receptor to respond — 3 business days from when the cancellation was requested. `null` for direct cancellations |
| `cancellationStatus` | string         | `pendiente`, `aceptada`, or `rechazada`                                                                                                     |
| `requestedBy`        | string \| null | Timbrix user ID who requested the cancellation                                                                                              |
| `resolvedBy`         | string \| null | Timbrix user ID who confirmed the final outcome — `null` for direct cancellations, which resolve automatically                              |
| `resolvedAt`         | string \| null | ISO 8601 timestamp the outcome was confirmed — `null` while `pendiente`                                                                     |
| `createdAt`          | string         | ISO 8601 timestamp the cancellation was requested                                                                                           |

## 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).

```json theme={null}
{
  "statusCode": 400,
  "message": "`folioSustitucion` es requerido cuando `motivo` es 01 (sustitución)",
  "error": "BAD_REQUEST"
}
```

### 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](/api-reference/invoices/resolve-cancellation) — or wait for the receptor to respond — before requesting another.

```json theme={null}
{
  "statusCode": 409,
  "message": "Esta factura ya tiene una cancelación pendiente de resolución",
  "error": "CONFLICT"
}
```


## OpenAPI

````yaml POST /invoices/{uuid}/cancel
openapi: 3.1.0
info:
  title: Timbrix API
  description: >-
    REST API with OAuth2 server for managing organizations, members, and
    webhooks
  version: '1.0'
  contact: {}
servers:
  - url: https://api.timbrix.mx
    description: Production
security: []
tags:
  - name: organizations
    description: Organization management endpoints
  - name: oauth
    description: OAuth2 authentication and authorization
  - name: webhooks
    description: Webhook configuration and delivery
  - name: users
    description: User information endpoints
  - name: api-keys
    description: API Keys management and validation
  - name: invoices
    description: CFDI 4.0 invoice creation, listing, and cancellation
paths:
  /invoices/{uuid}/cancel:
    post:
      tags:
        - invoices
      summary: Request cancellation of a stamped CFDI
      description: >-
        Authenticate with either a Supabase session (member of the invoice's
        organization) or an API key belonging to that same organization.
        Determines automatically whether the cancellation is direct or requires
        receptor approval per SAT rules — Timbrix cannot confirm receptor
        approval in real time, so a pending cancellation must be resolved
        manually via the resolve endpoint. This is a FLAT route — no
        `organizationId` in the URL, unlike create/list above; the organization
        is resolved from the invoice's globally-unique `uuid`.
      operationId: InvoicesController_cancel
      parameters:
        - name: uuid
          required: true
          in: path
          description: Folio fiscal UUID of the invoice to cancel
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelInvoiceDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceCancellationDto'
        '400':
          description: >-
            Invalid motivo/folioSustitucion, invoice already cancelled, or
            mass-cancellation limit reached
        '403':
          description: Caller does not belong to the invoice's organization
        '404':
          description: uuid does not match any invoice
        '409':
          description: A cancellation is already pending for this invoice
      security:
        - apiKey: []
        - bearer: []
components:
  schemas:
    CancelInvoiceDto:
      type: object
      properties:
        motivo:
          type: string
          example: '02'
          enum:
            - '01'
            - '02'
            - '03'
            - '04'
          description: >-
            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
        folioSustitucion:
          type: string
          example: d3bfbc57-44af-4390-a064-f0afab85e5df
          description: >-
            UUID fiscal del CFDI que sustituye a este. Requerido cuando
            motivo=01, debe ser una factura vigente de la misma organización.
      required:
        - motivo
    InvoiceCancellationDto:
      type: object
      properties:
        id:
          type: string
          example: 6a1b2c3d-4e5f-4890-9abc-def012345678
        invoiceId:
          type: string
          description: Timbrix invoice record ID
          example: 0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab
        motivo:
          type: string
          enum:
            - '01'
            - '02'
            - '03'
            - '04'
          example: '02'
        folioSustitucion:
          type: string
          nullable: true
          example: null
        requiresApproval:
          type: boolean
          example: false
        respondBy:
          type: string
          format: date-time
          nullable: true
          example: null
        cancellationStatus:
          type: string
          enum:
            - pendiente
            - aceptada
            - rechazada
          example: aceptada
        requestedBy:
          type: string
          nullable: true
          example: 9f8e7d6c-5b4a-3210-fedc-ba9876543210
        resolvedBy:
          type: string
          nullable: true
          example: null
        resolvedAt:
          type: string
          format: date-time
          nullable: true
          example: null
        createdAt:
          type: string
          format: date-time
          example: '2026-08-09T15:50:03.412Z'
      required:
        - id
        - invoiceId
        - motivo
        - requiresApproval
        - cancellationStatus
        - createdAt
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'API Key for authentication (format: sk_...)'
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http

````