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

# Errores y solución de problemas

> Formato de error de la API, catálogo completo de códigos y cómo reaccionar a cada uno.

## Formato de error

Todos los errores de la API usan el mismo formato JSON:

```json theme={null}
{
  "code": "CERTIFICATE_EXPIRED",
  "message": "El certificado para este RFC ha expirado"
}
```

| Campo      | Descripción                                                                                                                         |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `code`     | Código de error estable, en `SCREAMING_SNAKE_CASE`. Úsalo para la lógica de tu código; viene del catálogo de abajo.                 |
| `message`  | Descripción legible (en español) del error. Puede cambiar de redacción; no hagas matching sobre él.                                 |
| `sat_code` | *(opcional)* Código de validación del SAT (`CFDIxxxxx`) cuando el rechazo viene del SAT. Ver [Rechazos del SAT](#rechazos-del-sat). |

Los campos opcionales se **omiten** cuando no aplican: nunca llegan como `null`.

### Rechazos del SAT

Ipsofactura orquesta el timbrado contra una red de PACs con failover automático; ese
detalle es interno y no cambia el formato del error. Cuando el SAT rechaza un CFDI,
recibes un solo `VALIDATION_ERROR` con el código de validación del SAT (tipo `CFDIxxxxx`)
en el campo `sat_code`:

```json theme={null}
{
  "code": "VALIDATION_ERROR",
  "message": "CFDI40161 - El UsoCFDI 'D01' no es aplicable para el RegimenFiscal '601' del receptor.",
  "sat_code": "CFDI40161"
}
```

#### `code` vs. `sat_code`

Son dos códigos con dueños distintos, y por eso conviven:

|                           | `code`                                                                        | `sat_code`                                                                                            |
| ------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **De quién es**           | De Ipsofactura.                                                               | Del SAT.                                                                                              |
| **Qué tan específico es** | Grueso: **todo** rechazo de validación del SAT llega como `VALIDATION_ERROR`. | Específico: identifica la regla exacta que incumplió el comprobante.                                  |
| **Cuándo está presente**  | Siempre.                                                                      | Sólo cuando el rechazo viene del SAT (a través de un PAC).                                            |
| **Para qué sirve**        | Decidir el flujo general: reintentar, re-autenticar, corregir el comprobante. | Ramificar por regla del SAT: mapearla a un mensaje tuyo, a un campo de tu formulario, o a un runbook. |

<Warning>
  **No parsees `message`.** Cuando el rechazo viene del SAT, ese texto lo redacta el PAC que
  atendió el timbrado: varía entre proveedores y puede cambiar sin previo aviso. Antes era la
  única forma de saber qué regla se incumplió; hoy esa información es `sat_code`. Usa `message`
  para mostrarlo o registrarlo, nunca para decidir.
</Warning>

Dos reglas más sobre `sat_code`:

* **Se omite en nuestros propios errores** — validación local, autenticación, rate limit,
  servicio no disponible. Que el campo esté presente ya te dice que fue el SAT quien rechazó
  el comprobante, no nosotros.
* **Nunca se inventa.** Si la respuesta del PAC no trae ningún código, el campo se omite en
  lugar de adivinarlo. Trátalo siempre como opcional:

```python theme={null}
sat_code = error.get("sat_code")          # puede no venir
if sat_code == "CFDI40147":
    ...  # el RFC del receptor no está en la lista del SAT
```

***

## Catálogo de códigos

El status HTTP indica la familia del problema; `code` el motivo exacto.

### 400 — Error de validación

Tu request tiene un problema. Corrígelo antes de reintentar.

| Código                               | Causa                                                                                                                                                                                                                                                                                                             |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`                   | Validación genérica: falta un campo requerido, un valor no está en el catálogo del SAT, o el SAT rechazó el CFDI (en ese caso el error trae `sat_code` con el código `CFDIxxxxx`).                                                                                                                                |
| `INVALID_RFC_FORMAT`                 | El RFC no tiene formato válido (12 caracteres para moral, 13 para física).                                                                                                                                                                                                                                        |
| `INVALID_DATE_FORMAT`                | Una fecha no cumple ISO 8601.                                                                                                                                                                                                                                                                                     |
| `INVALID_UUID_FORMAT`                | Un UUID no tiene el formato 8-4-4-4-12 hexadecimal.                                                                                                                                                                                                                                                               |
| `INVALID_TIPO_COMPROBANTE`           | `tipo_comprobante` debe ser uno de: `I`, `E`, `T`, `P`, `N`.                                                                                                                                                                                                                                                      |
| `INVALID_MOTIVO`                     | El motivo de cancelación debe ser uno de: `01`, `02`, `03`, `04`.                                                                                                                                                                                                                                                 |
| `UUID_SUSTITUCION_REQUIRED`          | Cancelaste con `motivo=01` (emitido con errores **con relación**) sin enviar `uuid_sustitucion`. Ver [Cancelación de CFDI](/cancelacion#uuid_sustitucion).                                                                                                                                                        |
| `CFDI_ALREADY_CANCELLED`             | El CFDI ya está cancelado.                                                                                                                                                                                                                                                                                        |
| `RFC_MISMATCH`                       | El RFC enviado en la cancelación no coincide con el RFC emisor del CFDI.                                                                                                                                                                                                                                          |
| `EXTERNAL_CANCELLATION_NOT_ELIGIBLE` | Cancelaste por `folio_fiscal` (un CFDI que timbró otro proveedor) y el `rfc_emisor` no es una empresa **activa** de tu cuenta **con CSD vigente**. Ese certificado es el que firma la cancelación. Ver [Cancelar un CFDI timbrado con otro proveedor](/cancelacion#cancelar-un-cfdi-timbrado-con-otro-proveedor). |
| `CALCULATION_MISMATCH`               | Los importes no cuadran (impuestos vs. conceptos vs. total).                                                                                                                                                                                                                                                      |
| `SUBTOTAL_MISMATCH`                  | El `subtotal` no coincide con la suma de los importes de los conceptos.                                                                                                                                                                                                                                           |
| `IMPORTE_EXCEEDS_LIMIT`              | El importe total excede 18 dígitos.                                                                                                                                                                                                                                                                               |
| `INVALID_CURRENCY`                   | La moneda no está en el catálogo `c_Moneda` del SAT.                                                                                                                                                                                                                                                              |
| `TIPO_CAMBIO_REQUIRED`               | Falta `tipo_cambio` para un CFDI en moneda distinta de MXN.                                                                                                                                                                                                                                                       |
| `CONCEPTO_REQUIRED`                  | El CFDI no incluye ningún concepto.                                                                                                                                                                                                                                                                               |
| `DESCUENTO_EXCEEDS_IMPORTE`          | El descuento de un concepto es mayor que su importe.                                                                                                                                                                                                                                                              |
| `RFC_INACTIVE`                       | El RFC emisor está inactivo en tu cuenta.                                                                                                                                                                                                                                                                         |
| `CERTIFICATE_NOT_FOUND`              | La empresa emisora no tiene un CSD **activo** para timbrar. Súbelo con `POST /empresas/{empresa_id}/certificados`.                                                                                                                                                                                                |
| `CERTIFICATE_EXPIRED`                | El CSD del emisor está vencido. Tramita uno nuevo ante el SAT y súbelo.                                                                                                                                                                                                                                           |
| `CERTIFICATE_NOT_YET_VALID`          | El CSD aún no es vigente: su vigencia empieza en el futuro, así que todavía no sella nada que el SAT acepte. Espera a la fecha de inicio y súbelo entonces.                                                                                                                                                       |
| `CERTIFICATE_REVOKED`                | El SAT **revocó** el CSD del emisor. No es lo mismo que `CERTIFICATE_EXPIRED`: un CSD vencido se renueva en su fecha, un CSD revocado está muerto y la empresa se queda sin certificado activo. Tramita uno nuevo ante el SAT y súbelo.                                                                           |
| `CERTIFICATE_RFC_MISMATCH`           | El RFC del CSD no coincide con el RFC de la empresa.                                                                                                                                                                                                                                                              |
| `INVALID_BASE64`                     | `certificado_cer_base64`/`llave_key_base64` no son Base64 válido, o (en `POST /cfdi/timbrar-xml`) `xml_base64` no es Base64 válido.                                                                                                                                                                               |
| `KEY_CERT_MISMATCH`                  | La llave privada `.key` no corresponde al certificado `.cer`.                                                                                                                                                                                                                                                     |
| `INVALID_PASSWORD`                   | La contraseña de la llave privada es incorrecta.                                                                                                                                                                                                                                                                  |
| `INVALID_NUMERO_CERTIFICADO`         | El `numero_certificado` no coincide con el del `.cer` o no tiene 20 dígitos.                                                                                                                                                                                                                                      |
| `INVALID_XML`                        | (`POST /cfdi/timbrar-xml`) El XML decodificado está mal formado, o su raíz no es un `cfdi:Comprobante` en el namespace de CFDI 4.0.                                                                                                                                                                               |
| `XSD_VALIDATION_ERROR`               | (`POST /cfdi/timbrar-xml`) El documento no cumple los esquemas del SAT. `message` incluye hasta 3 errores `cvc-*`, cada uno con línea y columna. Ver [Timbrado por XML](/timbrado-por-xml).                                                                                                                       |
| `UNSUPPORTED_CFDI_CONTENT`           | (`POST /cfdi/timbrar-xml`) El documento ya trae `Sello`, trae `cfdi:Addenda`, o su complemento no es Pagos 2.0.                                                                                                                                                                                                   |

### 401 / 403 — Autenticación y permisos

| Código                    | Status | Causa                                          | Solución                                                                                                          |
| ------------------------- | ------ | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `AUTHENTICATION_REQUIRED` | 401    | La request no incluyó API key.                 | Envía el header `x-api: {tu_api_key}`.                                                                            |
| `INVALID_API_KEY`         | 401    | API key inválida, revocada o de otro ambiente. | Verifica la key y que apunte al ambiente correcto (las keys de sandbox no funcionan en producción, ni viceversa). |
| `ACCOUNT_SUSPENDED`       | 403    | Tu cuenta está suspendida.                     | Contacta a soporte.                                                                                               |
| `ACCESS_DENIED`           | 403    | Denegación genérica de la capa de seguridad.   | No lo produce la pertenencia de un recurso: ver la nota de abajo.                                                 |

<Note>
  **Un recurso de otra cuenta responde `404`, no `403`.** Pedir un CFDI o una empresa que
  existe pero es de alguien más devuelve exactamente la misma respuesta que pedir un `id` que
  nunca se emitió: `404 CFDI_NOT_FOUND` o `404 EMPRESA_NOT_FOUND`, mismo mensaje. Así la API no
  confirma la existencia de nada que no va a entregar. El único `403` que puedes recibir en una
  ruta autenticada es `ACCOUNT_SUSPENDED`.
</Note>

### 402 — Límite del plan

| Código                 | Causa                                                                                                                                                                                         | Solución                                 |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `TRIAL_LIMIT_EXCEEDED` | Alcanzaste el límite de timbres de tu periodo de prueba. Aplica igual al timbrado de facturas y al de complementos de pago (`POST /cfdi/complemento-pago`): un REP también consume un timbre. | Contacta a soporte para activar tu plan. |

### 404 — No encontrado

| Código                         | Causa                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `CFDI_NOT_FOUND`               | No existe un CFDI con ese identificador en tu cuenta. Cubre los tres casos con la misma respuesta: el `id` no existe, el folio fiscal no existe, o el CFDI existe pero es de otra cuenta. En `POST /cfdi/cancelar` con `motivo=01`, también que el `uuid_sustitucion` no resuelva a ningún CFDI tuyo (ver [Cancelación de CFDI](/cancelacion#qué-se-valida-y-qué-no)).                                                                                                                                                                                                                                                                           |
| `EMPRESA_NOT_FOUND`            | No existe esa empresa en tu cuenta — o existe y es de otra. `GET /empresas/{id}` y `PATCH /empresas/{id}` responden lo mismo en ambos casos.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `RFC_NOT_FOUND`                | El `rfc_emisor` no está registrado como empresa de tu cuenta. Lo devuelven las cuatro rutas de timbrado y `POST /cfdi/complemento-pago`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `CERTIFICATE_NUMERO_NOT_FOUND` | La empresa no tiene un certificado con ese `numero_certificado`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `ACUSE_NOT_AVAILABLE`          | `GET /cfdi/{id}/acuse` — el CFDI no tiene (todavía, o nunca tuvo) un acuse de cancelación del SAT.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `DOCUMENT_NOT_AVAILABLE`       | `GET /cfdi/{id}/xml` — el CFDI existe, pero no tenemos su XML. Tres casos: el documento no está en el almacenamiento (en producción no se resuelve reintentando; contacta a soporte con el `id`), en **sandbox** pasaron más de 15 minutos desde el timbrado y el documento se descartó (esperado; ver [Sandbox](/sandbox#diferencias-con-producción)), o el CFDI es `origen: externo` —lo timbró otro proveedor y sólo existe aquí para sostener su cancelación—, en cuyo caso nunca habrá XML. Ver [Datos y retención](/datos-y-retencion#urls-de-descarga-ttl) y [Cancelación de CFDI](/cancelacion#qué-queda-registrado-de-un-cfdi-externo). |

### 409 — Conflicto (duplicados)

| Código                   | Causa                                                                                                                                                                                                         | Solución                                                                                                                      |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `DUPLICATE_INVOICE`      | Ya existe un CFDI timbrado con la misma `serie` + `folio` + RFC emisor + tipo. Si el CFDI en conflicto es de tu propia cuenta, el error trae un campo `id` con su identificador (nunca si es de otra cuenta). | No vuelvas a timbrar: consulta el CFDI existente con el `id` del error, o con `GET /cfdi`. Ver [Idempotencia](#idempotencia). |
| `DUPLICATE_IN_PROGRESS`  | Hay un timbrado en curso para los mismos datos, o (si usas `idempotency_key`) una request en vuelo con esa misma llave. Trae el header `Retry-After`.                                                         | Espera los segundos indicados y reintenta con la misma `idempotency_key`.                                                     |
| `DUPLICATE_CERTIFICATE`  | Ese `numero_certificado` ya está registrado.                                                                                                                                                                  | En producción el número de certificado es único; verifica que no lo hayas subido ya.                                          |
| `EMPRESA_ALREADY_EXISTS` | Ya existe una empresa con ese RFC en tu cuenta.                                                                                                                                                               | Usa la empresa existente (`GET /empresas`).                                                                                   |

### 413 — Payload demasiado grande

| Código              | Causa                                                                 |
| ------------------- | --------------------------------------------------------------------- |
| `REQUEST_TOO_LARGE` | (`POST /cfdi/timbrar-xml`) El XML, una vez decodificado, excede 2 MB. |

### 422 — Request no procesable

| Código                   | Causa                                                                                                                                                                                                                       | Solución                                                                                                                                |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `IDEMPOTENCY_KEY_REUSED` | Reutilizaste un `idempotency_key` con un payload **distinto** del que usó esa llave la primera vez.                                                                                                                         | Usa una llave nueva para una operación nueva, o reenvía el payload original si quieres su respuesta. Ver [Idempotencia](#idempotencia). |
| `EMPRESA_RFC_IMMUTABLE`  | Enviaste `rfc` en el cuerpo de `PATCH /empresas/{id}`. El RFC identifica al emisor ante el SAT y ya está timbrado en todos los CFDI que emitió, así que no es un atributo editable — no se aplicó ningún cambio del cuerpo. | Para cambiar de RFC, desactiva la empresa y crea una nueva con `POST /empresas`.                                                        |

### 429 — Rate limit

| Código                | Causa                                       | Solución                                                                                    |
| --------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `RATE_LIMIT_EXCEEDED` | Excediste el límite de requests por minuto. | Espera lo que indique el header `Retry-After` y reintenta. Ver [Rate limits](#rate-limits). |

### 5xx — Errores del servidor

| Código                     | Status | Causa                                                                                                                                                                            | Solución                                                                                                                                                                                                                                |
| -------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `STAMPING_OUTCOME_UNKNOWN` | 503    | El PAC no confirmó a tiempo si el CFDI se timbró. El error trae `retry_after` (segundos hasta la próxima re-pregunta automática) y, si la enviaste, tu propia `idempotency_key`. | **No reintentes con otra llave ni otro folio.** Espera el webhook `cfdi.timbrado` tardío, consulta `GET /cfdi` / `GET /cfdi/{id}`, o reintenta con la misma `idempotency_key`. Ver [Recuperación tras timeout](/recuperacion-timeouts). |
| `SERVICE_UNAVAILABLE`      | 503    | El servicio no está disponible: al timbrar, todos los PACs fallaron por indisponibilidad; en `GET /cfdi/{id}/xml` y `/pdf`, el almacenamiento de documentos no respondió.        | Reintenta con backoff exponencial.                                                                                                                                                                                                      |
| `INTERNAL_ERROR`           | 500    | Error inesperado del servidor.                                                                                                                                                   | Reintenta; si persiste, contacta a soporte con el timestamp de la request.                                                                                                                                                              |

***

## Rate limits

Los límites se aplican **por cuenta**, en ventanas fijas de un minuto:

| Ruta                                                                                                                     | Límite                |
| ------------------------------------------------------------------------------------------------------------------------ | --------------------- |
| Timbrado (`/cfdi/timbrar`, `/cfdi/timbrar-xml`, `/cfdi/timbrar-sellado`, `/cfdi/complemento-pago`) y descarga de XML/PDF | 300 req/min           |
| `POST /cfdi/cancelar`                                                                                                    | 120 req/min           |
| Consultas de CFDI (`GET /cfdi`, `/cfdi/{id}`, `/estatus`, `/acuse`)                                                      | 60 req/min            |
| `/empresas` y `/empresas/{id}`                                                                                           | 60 req/min            |
| `/empresas/{empresa_id}/certificados/**`                                                                                 | 30 req/min            |
| Cualquier otra ruta autenticada (hoy `/webhooks*`)                                                                       | 60 req/min            |
| Requests sin credencial válida                                                                                           | 60 req/min **por IP** |

Toda respuesta de una ruta con límite incluye los headers `X-RateLimit-Limit`,
`X-RateLimit-Remaining` y `X-RateLimit-Reset` (epoch del inicio de la siguiente ventana).
Al exceder el límite recibes `429` con `code: RATE_LIMIT_EXCEEDED` y el header
`Retry-After` en segundos. La tabla completa y el manejo recomendado están en
[Rate limits](/limites).

***

## Reintentos

### ¿Cuándo reintentar?

| Status        | ¿Reintentar? | Notas                                                                                                                                           |
| ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`         | No           | Error en tu request. Corrígelo antes de reintentar.                                                                                             |
| `401` / `403` | No           | Problema de credenciales o permisos.                                                                                                            |
| `402`         | No           | Límite del plan; contacta a soporte.                                                                                                            |
| `404`         | No           | El recurso no existe (o no es de tu cuenta).                                                                                                    |
| `409`         | Depende      | `DUPLICATE_INVOICE`: no, consúltalo en lugar de re-crearlo. `DUPLICATE_IN_PROGRESS`: sí, tras el `Retry-After`, con la misma `idempotency_key`. |
| `413`         | No           | Reduce el payload (para `/cfdi/timbrar-xml`, el XML decodificado no puede exceder 2 MB).                                                        |
| `422`         | No           | `IDEMPOTENCY_KEY_REUSED`: usa una llave nueva, o reenvía el payload original. `EMPRESA_RFC_IMMUTABLE`: quita `rfc` del cuerpo.                  |
| `429`         | Sí           | Espera los segundos del header `Retry-After`.                                                                                                   |
| `500`         | Sí           | Reintenta máximo 3 veces.                                                                                                                       |
| `503`         | Depende      | `STAMPING_OUTCOME_UNKNOWN`: no reintentes con otra llave/folio — espera `retry_after` o el webhook. Cualquier otro 503: backoff exponencial.    |

### Backoff exponencial recomendado

```python Python theme={null}
import time
import random
import requests

def timbrar_con_reintento(payload, api_key, max_intentos=3):
    for intento in range(max_intentos):
        response = requests.post(
            "https://api.ipsofactura.com/cfdi/timbrar",
            headers={"x-api": api_key, "Content-Type": "application/json"},
            json=payload,
        )
        if response.status_code not in (500, 503):
            return response
        if intento == max_intentos - 1:
            return response
        espera = (2 ** intento) + random.uniform(0, 1)
        time.sleep(espera)
```

***

## Problemas frecuentes

### "Mi CFDI fue timbrado pero no recibí la respuesta"

Puede pasar si la conexión se interrumpió después de que el PAC timbró pero antes de que
la respuesta llegara a tu sistema.

* **Si enviaste `idempotency_key`**: reenvía la misma request con la misma llave. Recibes
  la respuesta original tal cual (`Idempotency-Replayed: true`) sin timbrar ni cobrar dos
  veces. Ver [Recuperación tras timeout](/recuperacion-timeouts).
* **Si asignas `serie` y `folio` propios pero no `idempotency_key`**: reenvía la misma
  request. Si el CFDI ya se timbró recibirás `409 DUPLICATE_INVOICE` (con el `id` del CFDI
  existente) o `DUPLICATE_IN_PROGRESS` si sigue en curso.
* **Si dejas que Ipsofactura asigne el folio y no enviaste `idempotency_key`**: **no
  reenvíes a ciegas** — cada envío se consideraría un CFDI nuevo. Consulta primero
  `GET /cfdi` y verifica si el comprobante ya aparece, o usa `GET /cfdi?serie=&folio=&rfc_emisor=`
  si conoces esos datos.
* **Si recibiste `503 STAMPING_OUTCOME_UNKNOWN`**: no es un timeout de red común. Ver la
  sección dedicada en [Recuperación tras timeout](/recuperacion-timeouts).

### "Mi CSD vence pronto, ¿qué pasa si no lo renuevo?"

Los CFDIs timbrados con el CSD anterior siguen siendo válidos — el SAT no los invalida.
Pero a partir del vencimiento recibirás `CERTIFICATE_EXPIRED` al timbrar, hasta subir el
CSD renovado.

Monitorea el campo `fecha_fin` de tus certificados en
`GET /empresas?include_certificados=true`, o el de una sola empresa con
`GET /empresas/{id}?include_certificados=true`. También puedes suscribirte al webhook
[`csd.por_vencer`](/webhooks#aviso-de-vencimiento-del-csd), que avisa a los 30, 15 y 7 días.

### "El SAT rechazó mi cancelación"

La cancelación puede requerir aceptación del receptor. Consulta el resultado con
`GET /cfdi/{id}/estatus`: cuando hay actividad de cancelación, el campo
`estatus_cancelacion` trae el detalle del SAT (`"En proceso"`, `"Solicitud rechazada"`,
`"Plazo vencido"`, `"Cancelado con aceptacion"`, …).

Si el receptor rechazó la cancelación:

* Negocia con el receptor para que la acepte desde su portal del SAT.
* Si el CFDI tiene un error de importe, emite una nota de crédito (egreso) por la diferencia.

***

## Errores en el sandbox

El [sandbox](/sandbox) valida el XSD, las reglas propias de Ipsofactura y un subconjunto
curado de las reglas CFDI 4.0 del SAT, y devuelve los errores en el mismo formato que
producción, incluido el `sat_code` con el código tipo `CFDIxxxxx`. Úsalo para probar el manejo
de errores de tu integración: el contrato que verifiques aquí es el mismo que recibirás en
producción. Lo que **no** valida —y producción sí rechaza— está en
[Qué valida el sandbox y qué no](/sandbox#qué-valida-el-sandbox-y-qué-no).

Para forzar escenarios de error de forma determinista, envía uno de los **RFC de receptor
mágicos**:

| RFC receptor   | Resultado forzado                                                                                                            |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `SBX010101ERR` | `400 VALIDATION_ERROR` — error de validación simulado (`CFDI40197`).                                                         |
| `SBX010101TMO` | `503 SERVICE_UNAVAILABLE` — servicio de timbrado no disponible (para probar reintentos). Termina en **letra O**, no en cero. |
| `SBX010101LIM` | `402 TRIAL_LIMIT_EXCEEDED` — límite de timbrado agotado.                                                                     |
| `SBX010101CSD` | `400 CERTIFICATE_EXPIRED` — CSD del emisor vencido.                                                                          |
| `SBX010101NOV` | `400 CERTIFICATE_NOT_YET_VALID` — CSD del emisor que aún no es vigente.                                                      |
| `SBX010101REV` | `400 CERTIFICATE_REVOKED` — CSD del emisor revocado por el SAT; la empresa queda sin certificado activo.                     |
| `SBX010101SUS` | `403 ACCOUNT_SUSPENDED` — cuenta suspendida.                                                                                 |

La tabla completa —con los escenarios de cancelación (`SBX010101PRO`, `SBX010101RCH`) y de
timbrado tardío (`SBX010101LAT`)— vive en **[RFCs mágicos](/sandbox)**.

<Note>
  Las validaciones del sandbox son representativas, no exhaustivas: un CFDI aceptado en sandbox
  puede ser rechazado en producción por una regla del SAT que el sandbox no aplica.
</Note>

***

## Idempotencia

`POST /cfdi/timbrar`, `/cfdi/complemento-pago` y `/cfdi/cancelar` aceptan un
`idempotency_key` en el body. Un reintento con la misma llave devuelve la respuesta del
primer intento tal cual, byte a byte, en lugar de volver a ejecutarse — la forma
recomendada de manejar timeouts. Ver la guía completa en
[Recuperación tras timeout](/recuperacion-timeouts).

Independientemente de `idempotency_key`, el timbrado también deduplica por `serie` +
`folio` + RFC emisor + tipo: dos CFDI con la misma combinación no pueden coexistir, y el
segundo intento regresa `409 DUPLICATE_INVOICE` en lugar de timbrar y cobrar de nuevo.

<Note>
  Sin `idempotency_key` ni `serie`/`folio` propios, Ipsofactura asigna el consecutivo
  siguiente en cada request y dos envíos producen dos CFDIs distintos. Para reintentos
  seguros, asigna tu propia `idempotency_key` (recomendado) o al menos `serie` y `folio`
  propios.
</Note>
