# Errores

> Cada error dice qué pasó, qué campo corregir y cómo. Aquí está cada código que puede recibir.

Un error responde con un código HTTP 4xx o 5xx y siempre con el mismo cuerpo:

```json
{
  "error": {
    "type": "invoice_error",
    "code": "invoice_rejected",
    "message": "El SAT no reconoce el nombre del cliente. Escríbalo exactamente como en su constancia de situación fiscal, en mayúsculas y sin «S.A. de C.V.» ni otro régimen de capital.",
    "param": "customer",
    "sat_code": "CFDI40145",
    "invoice": "inv_roVZuc6krJsw08oPyaiBiYX4fWH2pyEN1x",
    "doc_url": "https://fiscalisto.com/docs/api/errores#invoice_rejected",
    "request_id": "req_FfCT3zz9ktKinNLSGMqu"
  }
}
```

| Campo | Qué es |
| --- | --- |
| `type` | La familia del error. Su código decide qué hacer con este. |
| `code` | La razón precisa, estable: no cambia entre versiones. Está en la lista de abajo. |
| `message` | Qué pasó y cómo corregirlo, en español, para mostrarlo a una persona. Puede mejorar con el tiempo: no lo compare en su código. |
| `param` | El campo que hay que corregir, con su ruta en el cuerpo (`items.0.unit_price`, `customer.zip`). |
| `sat_code` | El código del SAT o del PAC cuando ellos lo rechazaron (`CFDI40145`). |
| `doc_url` | Esta página, en el código del error. |
| `request_id` | El id de la solicitud, el mismo del encabezado `Request-Id`. Búsquelo en **Desarrolladores › Solicitudes** o mándelo a soporte. |

Un rechazo del SAT o del PAC trae además el id del comprobante rechazado en `invoice` (o `retention` en una constancia), para consultarlo.

## Qué hacer con cada tipo

| `type` | HTTP | Qué hacer |
| --- | --- | --- |
| `invalid_request_error` | 400 | Corregir la solicitud: `param` y `message` dicen qué. |
| `authentication_error` | 401 | Revisar la llave. |
| `balance_error` | 402 | Comprar timbres y repetir con la misma `Idempotency-Key`. |
| `permission_error` | 403 | Usar una llave que pueda con ese emisor. |
| `not_found_error` | 404 | Revisar el id y el modo de la llave. |
| `idempotency_error` | 409 | `request_in_progress`: esperar y repetir. `idempotency_key_reused`: usar otra llave. |
| `invoice_error` | 422 | Corregir los datos del comprobante: el SAT, el PAC o la revisión de Fiscalisto no lo aceptan. |
| `rate_limit_error` | 429 | Esperar lo que dice `Retry-After` y repetir. |
| `api_error` | 500, 503 | Repetir con la misma `Idempotency-Key`, con espera creciente. |

> **Nunca se cobra un error.** Ningún error usa un timbre: ni las revisiones de Fiscalisto, ni los rechazos del SAT o del PAC. Una factura rechazada queda guardada con `status: "rejected"` y su `error`, y puede consultarla con su id.

## Si la API no contesta

Un `500`, un `503` o un corte de red: repita con la misma `Idempotency-Key` y espera creciente (los [SDKs](https://fiscalisto.com/docs/api/sdk#reintentos-seguros) ya lo hacen). Para saber si es general, vea [fiscalisto.com/estado](https://fiscalisto.com/estado): dice si el timbrado, la API y las listas del SAT funcionan, desde cuándo fallan y cómo funcionaron en los últimos 90 días, revisado cada 15 minutos. Para sus monitores, lo mismo en JSON con `GET https://fiscalisto.com/estado.json`, sin llave:

```json
{
  "status": "operativo",
  "checked_at": "2026-10-24T18:45:00.000Z",
  "page": "https://fiscalisto.com/estado",
  "components": [
    { "id": "pac", "name": "Timbrado y cancelación", "status": "operativo", "since": null },
    { "id": "api", "name": "API", "status": "operativo", "since": null },
    { "id": "sat_lists", "name": "Listas del SAT (69-B y 69)", "status": "operativo", "since": null }
  ]
}
```

`status` es `operativo`, `degradado` o `interrumpido`; `since`, desde cuándo, mientras no sea `operativo`. Con `?history=1` trae también los últimos 90 días (`days`: `day`, `status` y `minutes` con fallas). Se actualiza cada minuto; no lo consulte más seguido. Para enterarse sin consultar: avisos por correo en [Estado del servicio](/estado) o el RSS `https://fiscalisto.com/estado.rss`, con cada incidente al empezar y al resolverse.

## En inglés

Con el encabezado `Accept-Language: en` los errores llegan en inglés (y `Content-Language: en`). Los de validación dicen exactamente lo mismo que en español (`unit_price: must be greater than or equal to 0.`). Los demás dicen en inglés qué significa su `code`, y traen el mensaje original, más preciso, en `message_es`. Los datos no se traducen: los catálogos, nombres y campos del CFDI son los del SAT.

```bash
curl https://api.fiscalisto.com/v1/invoices/inv_nope \
  -H "Authorization: Bearer $FISCALISTO_API_KEY" \
  -H "Accept-Language: en"
```

En los SDKs, la opción `language: "en"` (`language="en"` en Python, `'language' => 'en'` en PHP, `WithLanguage("en")` en Go, `.language("en")` en Java y `Language = "en"` en .NET).

## Errores de validación

Antes de mandar al PAC, Fiscalisto revisa la factura completa con los catálogos y las reglas del SAT (por ejemplo, que el uso del CFDI corresponda al régimen del cliente o que los totales cuadren). Así casi todo error llega como `parameter_invalid` o `invoice_invalid`, con `param`, sin gastar tiempo ni timbre en el PAC. Un campo que no existe nunca se ignora en silencio: responde `parameter_unknown`, porque casi siempre es un nombre mal escrito.


## Solicitud inválida

`type`: `invalid_request_error`.

### parameter_missing

**HTTP 400.** Falta un campo obligatorio. `param` dice cuál.

### parameter_invalid

**HTTP 400.** Un campo tiene un valor que no se acepta (formato, catálogo del SAT, rango). `param` dice cuál y `message` cómo corregirlo.

### parameter_unknown

**HTTP 400.** Mandó un campo o un parámetro de la URL que no existe (casi siempre un nombre mal escrito, como legalName en vez de legal_name, o ?statuss= en vez de ?status=). Nunca se ignora en silencio.

### body_too_large

**HTTP 413.** El cuerpo pasa de 10 MB. Divida el trabajo en varios lotes.

### data_request_completed

**HTTP 400.** El cliente ya mandó sus datos con esa liga: una solicitud completada no se cancela.

### invalid_json

**HTTP 400.** El cuerpo no es un objeto JSON válido.

### unsupported_content_type

**HTTP 400.** Mande el cuerpo con Content-Type: application/json.

### method_not_allowed

**HTTP 405.** La ruta existe pero no con ese método (por ejemplo, PUT: la API usa POST para actualizar).

### browser_not_supported

**HTTP 400.** La API se llama desde su servidor, nunca desde el navegador: la llave secreta quedaría expuesta.

### livemode_mismatch

**HTTP 400.** El objeto es del otro modo: una llave fk_test_ solo ve lo de pruebas y una fk_live_ lo de producción.

### issuer_required

**HTTP 400.** La cuenta tiene varios emisores y la solicitud no dice cuál: mande `issuer` (iss_…).

### issuer_exists

**HTTP 400.** Ese RFC ya es un emisor de la cuenta en este modo.

### issuer_archived

**HTTP 400.** El emisor está archivado: ya no timbra. Sus comprobantes se pueden consultar y cancelar.

### certificate_invalid

**HTTP 400.** El CSD no sirve: no es un certificado de sello (es una e.firma), está vencido, el .key no corresponde o la contraseña no abre la llave.

### certificate_required

**HTTP 400.** Suba primero el CSD del emisor; después firme el contrato con su e.firma.

### efirma_invalid

**HTTP 400.** La e.firma no sirve: no es una e.firma (es un CSD), es de otro RFC, está vencida o la contraseña no abre la llave.

### efirma_required

**HTTP 400.** Para pedirle al SAT las facturas de un emisor en producción, guarde antes su e.firma: `POST /v1/issuers/{id}/efirma` con keep_for_downloads: true. También si la guardada venció.

### test_mode_only

**HTTP 400.** Esto solo existe en pruebas (como reiniciar los datos de prueba): use su llave fk_test_.

### sat_request_rejected

**HTTP 400.** El SAT rechazó la solicitud de descarga. El mensaje trae su motivo, por ejemplo que ya se pidió el mismo periodo con los mismos filtros demasiadas veces (el SAT pone un límite de por vida).

### ticket_invalid

**HTTP 400.** El ticket no se pudo registrar: el folio trae caracteres no válidos, la fecha de venta está en el futuro o es de hace más de un año, el producto ya no está en el catálogo… El mensaje dice cuál.

### ticket_folio_taken

**HTTP 400.** Ese folio ya está registrado para el emisor con otro total o con otra propina (409). Con el mismo total se contesta el ticket que ya existe.

### ticket_not_pending

**HTTP 400.** El ticket ya se facturó o entró en una factura global: no se puede cancelar.

### recurring_invalid

**HTTP 400.** La factura recurrente no se pudo guardar: el día, la hora, la zona o la factura no son válidos. El mensaje dice cuál.

### recurring_limit

**HTTP 400.** El emisor ya tiene el máximo de facturas recurrentes activas (25 en pruebas, 5,000 en producción). Pause o borre las que ya no use.

### recurring_paused

**HTTP 400.** La factura recurrente está en pausa: reanúdela con active: true para timbrarla.

### recurring_not_due

**HTTP 400.** Aún no toca timbrar la factura recurrente (409): su next_invoice_on es después de hoy. Para una factura antes de tiempo, use `POST /v1/invoices`.

### invoice_not_markable

**HTTP 400.** Solo una factura de ingreso PUE vigente se marca pagada o por cobrar; una PPD se paga con su complemento de pago (type P).

### bank_transaction_invalid

**HTTP 400.** El depósito no se puede conciliar, clasificar o reabrir así (ya está conciliado, las facturas son de otro cliente o una está cancelada…). El mensaje dice por qué.

### diot_empty

**HTTP 400.** El mes no tiene pagos a proveedores: esa DIOT se presenta sin operaciones desde el portal del SAT.

### store_connection_failed

**HTTP 400.** La tienda no aceptó la conexión o no respondió como se esperaba (llaves equivocadas, permisos read_orders/write_orders que faltan, una tienda que no vende en pesos). El mensaje dice qué contestó.

### store_platform_unavailable

**HTTP 400.** Esa plataforma aún no está disponible en Fiscalisto.

### sandbox_invalid

**HTTP 400.** No se pudo crear el entorno de prueba: falta su nombre o la cuenta ya tiene 25 (borre uno que ya no use).

### api_version_invalid

**HTTP 400.** No existe esa versión de la API (en Fiscalisto-Version o api_version). El mensaje dice cuáles hay; vea Versiones.

### expand_invalid

**HTTP 400.** Un campo de expand[] no existe en la respuesta, no es el id de otro objeto, o se pidieron demasiados (hasta 10 campos, 4 niveles). Vea Expandir objetos.

### test_clock_unsupported

**HTTP 400.** Los relojes de prueba son de emisores de prueba: use su llave fk_test_ y un emisor de pruebas.

### test_clock_exists

**HTTP 409.** El emisor ya tiene un reloj de prueba: adelántelo, o bórrelo para volver a la hora real.

### test_clock_invalid

**HTTP 400.** frozen_time no sirve: no es una fecha y hora ISO 8601, es anterior a la hora del reloj (solo avanza) o va más de dos años adelante de la hora real.

### test_clock_advancing

**HTTP 409.** El reloj se está adelantando en otra solicitud (409, con Retry-After). Espere a test_clock.ready y repita.

### store_already_connected

**HTTP 400.** Esa tienda ya está conectada a este emisor.

### store_not_authorized

**HTTP 400.** El vendedor aún no autoriza la conexión en authorize_url.

### store_disconnected

**HTTP 400.** La tienda se desconectó: conéctela otra vez con `POST /v1/store_connections`.

### quote_invalid

**HTTP 400.** La cotización no se pudo guardar o ya no cambia (aceptada, rechazada o facturada), o no se puede mandar así. El mensaje dice por qué.

### quote_not_draft

**HTTP 400.** Solo se borra una cotización que nunca se envió.

### quote_not_invoiceable

**HTTP 400.** La cotización se rechazó o venció: no se factura. Si venció, cámbiele valid_until (es una versión nueva).

### quote_customer_missing

**HTTP 400.** La cotización no tiene los datos fiscales del cliente: agrégueselos con `POST /v1/quotes/{id}` (customer) o mándela con auto_invoice para que los dé al aceptarla.

### idempotency_key_missing

**HTTP 400.** Lo que timbra exige el encabezado Idempotency-Key (8 a 255 caracteres, uno nuevo por comprobante). Así un reintento nunca timbra dos veces.

### idempotency_key_invalid

**HTTP 400.** Idempotency-Key es de 1 a 255 caracteres ASCII, sin acentos ni ñ: el id de su pedido o un UUID.

### invoice_not_stamped

**HTTP 400.** La acción es para facturas timbradas (enviar, cancelar, sustituir).

### substitution_unsupported

**HTTP 400.** Los complementos de pago y las notas de crédito no se sustituyen: se cancelan y se emite otro.

### retention_not_stamped

**HTTP 400.** La acción es para constancias timbradas.

### customer_exists

**HTTP 400.** Ya hay un cliente con ese RFC en el catálogo del emisor (o un producto con ese SKU).

### branch_exists

**HTTP 400.** El emisor ya tiene una sucursal con ese código postal (cada una lleva el suyo); el mensaje dice cuál. Si está archivada, restáurela con archived: false.

### branch_has_invoices

**HTTP 400.** La sucursal ya facturó, así que no se borra: archívela con archived: true y sus facturas la siguen nombrando.

### inventory_refused

**HTTP 400.** Inventario no lo permite: el mensaje dice por qué (un lote que no es de ese producto, otro ajuste del mismo almacén en curso).

### email_failed

**HTTP 400.** No se pudo mandar el correo; el comprobante sí está timbrado.

### endpoint_exists

**HTTP 400.** Ya hay un webhook con esa dirección en este modo; el mensaje dice cuál. Cámbielo en vez de crear otro.

### endpoint_limit

**HTTP 400.** La cuenta llegó al máximo de webhooks en este modo.

## Autenticación

`type`: `authentication_error`.

### missing_api_key

**HTTP 401.** Falta el encabezado Authorization: Bearer fk_test_… o fk_live_….

### invalid_api_key

**HTTP 401.** La llave no existe, se revocó o expiró. Cree otra en Desarrolladores › Llaves.

## Permisos

`type`: `permission_error`.

### key_restricted

**HTTP 403.** La llave está limitada a algunos emisores y la solicitud toca otro (o crea uno nuevo, o usa algo de toda la cuenta como webhooks).

### key_in_sandbox

**HTTP 403.** La llave es de un entorno de prueba y la solicitud es de toda la cuenta (como crear o borrar entornos): use una llave de pruebas de la cuenta.

### issuer_not_allowed

**HTTP 403.** La llave no puede usar ese emisor.

### key_read_only

**HTTP 403.** La llave es de solo lectura: puede hacer GET, validar RFC, revisar listas del SAT y calcular recibos de nómina, pero nada que timbre, cancele, envíe o cambie datos.

## No encontrado

`type`: `not_found_error`.

### route_not_found

**HTTP 404.** No existe esa ruta. Revise la dirección y la versión (/v1).

### issuer_missing

**HTTP 404.** No existe ese emisor en la cuenta (o es del otro modo).

### test_clock_missing

**HTTP 404.** El emisor no tiene reloj de prueba: créelo con `POST /v1/issuers/{id}/test_clock`.

### resource_missing

**HTTP 404.** No existe un objeto con ese id (o es de otro emisor o del otro modo).

## Idempotencia

`type`: `idempotency_error`.

### idempotency_key_reused

**HTTP 409.** Esa Idempotency-Key ya se usó en las últimas 24 horas con otra solicitud (otra ruta u otro cuerpo). Use una nueva para cada operación distinta.

### request_in_progress

**HTTP 409.** Otra solicitud con la misma Idempotency-Key sigue en proceso, o otra solicitud está cambiando el mismo objeto (dos actualizaciones del mismo cliente a la vez esperan su turno hasta 15 segundos) (409, con Retry-After). Repítala en un momento con la misma llave: contestará lo mismo.

## Comprobantes

`type`: `invoice_error`.

### invoice_invalid

**HTTP 422.** La factura no cumple una regla del SAT que Fiscalisto revisa antes de mandarla al PAC (uso contra régimen, totales, complementos…). No se usó ningún timbre.

### invoice_rejected

**HTTP 422.** El PAC o el SAT no la aceptaron. `sat_code` trae su código (CFDI40xxx) y `message` qué corregir. La factura queda como rejected; no se usó ningún timbre.

### cancellation_failed

**HTTP 422.** El SAT no aceptó la solicitud de cancelación; `message` dice por qué.

### payroll_invalid

**HTTP 422.** El recibo de nómina no se puede calcular o timbrar con esos datos (periodo, días, registro patronal del emisor…).

### retention_invalid

**HTTP 422.** La constancia de retenciones no cumple una regla del SAT. No se usó ningún timbre.

### retention_rejected

**HTTP 422.** El PAC o el SAT no aceptaron la constancia; `sat_code` trae su código.

### batch_invalid

**HTTP 422.** Una factura del lote no pasa la revisión; `param` dice cuál (invoices.3.items.0.unit_price). Ninguna del lote se timbró.

## Timbres

`type`: `balance_error`.

### insufficient_balance

**HTTP 402.** La cuenta no tiene timbres de producción: para timbrar, revisar RFC en las listas, consultar constancias o pedir descargas al SAT. Compre un paquete en Desarrolladores › Timbres. En pruebas nunca pasa. Para enterarse antes, escuche balance.low o active la recarga automática.

## Límites

`type`: `rate_limit_error`.

### issuer_creation_limit

**HTTP 429.** Se crearon 500 emisores en 24 horas. Escríbanos si necesita más.

### sat_refresh_too_soon

**HTTP 429.** Las declaraciones de ese emisor se consultaron en el SAT hace menos de 10 minutos (cada consulta entra al SAT con su e.firma). Retry-After dice cuántos segundos faltan; lo que ya trajo está en `GET /v1/sat_declarations`.

### stamp_rate_limit

**HTTP 429.** Demasiados timbres seguidos del mismo emisor. Espere lo que dice Retry-After; para muchas facturas use `POST /v1/invoice_batches`.

### daily_limit

**HTTP 429.** Llegó al tope del día de algo que no se paga con timbres: comprobantes en pruebas (1,000), RFC revisados en las listas del SAT o constancias consultadas. Retry-After dice cuántos segundos faltan para la medianoche de la Ciudad de México.

### rate_limit

**HTTP 429.** Demasiadas solicitudes con la llave. Espere lo que dice Retry-After y reintente.

## Errores de Fiscalisto

`type`: `api_error`.

### sat_unavailable

**HTTP 502.** El SAT no contestó o no dejó entrar con la e.firma del emisor. El mensaje trae lo que dijo; no se usó ningún timbre. Reintente en 10 minutos.

### catalog_unavailable

**HTTP 503.** El catálogo del SAT no respondió (503). Reintente en unos segundos.

### internal_error

**HTTP 500.** Algo falló de nuestro lado. Repita la solicitud con la misma Idempotency-Key: nunca se timbra dos veces. Si sigue, escríbanos con el request_id.
