# Errores

Cada código, su causa, su solución y si se reintenta.

Los errores de la API llegan con una sola forma, salvo las dos excepciones de abajo. Lee `code` para decidir, muestra `message` y guarda `request_id`.

## Forma del error

```json
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": { "fields": { "amount": "Monto decimal inválido" } },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`details.fields` trae los errores por campo. `retryable` dice si repetir la misma petición más tarde puede servir, y es el dato de cada respuesta el que manda. Las tablas de abajo dan el valor habitual de cada código: un rechazo ya registrado siempre llega con `false`, se repite tal cual con esa clave y necesita una nueva. Ver [Qué hacer con la clave tras un rechazo](/docs/idempotencia/#qué-hacer-con-la-clave-tras-un-rechazo). Tu código debe tolerar códigos nuevos.

### Excepciones

| Respuesta | Forma | Qué hacer |
|---|---|---|
| `503` de [`GET /v1/health`](/docs/operaciones/consultar-salud/) | `{"status": "down", "api_version": "v1"}`, sin `error` | Decide por `status`. Es el único error de la API sin esta forma |
| `404` del simulador público, cuando un escenario no aplica a la operación | `application/problem+json` | Sólo ocurre ahí. Ver [Ambientes](/docs/ambientes/#qué-no-simula-el-simulador-público) |

Un error fuera de la lista de arriba también puede ser de tu proxy o de tu red, no de FITAL: si no trae `error.code`, trátalo como un timeout.

## Códigos

### `idempotency_key_invalid`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 400 | `false` | No |

**Causa.** La clave no mide entre 8 y 200 caracteres, o viene duplicada.

**Solución.** Envía una sola cabecera con una clave de largo válido.

### `idempotency_key_required`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 400 | `false` | No |

**Causa.** Falta `Idempotency-Key` en una operación que la exige.

**Solución.** Envía una clave derivada de tu identificador interno.

### `invalid_request`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 400 | `false` | No |

**Causa.** Algún campo no cumple el esquema, el cuerpo no es JSON válido o supera el tamaño máximo. El detalle por campo llega en `details.fields`.

**Solución.** Corrige los campos indicados antes de repetir. Un cuerpo demasiado grande llega con HTTP 413 y este mismo código: ver [Límites](/docs/limites/#cuerpo).

### `invalid_api_key`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 401 | `false` | No |

**Causa.** Llave mal formada, vencida o revocada, o empresa suspendida.

**Solución.** Revisa la llave en tu gestor de secretos o crea otra en el panel.

### `invalid_signature`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 401 | `false` | No, hasta corregir la firma |

**Causa.** Falta la firma de la petición, es inválida o tiene más de 5 minutos.

**Solución.** Revisa la cadena firmada en [Autenticación](/docs/autenticacion/#firma-de-petición) y el reloj de tu servidor.

### `wrong_environment`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 401 | `false` | No |

**Causa.** Llave de otro ambiente: `sk_test_` en producción, o `sk_live_` en un simulador.

**Solución.** Usa la base y la llave del mismo ambiente. Ver [Ambientes](/docs/ambientes/).

### `account_not_active`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No |

**Causa.** La cuenta no está activa. No dice si un pago previo ocurrió.

**Solución.** Consulta el `pay_` o escribe a soporte.

### `account_restricted`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No |

**Causa.** Tu cuenta tiene una restricción.

**Solución.** Contacta a tu ejecutivo FITAL. No reintentes en bucle.

### `forbidden_scope`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No |

**Causa.** La llave no tiene el scope de esta operación.

**Solución.** Crea en el panel una llave con el scope necesario.

### `ip_not_allowed`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No, hasta registrar la IP |

**Causa.** La IP de origen no está en la lista de esa llave.

**Solución.** Regístrala en el panel. Ver [IP permitidas](/docs/seguridad/ip-permitidas/).

### `kyb_required`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No |

**Causa.** La empresa no tiene KYB aprobado o aún no está inscrita.

**Solución.** Completa el KYB en [empresa.fital.xyz](https://empresa.fital.xyz). Si ya está aprobado, pide la inscripción a FITAL.

### `live_not_enabled`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No, hasta la inscripción |

**Causa.** Tu empresa aún no está habilitada para producción: tiene KYB aprobado pero falta la inscripción de FITAL. Lo responde el panel al crear una llave `sk_live_fital_…`; la cuenta de prueba sigue disponible.

**Solución.** Completa la inscripción con FITAL. Ver [Ambientes](/docs/ambientes/).

### `not_enabled`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No |

**Causa.** Función sin activar para tu empresa, como PIX sin activación vigente o un método de cobro.

**Solución.** Consulta con tu ejecutivo FITAL.

### `rail_not_enabled`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 403 | `false` | No |

**Causa.** El riel de pago no está habilitado para esta cuenta. En la cuenta de prueba, cualquier riel que no sea SPEI.

**Solución.** Pide a FITAL habilitarlo o usa otro riel. En la cuenta de prueba usa `mxn_spei`.

### `not_found`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 404 | `false` | No |

**Causa.** El recurso no existe o es de otra empresa. La respuesta es la misma en ambos casos.

**Solución.** Revisa el identificador y el ambiente.

### `beneficiary_ref_conflict`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | No |

**Causa.** El identificador del alta choca con un beneficiario que ya existe.

**Solución.** Consulta [`GET /v1/beneficiaries`](/docs/operaciones/listar-beneficiarios/) antes de repetir.

### `beneficiary_state_conflict`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | No |

**Causa.** El estado actual del beneficiario no permite esta operación.

**Solución.** Consulta su estado con [`GET /v1/beneficiaries/{id}`](/docs/operaciones/consultar-beneficiario/).

### `collection_not_ready`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | No |

**Causa.** El cobro aún no está listo para esta operación.

**Solución.** Consulta el cobro con [`GET /v1/collections/{id}`](/docs/operaciones/consultar-cobro/).

### `idempotency_in_progress`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `true` | Sí, misma clave |

**Causa.** Una petición anterior con esa clave sigue en curso.

**Solución.** Repite con la misma clave tras `Retry-After`.

### `not_cancellable`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | No |

**Causa.** El pago ya no se puede cancelar. Es la respuesta normal cuando ya avanzó.

**Solución.** Sigue el pago con [`GET /v1/payments/{id}`](/docs/operaciones/consultar-pago/).

### `payment_in_flight`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `true` | Sí, misma clave, tras resolverse |

**Causa.** Otra salida de dinero de tu empresa sigue en curso.

**Solución.** Repite con la misma clave cuando se resuelva.

### `quote_changed`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | Tras recotizar, con clave nueva |

**Causa.** El débito superaría `max_source_amount` o lo cotizado. No se creó ningún pago. También aplica en la cuenta de prueba.

**Solución.** Pide otra cotización y usa una clave nueva.

### `quote_expired`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | Tras recotizar, con clave nueva |

**Causa.** La cotización `quote_id` venció antes de crear el pago.

**Solución.** Pide otra cotización y usa una clave nueva.

### `receipt_not_available`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | Cuando el movimiento sea terminal |

**Causa.** El movimiento aún no llega a un estado terminal, o no tiene recibo.

**Solución.** Consulta el movimiento y pide el recibo cuando sea terminal.

### `receiving_account_in_progress`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `true` | Sí, con espera |

**Causa.** Otra alta de esa moneda sigue en curso.

**Solución.** Espera unos segundos y reintenta con la misma clave.

### `sandbox_invalid_transition`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 409 | `false` | No |

**Causa.** Sólo cuenta de prueba: `advance` pidió un cambio que un pago real no puede hacer, como llevar a `returned` un pago que no está `completed`.

**Solución.** Consulta el estado del pago y pide una transición válida. Ver [Avanzar un pago](/docs/sandbox/cuenta-de-prueba/#avanzar-un-pago).

### `amount_below_minimum`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | Con otro monto, con clave nueva |

**Causa.** El monto está por debajo del mínimo aplicable.

**Solución.** Aumenta el monto y usa una clave nueva.

### `amount_out_of_range`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | Con otro monto |

**Causa.** El monto está fuera del rango del método.

**Solución.** Consulta `min_amount` y `max_amount` en [`GET /v1/collection-methods`](/docs/operaciones/listar-metodos-de-cobro/).

### `beneficiary_not_approved`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `true` | Tras aprobar en el panel, misma clave |

**Causa.** Beneficiario propio sin aprobar. Uno ajeno o inexistente da `not_found`.

**Solución.** Apruébalo en el panel y repite con la misma clave.

### `collection_rejected`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** El cobro fue rechazado.

**Solución.** Revisa los datos y conserva el `request_id`.

### `currency_not_supported`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** La moneda no aplica a este beneficiario o riel.

**Solución.** Revisa la moneda de la operación.

### `deposit_address_rejected`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** La dirección de depósito fue rechazada.

**Solución.** Revisa `chain` y `currency`.

### `idempotency_key_reused`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** La misma clave se usó con otro cuerpo, también si pasaron más de 7 días. Es un error de programación.

**Solución.** Corrige tu integración: una clave identifica una sola operación.

### `in_review`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `true` | Según `retryable`: misma clave con `true`, clave nueva con `false` |

**Causa.** Operación frenada por revisión. No confundir con el estado `in_review` de un pago que ya existe. Con `retryable: true` se frenó antes de ejecutar: no se creó nada y no hay `pay_`. Con `retryable: false` quedó registrada al ejecutar: el pago queda `failed`, con `failure_code: in_review`.

**Solución.** Espera la revisión. Con `retryable: true`, repite con la misma clave. Con `retryable: false`, esa clave repite el rechazo: usa una nueva.

### `insufficient_funds`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | Tras fondear, con clave nueva |

**Causa.** Saldo insuficiente.

**Solución.** Fondea la cuenta y repite con una clave nueva: la misma clave repite este rechazo.

### `invalid_beneficiary`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** El destino es inválido. En un alta `latam`, los datos no cumplen lo que pide el país.

**Solución.** Revisa los datos de la cuenta destino. En `latam`, compáralos con [`GET /v1/latam/countries`](/docs/operaciones/listar-paises-latam/). Usa una clave nueva: la misma repite el rechazo.

### `limit_exceeded`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | Con otro monto o más tarde, con clave nueva |

**Causa.** Se rebasó el tope acumulado de la empresa o un límite del riel: ver [Límites](/docs/limites/#montos). En la cuenta de prueba, un tope del ambiente: ver [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/#límites).

**Solución.** Ajusta el monto o el momento. Usa una clave nueva: la misma repite este rechazo.

### `method_unavailable`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** El método de cobro no está disponible.

**Solución.** Consulta [`GET /v1/collection-methods`](/docs/operaciones/listar-metodos-de-cobro/).

### `pair_not_supported`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** El par de monedas no está disponible.

**Solución.** Elige otro par de monedas.

### `payment_rejected`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No con la misma clave |

**Causa.** El pago fue rechazado sin un motivo más específico.

**Solución.** Conserva el `request_id` y escribe a soporte si persiste. La misma clave repite este rechazo.

### `rail_unavailable`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `true` | Sí, misma clave, más tarde |

**Causa.** El riel no está disponible por ahora.

**Solución.** Repite más tarde con la misma clave.

### `receiving_account_rejected`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | No |

**Causa.** La cuenta para recibir no se pudo abrir.

**Solución.** Contacta a tu ejecutivo FITAL y conserva el `request_id`.

### `sandbox_real_account_rejected`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 422 | `false` | Con datos ficticios |

**Causa.** Sólo cuenta de prueba: la CLABE no empieza con `999` o el titular no incluye la palabra `FICTICIO`.

**Solución.** Usa una CLABE `999` y un titular con `FICTICIO`. Ningún dato real entra a este ambiente.

### `rate_limited`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 429 | `true` | Sí, tras `Retry-After` |

**Causa.** Superaste un límite de peticiones.

**Solución.** Espera lo que indique `Retry-After`.

### `sandbox_limit`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 429 | `false` | Al día siguiente |

**Causa.** Sólo cuenta de prueba: alcanzaste el tope diario de pagos y cobros, o de reinicios de datos.

**Solución.** Repite al día siguiente. El tope de pagos y cobros se puede liberar reiniciando los datos desde el panel.

### `internal_error`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 500 | `true` | Sí, misma clave |

**Causa.** Fallo nuestro.

**Solución.** Repite con la misma clave. Si persiste, escribe a soporte con el `request_id`.

### `service_unavailable`

| HTTP | `retryable` | ¿Reintentar? |
|---|---|---|
| 503 | `true` | Sí, misma clave |

**Causa.** El servicio no está disponible por ahora.

**Solución.** Repite con la misma clave y espera exponencial.

## Errores comunes

- Decidir por el texto de `message`. Puede cambiar; `code` no.
- Reintentar un error con `retryable: false` sin corregir la causa.

## Qué sigue

[Reintentos](/docs/recetas/reintentos/).
