# Referencia de la API

Texto completo del contrato, versión `2026-11-01`. Base de producción: `https://widget.fital.xyz/v1`. Hay una [página por operación](/docs/operaciones/), con muestras en 4 lenguajes, y una [referencia interactiva](/docs/referencia/).

Los errores usan la forma de [Errores](/docs/errores.md), salvo las [dos excepciones](/docs/errores.md#forma-del-error) que esa página describe.

Operaciones: 37.

## Cuenta

### GET /v1/balances

Saldos disponibles

Saldo disponible de la empresa dueña de la llave, una fila por moneda.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/balances` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listBalances` |
| Acceso | Llave con el scope `balances:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Un saldo por moneda. |
| `data[].available` | texto | Sí | Saldo disponible en esa moneda, como decimal en texto. |
| `data[].currency` | texto | Sí | Moneda del saldo. |

```json noexec="ejemplo"
{
  "data": [
    {
      "available": "125000.00",
      "currency": "MXN"
    },
    {
      "available": "4200.00",
      "currency": "USD"
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "balances:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-saldos/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Conciliar](/docs/recetas/conciliar/)
- [Autenticación](/docs/autenticacion/)
- [Paginación](/docs/paginacion/)
- [Direcciones de depósito](/docs/recetas/direcciones-deposito/)
- [Pago masivo](/docs/recetas/pago-masivo/)

### GET /v1/limits

Límites de la cuenta

Monto máximo por operación, si existe uno fijo para la empresa, y rieles habilitados. Un riel fuera de `rails` no se puede usar.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/limits` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getLimits` |
| Acceso | Llave con el scope `balances:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- En producción, `max_amount` llega en `null`: no hay tope por pago. Sí hay tope acumulado por empresa, y aplican los límites de cada riel. Ver [Límites](/docs/limites/#montos). En la cuenta de prueba trae el tope ficticio.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `max_amount` | texto o `null` | Sí | Monto máximo por operación. Hoy siempre null: no hay tope por pago. Los pagos sí tienen tope acumulado por empresa, en la guía de límites. Ese tope y los del riel se aplican al ejecutar y responden `limit_exceeded`. |
| `rails` | lista de texto | Sí | Rieles habilitados para la empresa. |

###### Valores de `rails`

| Valor | Significado |
|---|---|
| `mxn_spei` | SPEI, en México. |
| `usd_ach` | ACH, en Estados Unidos. |
| `usd_wire` | Wire, en Estados Unidos. |
| `eur_sepa` | SEPA, en la zona euro. |
| `gbp_fps` | Faster Payments, en Reino Unido. |
| `cop` | Bre-B, en Colombia. |
| `pix` | PIX, en Brasil. |
| `latam` | Transferencia local en otros países de América Latina. |

`null` mientras el alta no se confirme.

```json noexec="ejemplo"
{
  "max_amount": null,
  "rails": [
    "mxn_spei"
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "balances:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-limites/).

#### Guías

- [Límites](/docs/limites/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Conceptos](/docs/conceptos/)
- [Pago ACH](/docs/recetas/pago-ach/)
- [Pago Bre-B](/docs/recetas/pago-bre-b/)
- [Pago FPS](/docs/recetas/pago-fps/)
- [Pago LatAm](/docs/recetas/pago-latam/)
- [Pago PIX](/docs/recetas/pago-pix/)
- [Pago SEPA](/docs/recetas/pago-sepa/)
- [Pago SPEI](/docs/recetas/pago-spei/)
- [Pago wire](/docs/recetas/pago-wire/)

### GET /v1/receiving-accounts

Cuentas para recibir

Cuentas de la empresa para recibir fondos. Los campos que no apliquen a un tipo de cuenta llegan en null.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/receiving-accounts` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listReceivingAccounts` |
| Acceso | Llave con el scope `accounts:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Cuentas de la empresa para recibir fondos. |
| `data[].account_number` | texto o `null` | Sí | Número de cuenta. null si no aplica a esta cuenta. |
| `data[].bank` | texto o `null` | Sí | Banco de la cuenta. null si no aplica a esta cuenta. |
| `data[].bic` | texto o `null` | Sí | Código BIC. null si no aplica a esta cuenta. |
| `data[].br_code` | texto o `null` | Sí | Código BR para pagar por PIX, en cuentas BRL. null si no aplica a esta cuenta. |
| `data[].bre_b_key` | texto o `null` | Sí | Llave Bre-B, en cuentas COP. null si no aplica a esta cuenta. |
| `data[].clabe` | texto o `null` | Sí | CLABE de 18 dígitos. null si no aplica a esta cuenta. |
| `data[].currency` | texto o `null` | Sí | Moneda que recibe la cuenta. |
| `data[].holder` | texto o `null` | Sí | Titular de la cuenta. null si no aplica a esta cuenta. |
| `data[].iban` | texto o `null` | Sí | IBAN. null si no aplica a esta cuenta. |
| `data[].pix_key` | texto o `null` | Sí | Llave PIX, en cuentas BRL. null si no aplica a esta cuenta. |
| `data[].routing_number` | texto o `null` | Sí | Número de ruta bancaria. null si no aplica a esta cuenta. |
| `data[].sort_code` | texto o `null` | Sí | Sort code, en cuentas GBP. null si no aplica a esta cuenta. |

```json noexec="ejemplo"
{
  "data": [
    {
      "account_number": null,
      "bank": "Banco de ejemplo",
      "bic": null,
      "br_code": null,
      "bre_b_key": null,
      "clabe": "999180000000000022",
      "currency": "MXN",
      "holder": "EMPRESA FICTICIA SA DE CV",
      "iban": null,
      "pix_key": null,
      "routing_number": null,
      "sort_code": null
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "accounts:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-cuentas-para-recibir/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Cuentas para recibir](/docs/recetas/cuentas-para-recibir/)
- [Autenticación](/docs/autenticacion/)
- [Paginación](/docs/paginacion/)

### POST /v1/receiving-accounts

Abrir una cuenta para recibir

Abre una cuenta de la empresa en una moneda, si tu empresa tiene esa moneda activada. Hay una por moneda: si ya existe, responde 200 con esa misma cuenta y `already_existed: true`, nunca abre otra. Exige `Idempotency-Key` y firma. 201 si se abrió; 202 si aún no se confirma: repite con la misma clave.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/receiving-accounts` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `createReceivingAccount` |
| Acceso | Llave con el scope `accounts:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |

- Hay una cuenta por moneda. Si ya existe, responde `200` con esa misma cuenta y `already_existed: true`; nunca abre otra.
- `202` significa que el alta aún no se confirma: los datos llegan en `null`. No compartas nada todavía. Repite con la misma clave.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `currency` | texto | Sí | Moneda de la cuenta: `USD`, `EUR`, `BRL`, `GBP` o `COP`. No distingue mayúsculas. MXN no se abre aquí: tu CLABE ya aparece en `GET /v1/receiving-accounts`. |

```json noexec="ejemplo"
{
  "currency": "USD"
}
```

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account_number` | texto o `null` | Sí | Número de cuenta. null si no aplica a esta cuenta. |
| `already_existed` | booleano | Sí | `true` si la empresa ya tenía una cuenta activa en esa moneda y esta es la misma: nunca se abre una segunda. |
| `bank` | texto o `null` | Sí | Banco de la cuenta. null si no aplica a esta cuenta. |
| `bic` | texto o `null` | Sí | Código BIC. null si no aplica a esta cuenta. |
| `br_code` | texto o `null` | Sí | Código BR para pagar por PIX, en cuentas BRL. null si no aplica a esta cuenta. |
| `bre_b_key` | texto o `null` | Sí | Llave Bre-B, en cuentas COP. null si no aplica a esta cuenta. |
| `clabe` | texto o `null` | Sí | CLABE de 18 dígitos. null si no aplica a esta cuenta. |
| `currency` | texto o `null` | Sí | Moneda que recibe la cuenta. |
| `holder` | texto o `null` | Sí | Titular de la cuenta. null si no aplica a esta cuenta. |
| `iban` | texto o `null` | Sí | IBAN. null si no aplica a esta cuenta. |
| `pix_key` | texto o `null` | Sí | Llave PIX, en cuentas BRL. null si no aplica a esta cuenta. |
| `routing_number` | texto o `null` | Sí | Número de ruta bancaria. null si no aplica a esta cuenta. |
| `sort_code` | texto o `null` | Sí | Sort code, en cuentas GBP. null si no aplica a esta cuenta. |
| `status` | texto | Sí | Estado de la cuenta. `processing`: El alta aún no se confirma: repite con la misma clave. Los datos llegan en null. `active`: Ya recibe fondos. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma: repite con la misma clave. Los datos llegan en `null`. |
| `active` | Ya recibe fondos. |

```json noexec="ejemplo"
{
  "account_number": "900000000012",
  "already_existed": true,
  "bank": "Banco de ejemplo",
  "bic": null,
  "br_code": null,
  "bre_b_key": null,
  "clabe": null,
  "currency": "USD",
  "holder": "EMPRESA FICTICIA SA DE CV",
  "iban": null,
  "pix_key": null,
  "routing_number": "999999999",
  "sort_code": null,
  "status": "active"
}
```

##### 201 Creado

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "account_number": "900000000012",
  "already_existed": false,
  "bank": "Banco de ejemplo",
  "bic": null,
  "br_code": null,
  "bre_b_key": null,
  "clabe": null,
  "currency": "USD",
  "holder": "EMPRESA FICTICIA SA DE CV",
  "iban": null,
  "pix_key": null,
  "routing_number": "999999999",
  "sort_code": null,
  "status": "active"
}
```

##### 202 Aceptado: el resultado final llega después

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "account_number": null,
  "already_existed": false,
  "bank": null,
  "bic": null,
  "br_code": null,
  "bre_b_key": null,
  "clabe": null,
  "currency": "USD",
  "holder": null,
  "iban": null,
  "pix_key": null,
  "routing_number": null,
  "sort_code": null,
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`account_restricted`](/docs/errores/#account_restricted) | La cuenta tiene una restricción. | `false` |
| 403 | [`not_enabled`](/docs/errores/#not_enabled) | Esa moneda no está activada para tu empresa. No se abrió nada. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 409 | [`receiving_account_in_progress`](/docs/errores/#receiving_account_in_progress) | Otra alta de esa moneda sigue en curso. Reintenta en unos segundos. | `true` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`in_review`](/docs/errores/#in_review) | Una revisión de tu cuenta frena la operación. | `true` |
| 422 | [`currency_not_supported`](/docs/errores/#currency_not_supported) | Esa moneda no se abre por API. `MXN` responde esto: tu CLABE ya aparece en [`GET /v1/receiving-accounts`](/docs/operaciones/listar-cuentas-para-recibir/). | `false` |
| 422 | [`receiving_account_rejected`](/docs/errores/#receiving_account_rejected) | La cuenta no se pudo abrir. No se reintenta sola. | `false` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 account_restricted`:

```json noexec="ejemplo"
{
  "error": {
    "code": "account_restricted",
    "message": "Tu cuenta tiene una restricción. Contacta a tu ejecutivo FITAL.",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#account_restricted"
  }
}
```

`409 receiving_account_in_progress`:

```json noexec="ejemplo"
{
  "error": {
    "code": "receiving_account_in_progress",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#receiving_account_in_progress"
  }
}
```

`413 invalid_request`:

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

`422 in_review`:

```json noexec="ejemplo"
{
  "error": {
    "code": "in_review",
    "message": "La operación está en revisión",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#in_review"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout o una conexión cortada, repite con la misma clave.

- `409 receiving_account_in_progress`: Sí, con espera
- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `422 in_review`: Mira `retryable`. Con `true`, repite con la misma clave cuando la revisión se levante. Con `false`, el rechazo quedó registrado y esa clave lo repite: usa una clave nueva tras la revisión.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/abrir-cuenta-para-recibir/).

#### Guías

- [Cuentas para recibir](/docs/recetas/cuentas-para-recibir/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Conceptos](/docs/conceptos/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)

### GET /v1/wallets

Wallets de la empresa

Las wallets de la empresa dueña de la llave, con su red y su saldo disponible. Sólo consulta: las wallets no se crean ni se modifican por API. Una red puede tener más de una wallet. La suma por moneda es lo que da `GET /v1/balances`, salvo redondeo al centavo. Lo apartado por compras con tarjeta se descuenta primero de la wallet con más saldo. Para recibir fondos usa `GET /v1/deposit-addresses`.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/wallets` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listWallets` |
| Acceso | Llave con el scope `wallets:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Sólo consulta: las wallets no se crean ni se modifican por API. Para recibir fondos usa [`GET /v1/deposit-addresses`](/docs/operaciones/listar-direcciones-de-deposito/).
- La suma por moneda es lo que da [`GET /v1/balances`](/docs/operaciones/listar-saldos/), salvo redondeo al centavo. Lo apartado por compras con tarjeta se descuenta una vez, primero de la wallet con más saldo.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Todas las wallets de la empresa, ordenadas por red. |
| `data[].balances` | lista de objeto | Sí | Saldo disponible de esta wallet, una fila por moneda, en las mismas monedas de `GET /v1/balances`: USD y EUR. Lista vacía si no tiene saldo en ellas. |
| `data[].balances[].available` | texto | Sí | Saldo disponible en esa moneda, como decimal en texto. |
| `data[].balances[].currency` | texto | Sí | Moneda del saldo. |
| `data[].chain` | texto o `null` | Sí | Red de la wallet, en minúsculas, por ejemplo `base`. |
| `data[].id` | texto | Sí | Identificador de la wallet. No cambia y sólo vale para tu empresa. |

```json noexec="ejemplo"
{
  "data": [
    {
      "balances": [
        {
          "available": "4200.00",
          "currency": "USD"
        }
      ],
      "chain": "base",
      "id": "wal_5f2c8e1ab37d4c60918e2d7f4a6b0c13"
    },
    {
      "balances": [
        {
          "available": "0.00",
          "currency": "USD"
        }
      ],
      "chain": "solana",
      "id": "wal_a94d03be6c1f47d2b8e05c7e12fd6a38"
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "wallets:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-wallets/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Conciliar](/docs/recetas/conciliar/)
- [Autenticación](/docs/autenticacion/)
- [Paginación](/docs/paginacion/)

## Beneficiarios

### GET /v1/beneficiaries

Beneficiarios

Beneficiarios de la empresa, con su estado. Paginación por cursor.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/beneficiaries` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listBeneficiaries` |
| Acceso | Llave con el scope `beneficiaries:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].country` | texto o `null` | Sí | País de la cuenta, si se conoce. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].id` | texto | Sí | Identificador del beneficiario, con prefijo `ben_`. |
| `data[].rail` | texto o `null` | Sí | Riel del beneficiario. null mientras el alta no se confirme. `mxn_spei`: SPEI, en México. `usd_ach`: ACH, en Estados Unidos. `usd_wire`: Wire, en Estados Unidos. `eur_sepa`: SEPA, en la zona euro. `gbp_fps`: Faster Payments, en Reino Unido. `cop`: Bre-B, en Colombia. `pix`: PIX, en Brasil. `latam`: Transferencia local en otros países de América Latina. |
| `data[].status` | texto | Sí | Sólo con `approved` se le puede pagar. La aprobación se hace en el panel. `processing`: El alta sigue en curso. `pending_approval`: Falta aprobarlo en el panel. `approved`: Ya se le puede pagar. `rejected`: No se aprobó. `deleted`: Se dio de baja. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].rail`

| Valor | Significado |
|---|---|
| `mxn_spei` | SPEI, en México. |
| `usd_ach` | ACH, en Estados Unidos. |
| `usd_wire` | Wire, en Estados Unidos. |
| `eur_sepa` | SEPA, en la zona euro. |
| `gbp_fps` | Faster Payments, en Reino Unido. |
| `cop` | Bre-B, en Colombia. |
| `pix` | PIX, en Brasil. |
| `latam` | Transferencia local en otros países de América Latina. |

`null` mientras el alta no se confirme.

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `processing` | El alta sigue en curso. |
| `pending_approval` | Falta aprobarlo en el panel. |
| `approved` | Ya se le puede pagar. |
| `rejected` | No se aprobó. |
| `deleted` | Se dio de baja. |

```json noexec="ejemplo"
{
  "data": [
    {
      "country": "MX",
      "created_at": "2026-11-04T18:00:00.000Z",
      "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
      "rail": "mxn_spei",
      "status": "approved"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "beneficiaries:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-beneficiarios/).

#### Guías

- [Pago SPEI](/docs/recetas/pago-spei/)
- [Paginación](/docs/paginacion/)
- [Autenticación](/docs/autenticacion/)

### POST /v1/beneficiaries

Alta de beneficiario, pendiente de aprobar en el panel

Da de alta una cuenta destino. Nace pendiente y se aprueba en el panel, nunca por API: una llave robada no paga a un destino nuevo. Exige `Idempotency-Key` y firma. 201 si el alta quedó registrada; 202 si aún no se confirma: repite con la misma clave.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/beneficiaries` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `createBeneficiary` |
| Acceso | Llave con el scope `beneficiaries:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |

- `rail` decide qué campos se piden: cada variante del cuerpo tiene los suyos.
- En `latam`, `entity`, `account_type`, `document_type` y `document` dependen del país: [`GET /v1/latam/countries`](/docs/operaciones/listar-paises-latam/) dice qué pide cada uno. En `pix`, `document` es opcional.
- Si la misma cuenta ya estaba registrada, la respuesta trae el beneficiario que ya existe, con su `id` y su estado. Guarda el `id` que recibes, no el que esperabas.
- `202` significa que el alta aún no se confirma: el `id` ya es válido y `rail`, `country` y `created_at` llegan en `null`. Repite con la misma clave, o consulta el `id`.
- El beneficiario nace `pending_approval` y se aprueba en el panel, nunca por API. En la cuenta de prueba nace `approved`.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

##### Con `rail: mxn_spei`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `clabe` | texto | Sí | CLABE de 18 dígitos. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `mxn_spei`: SPEI, en México. |

```json noexec="ejemplo"
{
  "rail": "mxn_spei",
  "holder_name": "PROVEEDOR FICTICIO SA DE CV",
  "clabe": "999180000000000011"
}
```

##### Con `rail: usd_ach`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account_number` | texto | Sí | Número de cuenta, de 4 a 17 dígitos. |
| `account_type` | texto | Sí | Tipo de cuenta. `checking`: Cuenta de cheques o corriente. `savings`: Cuenta de ahorro. |
| `address` | objeto | Sí | Domicilio del titular. |
| `address.city` | texto | Sí | Ciudad. Hasta 100 caracteres. |
| `address.country` | texto | Sí | País, en código de 3 letras. 3 caracteres. |
| `address.postal_code` | texto | Sí | Código postal. Hasta 20 caracteres. |
| `address.state` | texto | Sí | Estado o provincia. Hasta 100 caracteres. |
| `address.street_line_1` | texto | Sí | Calle y número. De 4 a 35 caracteres. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `usd_ach`: ACH, en Estados Unidos. |
| `routing_number` | texto | Sí | Número de ruta, 9 dígitos. |

```json noexec="ejemplo"
{
  "rail": "usd_ach",
  "holder_name": "PROVEEDOR FICTICIO LLC",
  "routing_number": "999999999",
  "account_number": "000123456789",
  "account_type": "checking",
  "address": {
    "street_line_1": "100 Calle Ficticia",
    "city": "Ciudad Ficticia",
    "state": "TX",
    "postal_code": "00000",
    "country": "USA"
  }
}
```

##### Con `rail: usd_wire`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account_number` | texto | Sí | Número de cuenta, de 4 a 17 dígitos. |
| `account_type` | texto | Sí | Tipo de cuenta. `checking`: Cuenta de cheques o corriente. `savings`: Cuenta de ahorro. |
| `address` | objeto | Sí | Domicilio del titular. |
| `address.city` | texto | Sí | Ciudad. Hasta 100 caracteres. |
| `address.country` | texto | Sí | País, en código de 3 letras. 3 caracteres. |
| `address.postal_code` | texto | Sí | Código postal. Hasta 20 caracteres. |
| `address.state` | texto | Sí | Estado o provincia. Hasta 100 caracteres. |
| `address.street_line_1` | texto | Sí | Calle y número. De 4 a 35 caracteres. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `usd_wire`: Wire, en Estados Unidos. |
| `routing_number` | texto | Sí | Número de ruta, 9 dígitos. |

```json noexec="ejemplo"
{
  "rail": "usd_wire",
  "holder_name": "PROVEEDOR FICTICIO LLC",
  "routing_number": "999999999",
  "account_number": "000123456789",
  "account_type": "checking",
  "address": {
    "street_line_1": "100 Calle Ficticia",
    "city": "Ciudad Ficticia",
    "state": "TX",
    "postal_code": "00000",
    "country": "USA"
  }
}
```

##### Con `rail: eur_sepa`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `bic` | texto | Sí | Código BIC del banco. De 8 a 11 caracteres. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `iban` | texto | Sí | IBAN de la cuenta. De 15 a 34 caracteres. |
| `iban_country` | texto | Sí | País del IBAN, en código de 3 letras. 3 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `eur_sepa`: SEPA, en la zona euro. |

```json noexec="ejemplo"
{
  "rail": "eur_sepa",
  "holder_name": "PROVEEDOR FICTICIO SL",
  "iban": "XX00FICTICIO0000000000",
  "bic": "FICTXXXX",
  "iban_country": "ESP"
}
```

##### Con `rail: gbp_fps`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account_number` | texto | Sí | Número de cuenta, 8 dígitos. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `gbp_fps`: Faster Payments, en Reino Unido. |
| `sort_code` | texto | Sí | Sort code, 6 dígitos. |

```json noexec="ejemplo"
{
  "rail": "gbp_fps",
  "holder_name": "PROVEEDOR FICTICIO LTD",
  "account_number": "00000000",
  "sort_code": "000000"
}
```

##### Con `rail: cop`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `bre_b_key` | texto | Sí | Llave Bre-B del titular. Hasta 100 caracteres. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `cop`: Bre-B, en Colombia. |

```json noexec="ejemplo"
{
  "rail": "cop",
  "holder_name": "PROVEEDOR FICTICIO SAS",
  "bre_b_key": "llave-bre-b-de-ejemplo"
}
```

##### Con `rail: pix`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `document` | texto | No | Documento del titular. Hasta 20 caracteres. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `pix_key` | texto | Sí | Llave PIX del titular. Hasta 100 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `pix`: PIX, en Brasil. |

```json noexec="ejemplo"
{
  "rail": "pix",
  "holder_name": "PROVEEDOR FICTICIO LTDA",
  "pix_key": "llave-pix-de-ejemplo",
  "document": "00000000000"
}
```

##### Con `rail: latam`

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `account_number` | texto | Sí | Número de cuenta. `GET /v1/latam/countries` dice qué pide cada país. De 4 a 40 caracteres. |
| `account_type` | texto | No | Tipo de cuenta, si el país lo pide. `GET /v1/latam/countries` dice qué pide cada país. `checking`: Cuenta de cheques o corriente. `savings`: Cuenta de ahorro. |
| `country` | texto | Sí | País de la cuenta, en código de 2 letras. 2 caracteres. |
| `currency` | texto | Sí | Moneda de la cuenta, en código de 3 letras. 3 caracteres. |
| `document` | texto | No | Documento del titular, si el país lo pide. Hasta 30 caracteres. |
| `document_type` | texto | No | Tipo de documento del titular, si el país lo pide. `GET /v1/latam/countries` dice qué pide cada país. Hasta 20 caracteres. |
| `entity` | texto | No | Banco o entidad, si el país lo pide. Los valores salen de `GET /v1/latam/countries/{country}/entities`. Hasta 40 caracteres. |
| `holder_name` | texto | Sí | Titular de la cuenta destino. Hasta 140 caracteres. |
| `rail` | texto | Sí | Riel de pago. Decide qué campos se piden. `latam`: Transferencia local en otros países de América Latina. |

```json noexec="ejemplo"
{
  "rail": "latam",
  "holder_name": "PROVEEDOR FICTICIO SPA",
  "country": "CL",
  "currency": "CLP",
  "account_number": "00000000",
  "entity": "000",
  "document_type": "RUT",
  "document": "00000000-0"
}
```

##### Valores de `rail`

| Valor | Significado |
|---|---|
| `mxn_spei` | SPEI, en México. Ver [Pago SPEI](/docs/recetas/pago-spei/). |
| `usd_ach` | ACH, en Estados Unidos. Ver [Pago ACH](/docs/recetas/pago-ach/). |
| `usd_wire` | Wire, en Estados Unidos. Ver [Pago wire](/docs/recetas/pago-wire/). |
| `eur_sepa` | SEPA, en la zona euro. Ver [Pago SEPA](/docs/recetas/pago-sepa/). |
| `gbp_fps` | Faster Payments, en Reino Unido. Ver [Pago FPS](/docs/recetas/pago-fps/). |
| `cop` | Bre-B, en Colombia. Ver [Pago Bre-B](/docs/recetas/pago-bre-b/). |
| `pix` | PIX, en Brasil. Ver [Pago PIX](/docs/recetas/pago-pix/). |
| `latam` | Transferencia local en otros países de América Latina. Ver [Pago LatAm](/docs/recetas/pago-latam/). |

##### Valores de `account_type`

| Valor | Significado |
|---|---|
| `checking` | Cuenta de cheques o corriente. |
| `savings` | Cuenta de ahorro. |

#### Respuestas correctas

##### 201 Creado

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `country` | texto o `null` | Sí | País de la cuenta, si se conoce. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `id` | texto | Sí | Identificador del beneficiario, con prefijo `ben_`. |
| `rail` | texto o `null` | Sí | Riel del beneficiario. null mientras el alta no se confirme. `mxn_spei`: SPEI, en México. `usd_ach`: ACH, en Estados Unidos. `usd_wire`: Wire, en Estados Unidos. `eur_sepa`: SEPA, en la zona euro. `gbp_fps`: Faster Payments, en Reino Unido. `cop`: Bre-B, en Colombia. `pix`: PIX, en Brasil. `latam`: Transferencia local en otros países de América Latina. |
| `status` | texto | Sí | Sólo con `approved` se le puede pagar. La aprobación se hace en el panel. `processing`: El alta sigue en curso. `pending_approval`: Falta aprobarlo en el panel. `approved`: Ya se le puede pagar. `rejected`: No se aprobó. `deleted`: Se dio de baja. |

###### Valores de `rail`

| Valor | Significado |
|---|---|
| `mxn_spei` | SPEI, en México. |
| `usd_ach` | ACH, en Estados Unidos. |
| `usd_wire` | Wire, en Estados Unidos. |
| `eur_sepa` | SEPA, en la zona euro. |
| `gbp_fps` | Faster Payments, en Reino Unido. |
| `cop` | Bre-B, en Colombia. |
| `pix` | PIX, en Brasil. |
| `latam` | Transferencia local en otros países de América Latina. |

`null` mientras el alta no se confirme.

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta sigue en curso. |
| `pending_approval` | Falta aprobarlo en el panel. |
| `approved` | Ya se le puede pagar. |
| `rejected` | No se aprobó. |
| `deleted` | Se dio de baja. |

```json noexec="ejemplo"
{
  "country": "MX",
  "created_at": "2026-11-04T18:00:00.000Z",
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "rail": "mxn_spei",
  "status": "pending_approval"
}
```

##### 202 Aceptado: el resultado final llega después

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "country": null,
  "created_at": null,
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "rail": null,
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Location` | Ruta del recurso, con `201` y `202`. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún campo no cumple el esquema de su riel. En `usd_ach` y `usd_wire`, también si `address.street_line_1` no trae número de calle. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`account_restricted`](/docs/errores/#account_restricted) | La cuenta tiene una restricción. | `false` |
| 403 | [`rail_not_enabled`](/docs/errores/#rail_not_enabled) | El riel no está habilitado para tu empresa. En la cuenta de prueba, cualquier riel que no sea `mxn_spei`. | `false` |
| 403 | [`not_enabled`](/docs/errores/#not_enabled) | El riel `latam` no está activado para tu empresa. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 409 | [`beneficiary_ref_conflict`](/docs/errores/#beneficiary_ref_conflict) | El identificador del alta ya existe. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`in_review`](/docs/errores/#in_review) | Una revisión de tu cuenta frena las altas nuevas. | `true` |
| 422 | [`invalid_beneficiary`](/docs/errores/#invalid_beneficiary) | El riel rechazó la cuenta destino por sus datos. En `latam`, también un número, banco, documento, moneda o tipo de cuenta que no cumple lo que pide el país: se responde al momento. | `false` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 422 | [`sandbox_real_account_rejected`](/docs/errores/#sandbox_real_account_rejected) (sólo cuenta de prueba) | La CLABE no empieza con `999` o el titular no incluye `FICTICIO`. | `false` |
| 422 | [`limit_exceeded`](/docs/errores/#limit_exceeded) (sólo cuenta de prueba) | Ya tienes 20 beneficiarios. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 account_restricted`:

```json noexec="ejemplo"
{
  "error": {
    "code": "account_restricted",
    "message": "Tu cuenta tiene una restricción. Contacta a tu ejecutivo FITAL.",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#account_restricted"
  }
}
```

`409 beneficiary_ref_conflict`:

```json noexec="ejemplo"
{
  "error": {
    "code": "beneficiary_ref_conflict",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#beneficiary_ref_conflict"
  }
}
```

`413 invalid_request`:

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

`422 in_review`:

```json noexec="ejemplo"
{
  "error": {
    "code": "in_review",
    "message": "La operación está en revisión",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#in_review"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout o una conexión cortada, repite con la misma clave.

- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `422 in_review`: Mira `retryable`. Con `true`, repite con la misma clave cuando la revisión se levante. Con `false`, el rechazo quedó registrado y esa clave lo repite: usa una clave nueva tras la revisión.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/crear-beneficiario/).

#### Guías

- [Pago SPEI](/docs/recetas/pago-spei/)
- [Pago ACH](/docs/recetas/pago-ach/)
- [Pago wire](/docs/recetas/pago-wire/)
- [Pago SEPA](/docs/recetas/pago-sepa/)
- [Pago FPS](/docs/recetas/pago-fps/)
- [Pago Bre-B](/docs/recetas/pago-bre-b/)
- [Pago PIX](/docs/recetas/pago-pix/)
- [Pago LatAm](/docs/recetas/pago-latam/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)

### GET /v1/beneficiaries/{id}

Un beneficiario

Un beneficiario de tu empresa. Si el alta quedó sin confirmar responde `processing`. El id de otra empresa da 404.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/beneficiaries/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getBeneficiary` |
| Acceso | Llave con el scope `beneficiaries:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `country` | texto o `null` | Sí | País de la cuenta, si se conoce. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `id` | texto | Sí | Identificador del beneficiario, con prefijo `ben_`. |
| `rail` | texto o `null` | Sí | Riel del beneficiario. null mientras el alta no se confirme. `mxn_spei`: SPEI, en México. `usd_ach`: ACH, en Estados Unidos. `usd_wire`: Wire, en Estados Unidos. `eur_sepa`: SEPA, en la zona euro. `gbp_fps`: Faster Payments, en Reino Unido. `cop`: Bre-B, en Colombia. `pix`: PIX, en Brasil. `latam`: Transferencia local en otros países de América Latina. |
| `status` | texto | Sí | Sólo con `approved` se le puede pagar. La aprobación se hace en el panel. `processing`: El alta sigue en curso. `pending_approval`: Falta aprobarlo en el panel. `approved`: Ya se le puede pagar. `rejected`: No se aprobó. `deleted`: Se dio de baja. |

###### Valores de `rail`

| Valor | Significado |
|---|---|
| `mxn_spei` | SPEI, en México. |
| `usd_ach` | ACH, en Estados Unidos. |
| `usd_wire` | Wire, en Estados Unidos. |
| `eur_sepa` | SEPA, en la zona euro. |
| `gbp_fps` | Faster Payments, en Reino Unido. |
| `cop` | Bre-B, en Colombia. |
| `pix` | PIX, en Brasil. |
| `latam` | Transferencia local en otros países de América Latina. |

`null` mientras el alta no se confirme.

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta sigue en curso. |
| `pending_approval` | Falta aprobarlo en el panel. |
| `approved` | Ya se le puede pagar. |
| `rejected` | No se aprobó. |
| `deleted` | Se dio de baja. |

```json noexec="ejemplo"
{
  "country": "MX",
  "created_at": "2026-11-04T18:00:00.000Z",
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "rail": "mxn_spei",
  "status": "approved"
}
```

Falta aprobarlo en el panel:

```json noexec="ejemplo"
{
  "country": "MX",
  "created_at": "2026-11-04T18:00:00.000Z",
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "rail": "mxn_spei",
  "status": "pending_approval"
}
```

Alta sin confirmar:

```json noexec="ejemplo"
{
  "country": null,
  "created_at": null,
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "rail": null,
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El beneficiario no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "beneficiaries:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-beneficiario/).

#### Guías

- [Pago SPEI](/docs/recetas/pago-spei/)
- [Conceptos](/docs/conceptos/)
- [Autenticación](/docs/autenticacion/)

### DELETE /v1/beneficiaries/{id}

Baja de beneficiario

Da de baja un beneficiario. Exige `Idempotency-Key` y firma. 202 si la baja aún no se confirma: repite con la misma clave.

| Dato | Valor |
|---|---|
| Método y ruta | `DELETE /v1/beneficiaries/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `deleteBeneficiary` |
| Acceso | Llave con el scope `beneficiaries:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |

- Un pago ya creado conserva su riel: dar de baja al beneficiario no lo cambia.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `id` | texto | Sí | Identificador del beneficiario, con prefijo `ben_`. |
| `status` | texto | Sí | Resultado de la baja. `deleted`: La baja quedó confirmada. `processing`: La baja aún no se confirma: repite con la misma clave. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `deleted` | La baja quedó confirmada. |
| `processing` | La baja aún no se confirma: repite con la misma clave. |

```json noexec="ejemplo"
{
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "status": "deleted"
}
```

##### 202 Aceptado: el resultado final llega después

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "status": "deleted"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`account_restricted`](/docs/errores/#account_restricted) | La cuenta tiene una restricción. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El beneficiario no existe o es de otra empresa. | `false` |
| 409 | [`beneficiary_state_conflict`](/docs/errores/#beneficiary_state_conflict) | Sólo se da de baja un beneficiario `pending_approval` o `approved`. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 422 | [`in_review`](/docs/errores/#in_review) | Una revisión de tu cuenta frena esta operación. | `true` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 account_restricted`:

```json noexec="ejemplo"
{
  "error": {
    "code": "account_restricted",
    "message": "Tu cuenta tiene una restricción. Contacta a tu ejecutivo FITAL.",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#account_restricted"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`409 beneficiary_state_conflict`:

```json noexec="ejemplo"
{
  "error": {
    "code": "beneficiary_state_conflict",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#beneficiary_state_conflict"
  }
}
```

`422 in_review`:

```json noexec="ejemplo"
{
  "error": {
    "code": "in_review",
    "message": "La operación está en revisión",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#in_review"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout o una conexión cortada, repite con la misma clave.

- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `422 in_review`: Mira `retryable`. Con `true`, repite con la misma clave cuando la revisión se levante. Con `false`, el rechazo quedó registrado y esa clave lo repite: usa una clave nueva tras la revisión.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/eliminar-beneficiario/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Autenticación](/docs/autenticacion/)

### GET /v1/latam/countries

Países del riel `latam`

Países a los que tu empresa puede pagar por el riel `latam`, con su moneda y lo que cada uno pide para dar de alta una cuenta. Sale de la configuración vigente: consúltalo en vez de copiarlo.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/latam/countries` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listLatamCountries` |
| Acceso | Llave con el scope `beneficiaries:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Sale de la configuración vigente y sólo trae los países que tu empresa puede pagar: consúltalo antes de un alta, en vez de copiar la tabla de [Cobertura](/docs/cobertura/#países-del-riel-latam).
- `available: false` significa que el país está restringido por ahora: el alta y los pagos pueden fallar.
- `account` dice qué pide el alta: `entity_required`, `account_type_required` con sus valores en `account_types`, y el largo del número. Un largo que no aplica llega en `null`.
- En la cuenta de prueba los países son ficticios y todos llegan con `available: false`: ese ambiente sólo simula SPEI, así que el alta `latam` no procede.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Países del riel `latam` para tu empresa. |
| `data[].account` | objeto | Sí | Qué se pide de la cuenta. |
| `data[].account.account_type_required` | booleano | Sí | true si el alta exige `account_type`. |
| `data[].account.account_types` | lista de texto | Sí | Valores admitidos en `account_type`. |
| `data[].account.digits_only` | booleano | Sí | true si sólo admite dígitos. |
| `data[].account.entity_required` | booleano | Sí | true si el alta exige `entity`. |
| `data[].account.exact_length` | entero o `null` | Sí | Largo exacto. null si no aplica. |
| `data[].account.label` | texto o `null` | Sí | Cómo se llama el número de cuenta en ese país. |
| `data[].account.max_length` | entero o `null` | Sí | Largo máximo. null si no aplica. |
| `data[].account.min_length` | entero o `null` | Sí | Largo mínimo. null si no aplica. |
| `data[].available` | booleano | Sí | false mientras el país esté restringido: el alta y los pagos pueden fallar. |
| `data[].country` | texto | Sí | País, en código de 2 letras. Es el `country` del alta. |
| `data[].currency` | texto | Sí | Moneda de la cuenta. Es el `currency` del alta. |
| `data[].document_types` | lista de texto | Sí | Valores admitidos en `document_type`. |

###### Valores de `data[].account.account_types`

| Valor | Significado |
|---|---|
| `checking` | Cuenta de cheques o corriente. |
| `savings` | Cuenta de ahorro. |

Lista vacía si el país no usa tipo de cuenta.

```json noexec="ejemplo"
{
  "data": [
    {
      "account": {
        "account_type_required": false,
        "account_types": [
          "checking",
          "savings"
        ],
        "digits_only": true,
        "entity_required": true,
        "exact_length": null,
        "label": "Número de cuenta",
        "max_length": 20,
        "min_length": 5
      },
      "available": true,
      "country": "CL",
      "currency": "CLP",
      "document_types": [
        "RUT",
        "PP"
      ]
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`rail_not_enabled`](/docs/errores/#rail_not_enabled) | El riel `latam` no está habilitado para tu empresa. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 rail_not_enabled`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rail_not_enabled",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rail_not_enabled"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-paises-latam/).

#### Guías

- [Pago LatAm](/docs/recetas/pago-latam/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)

### GET /v1/latam/countries/{country}/entities

Bancos de un país del riel `latam`

Bancos y entidades de un país del riel `latam`, con el valor a enviar en `entity`. Un país fuera de `GET /v1/latam/countries` responde `404`.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/latam/countries/{country}/entities` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listLatamEntities` |
| Acceso | Llave con el scope `beneficiaries:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Envía `code` tal cual en `entity`. `country` admite mayúsculas o minúsculas.
- Consúltalo cuando `account.entity_required` sea `true`.
- En la cuenta de prueba responde un solo banco ficticio.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `country` | ruta | texto | Sí | País, en código de 2 letras. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Bancos y entidades del país. |
| `data[].code` | texto | Sí | Valor a enviar en `entity`. |
| `data[].name` | texto | Sí | Nombre del banco o entidad. |

```json noexec="ejemplo"
{
  "data": [
    {
      "code": "999",
      "name": "Banco de ejemplo"
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`rail_not_enabled`](/docs/errores/#rail_not_enabled) | El riel `latam` no está habilitado para tu empresa. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El país no está en `GET /v1/latam/countries`. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 rail_not_enabled`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rail_not_enabled",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rail_not_enabled"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-bancos-latam/).

#### Guías

- [Pago LatAm](/docs/recetas/pago-latam/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)

## Pagos

### GET /v1/payments

Pagos, con su último estado conocido

Pagos creados por esta API, con su último estado conocido, del más nuevo al más viejo. Para el estado más fresco de uno, usa `GET /v1/payments/{id}`.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/payments` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listPayments` |
| Acceso | Llave con el scope `payments:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Es una vista guardada: no lee en vivo y no lleva `freshness`. Puede ir una vuelta por detrás del detalle. Ver [Lista y detalle](/docs/conceptos/#lista-y-detalle).
- `failure_code` llega en `null` en esta lista, también con `failed`: léelo en `GET /v1/payments/{id}` o en `payment.failed`.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].amount` | texto o `null` | Sí | Monto pedido. |
| `data[].beneficiary_id` | texto o `null` | Sí | Beneficiario del pago. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].currency` | texto o `null` | Sí | Moneda pedida. |
| `data[].external_ref` | texto o `null` | Sí | Tu referencia. |
| `data[].failure_code` | texto o `null` | Sí | Código del catálogo de errores si el pago falló, null en cualquier otro estado. En `GET /v1/payments` siempre es null: léelo en `GET /v1/payments/{id}` o en `payment.failed`. Trata la lista como extensible. `payment_rejected`: Rechazado sin un motivo más específico. `insufficient_funds`: Saldo insuficiente al ejecutar. `limit_exceeded`: Se superó el tope acumulado de la empresa o un límite del riel. `amount_below_minimum`: El monto quedó por debajo del mínimo aplicable. `account_restricted`: La cuenta tiene una restricción. `in_review`: Una revisión detuvo la ejecución. `quote_changed`: El débito superaría lo permitido. `quote_expired`: La cotización venció antes de ejecutar. |
| `data[].final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `data[].final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `data[].freshness` | objeto | No | De dónde sale `status`. Sólo en `GET /v1/payments/{id}`. |
| `data[].freshness.as_of` | texto o `null` | Sí | Hora de esa lectura, de la última que se aplicó o de la cancelación confirmada. Null si aún no hay ninguna. ISO 8601 en UTC. |
| `data[].freshness.status` | texto | Sí | De dónde sale `status`. `current`: `status` sale de una lectura de ahora. `delayed`: La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |
| `data[].id` | texto | Sí | Identificador del pago, con prefijo `pay_`. |
| `data[].metadata` | objeto | Sí | Los pares que enviaste al crear el pago. |
| `data[].reference` | texto o `null` | Sí | Concepto del pago. |
| `data[].status` | texto | Sí | Estado público. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].failure_code`

| Valor | Significado |
|---|---|
| `payment_rejected` | Rechazado sin un motivo más específico. |
| `insufficient_funds` | Saldo insuficiente al ejecutar. |
| `limit_exceeded` | Se superó el tope acumulado de la empresa o un límite del riel. |
| `amount_below_minimum` | El monto quedó por debajo del mínimo aplicable. |
| `account_restricted` | La cuenta tiene una restricción. |
| `in_review` | Una revisión detuvo la ejecución. |
| `quote_changed` | El débito superaría lo permitido. |
| `quote_expired` | La cotización venció antes de ejecutar. |

Con `failed`, `GET /v1/payments/{id}` y `payment.failed` siempre traen un código: `payment_rejected` si no hay uno más específico. En cualquier otro estado, y en la lista `GET /v1/payments`, es `null`. Trata la lista como extensible.

###### Valores de `data[].freshness.status`

| Valor | Significado |
|---|---|
| `current` | `status` sale de una lectura de ahora. |
| `delayed` | La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1500.00",
      "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "external_ref": "pedido-7731",
      "failure_code": null,
      "final_amount": "1500.00",
      "final_currency": "MXN",
      "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
      "metadata": {},
      "reference": "Factura 1042",
      "status": "completed"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "payments:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-pagos/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Paginación](/docs/paginacion/)
- [Autenticación](/docs/autenticacion/)
- [Conciliar](/docs/recetas/conciliar/)
- [Pago masivo](/docs/recetas/pago-masivo/)
- [Pago SPEI](/docs/recetas/pago-spei/)

### POST /v1/payments

Crear un pago

Crea un pago a un beneficiario aprobado. Exige `Idempotency-Key` y firma. Responde 202: el resultado llega por webhook o en `GET /v1/payments/{id}`. Si la respuesta se corta, repite con la misma clave: nunca crea un segundo pago. `in_review` con `retryable: false` quedó registrado: esa clave repite el rechazo, y volver a intentarlo exige otra.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/payments` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `createPayment` |
| Acceso | Llave con el scope `payments:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |
| Escenarios | `payment_completed`, `insufficient_funds`, `payment_held`. Ver [Ambientes](/docs/ambientes/#escenarios) |

- El riel sale del beneficiario: no se elige aquí.
- `quote_id` es opcional. Si lo envías, debe ser de este mismo beneficiario, monto y moneda, y `max_source_amount` manda sólo si es menor que lo cotizado.
- `202` con `processing` no es fallo: el resultado llega por webhook o en `GET /v1/payments/{id}`. El encabezado `Location` trae la ruta del pago.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto | Sí | Monto a enviar, como decimal en texto. |
| `beneficiary_id` | texto | Sí | Beneficiario aprobado que recibe el pago. |
| `currency` | texto | Sí | Moneda del monto. |
| `external_ref` | texto | Sí | Tu referencia del pago, hasta 120 caracteres imprimibles. Vuelve en el pago y en sus eventos. Hasta 120 caracteres. |
| `max_source_amount` | texto | No | Máximo a debitar. Con cotización, manda el menor de los dos. |
| `metadata` | objeto | No | Hasta 20 pares clave y valor tuyos. Valores de hasta 500 caracteres. |
| `quote_id` | texto | No | Cotización vigente de este mismo beneficiario, monto y moneda. Fija el tope de débito. |
| `reference` | texto | No | Concepto del pago, hasta 140 caracteres imprimibles. Hasta 140 caracteres. |

```json noexec="ejemplo"
{
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "external_ref": "factura-1042"
}
```

#### Respuestas correctas

##### 202 Aceptado: el resultado final llega después

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto pedido. |
| `beneficiary_id` | texto o `null` | Sí | Beneficiario del pago. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda pedida. |
| `external_ref` | texto o `null` | Sí | Tu referencia. |
| `failure_code` | texto o `null` | Sí | Código del catálogo de errores si el pago falló, null en cualquier otro estado. En `GET /v1/payments` siempre es null: léelo en `GET /v1/payments/{id}` o en `payment.failed`. Trata la lista como extensible. `payment_rejected`: Rechazado sin un motivo más específico. `insufficient_funds`: Saldo insuficiente al ejecutar. `limit_exceeded`: Se superó el tope acumulado de la empresa o un límite del riel. `amount_below_minimum`: El monto quedó por debajo del mínimo aplicable. `account_restricted`: La cuenta tiene una restricción. `in_review`: Una revisión detuvo la ejecución. `quote_changed`: El débito superaría lo permitido. `quote_expired`: La cotización venció antes de ejecutar. |
| `final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `freshness` | objeto | No | De dónde sale `status`. Sólo en `GET /v1/payments/{id}`. |
| `freshness.as_of` | texto o `null` | Sí | Hora de esa lectura, de la última que se aplicó o de la cancelación confirmada. Null si aún no hay ninguna. ISO 8601 en UTC. |
| `freshness.status` | texto | Sí | De dónde sale `status`. `current`: `status` sale de una lectura de ahora. `delayed`: La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |
| `id` | texto | Sí | Identificador del pago, con prefijo `pay_`. |
| `metadata` | objeto | Sí | Los pares que enviaste al crear el pago. |
| `reference` | texto o `null` | Sí | Concepto del pago. |
| `status` | texto | Sí | Estado público. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |

###### Valores de `failure_code`

| Valor | Significado |
|---|---|
| `payment_rejected` | Rechazado sin un motivo más específico. |
| `insufficient_funds` | Saldo insuficiente al ejecutar. |
| `limit_exceeded` | Se superó el tope acumulado de la empresa o un límite del riel. |
| `amount_below_minimum` | El monto quedó por debajo del mínimo aplicable. |
| `account_restricted` | La cuenta tiene una restricción. |
| `in_review` | Una revisión detuvo la ejecución. |
| `quote_changed` | El débito superaría lo permitido. |
| `quote_expired` | La cotización venció antes de ejecutar. |

Con `failed`, `GET /v1/payments/{id}` y `payment.failed` siempre traen un código: `payment_rejected` si no hay uno más específico. En cualquier otro estado, y en la lista `GET /v1/payments`, es `null`. Trata la lista como extensible.

###### Valores de `freshness.status`

| Valor | Significado |
|---|---|
| `current` | `status` sale de una lectura de ahora. |
| `delayed` | La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": null,
  "final_currency": null,
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "metadata": {},
  "reference": "Factura 1042",
  "status": "processing"
}
```

Variante `payment_completed`:

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": null,
  "final_currency": null,
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "metadata": {},
  "reference": "Factura 1042",
  "status": "processing"
}
```

Variante `payment_failed`:

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": null,
  "final_currency": null,
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "metadata": {},
  "reference": "Factura 1042",
  "status": "processing"
}
```

Variante `payment_returned`:

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": null,
  "final_currency": null,
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "metadata": {},
  "reference": "Factura 1042",
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Location` | Ruta del recurso, con `201` y `202`. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún campo no cumple el esquema. También si `amount` es cero, si lleva decimales en una moneda sin decimales, o si `quote_id` es de otra operación. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`rail_not_enabled`](/docs/errores/#rail_not_enabled) | El riel del beneficiario no está habilitado para tu empresa. | `false` |
| 403 | [`not_enabled`](/docs/errores/#not_enabled) | PIX sin activación vigente para tu empresa. | `false` |
| 403 | [`account_restricted`](/docs/errores/#account_restricted) | La cuenta tiene una restricción. Puede llegar antes de ejecutar, o quedar registrado al ejecutar. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El beneficiario o la cotización no existen, o son de otra empresa. | `false` |
| 409 | [`payment_in_flight`](/docs/errores/#payment_in_flight) | Otra salida de dinero de tu empresa sigue sin confirmarse. | `true` |
| 409 | [`quote_changed`](/docs/errores/#quote_changed) | El débito superaría `max_source_amount` o lo cotizado. No se creó ningún pago. | `false` |
| 409 | [`quote_expired`](/docs/errores/#quote_expired) | La cotización `quote_id` venció. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`beneficiary_not_approved`](/docs/errores/#beneficiary_not_approved) | El beneficiario existe pero no está `approved`. | `true` |
| 422 | [`rail_unavailable`](/docs/errores/#rail_unavailable) | El riel no está disponible por ahora. | `true` |
| 422 | [`currency_not_supported`](/docs/errores/#currency_not_supported) | `currency` no es la moneda del riel del beneficiario. | `false` |
| 422 | [`in_review`](/docs/errores/#in_review) | Una revisión frena la operación. Con `retryable: true` se frenó antes de ejecutar y no hay `pay_`. Con `retryable: false` quedó registrado: el pago queda `failed` con `failure_code: in_review`. | `true` |
| 422 | [`insufficient_funds`](/docs/errores/#insufficient_funds) | Saldo insuficiente. El rechazo queda registrado con esa clave. | `false` |
| 422 | [`limit_exceeded`](/docs/errores/#limit_exceeded) | Se superó un límite del riel. El rechazo queda registrado con esa clave. | `false` |
| 422 | [`amount_below_minimum`](/docs/errores/#amount_below_minimum) | El monto está por debajo del mínimo aplicable. El rechazo queda registrado con esa clave. | `false` |
| 422 | [`payment_rejected`](/docs/errores/#payment_rejected) | El pago se rechazó sin un motivo más específico. El rechazo queda registrado con esa clave. | `false` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 422 | [`invalid_beneficiary`](/docs/errores/#invalid_beneficiary) (sólo cuenta de prueba) | El beneficiario se dio de baja, o usaste el escenario `invalid_beneficiary`. | `false` |
| 422 | [`limit_exceeded`](/docs/errores/#limit_exceeded) (sólo cuenta de prueba) | El monto supera 1,000,000 ficticios por pago. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 429 | [`sandbox_limit`](/docs/errores/#sandbox_limit) (sólo cuenta de prueba) | Alcanzaste 100 pagos y cobros de hoy. | `false` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 rail_not_enabled`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rail_not_enabled",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rail_not_enabled"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`409 payment_in_flight`:

```json noexec="ejemplo"
{
  "error": {
    "code": "payment_in_flight",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#payment_in_flight"
  }
}
```

`413 invalid_request`:

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

`422 beneficiary_not_approved`:

```json noexec="ejemplo"
{
  "error": {
    "code": "beneficiary_not_approved",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#beneficiary_not_approved"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout o una conexión cortada, repite con la misma clave: nunca crea un segundo pago. Un error con `retryable: false` quedó registrado o no se arregla repitiendo: la misma clave repite ese rechazo, así que corrige la causa y usa una clave nueva. Ver [Qué hacer con la clave tras un rechazo](/docs/idempotencia/#qué-hacer-con-la-clave-tras-un-rechazo).

- `409 payment_in_flight`: Repite con la misma clave cuando la otra salida se resuelva.
- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `422 beneficiary_not_approved`: Apruébalo en el panel y repite con la misma clave.
- `422 rail_unavailable`: Repite más tarde.
- `422 in_review`: Mira `retryable`. Con `true`, repite con la misma clave cuando la revisión se levante. Con `false`, el rechazo quedó registrado y esa clave lo repite: usa una clave nueva tras la revisión.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/crear-pago/).

#### Guías

- [Tu primer pago de prueba](/docs/primer-pago/)
- [Pago SPEI](/docs/recetas/pago-spei/)
- [Pago masivo](/docs/recetas/pago-masivo/)
- [Reintentos](/docs/recetas/reintentos/)
- [Idempotencia](/docs/idempotencia/)
- [Autenticación](/docs/autenticacion/)
- [Pago ACH](/docs/recetas/pago-ach/)
- [Pago Bre-B](/docs/recetas/pago-bre-b/)
- [Pago FPS](/docs/recetas/pago-fps/)
- [Pago LatAm](/docs/recetas/pago-latam/)
- [Pago PIX](/docs/recetas/pago-pix/)
- [Pago SEPA](/docs/recetas/pago-sepa/)
- [Pago wire](/docs/recetas/pago-wire/)

### GET /v1/payments/{id}

Un pago

Un pago de tu empresa. `completed` exige confirmación del riel. `freshness` dice de dónde sale el estado: con `current` es la lectura de ahora y `as_of` su hora; con `delayed` no hubo lectura concluyente y es lo último que se sabe, a la hora de `as_of`. Una cancelación confirmada responde `canceled` con `delayed` hasta que una lectura la confirme. Si un pago cancelado vuelve a `processing` con `current`, se reabrió de verdad.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/payments/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getPayment` |
| Acceso | Llave con el scope `payments:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- `freshness` sólo existe aquí. Con `delayed`, consulta otra vez más tarde. Ver [Frescura del detalle](/docs/conceptos/#frescura-del-detalle).
- Con `failed`, `failure_code` siempre trae un código: `payment_rejected` si no hay uno más específico o si el detalle ya no se puede leer. En cualquier otro estado es `null`.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto pedido. |
| `beneficiary_id` | texto o `null` | Sí | Beneficiario del pago. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda pedida. |
| `external_ref` | texto o `null` | Sí | Tu referencia. |
| `failure_code` | texto o `null` | Sí | Código del catálogo de errores si el pago falló, null en cualquier otro estado. En `GET /v1/payments` siempre es null: léelo en `GET /v1/payments/{id}` o en `payment.failed`. Trata la lista como extensible. `payment_rejected`: Rechazado sin un motivo más específico. `insufficient_funds`: Saldo insuficiente al ejecutar. `limit_exceeded`: Se superó el tope acumulado de la empresa o un límite del riel. `amount_below_minimum`: El monto quedó por debajo del mínimo aplicable. `account_restricted`: La cuenta tiene una restricción. `in_review`: Una revisión detuvo la ejecución. `quote_changed`: El débito superaría lo permitido. `quote_expired`: La cotización venció antes de ejecutar. |
| `final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `freshness` | objeto | No | De dónde sale `status`. Sólo en `GET /v1/payments/{id}`. |
| `freshness.as_of` | texto o `null` | Sí | Hora de esa lectura, de la última que se aplicó o de la cancelación confirmada. Null si aún no hay ninguna. ISO 8601 en UTC. |
| `freshness.status` | texto | Sí | De dónde sale `status`. `current`: `status` sale de una lectura de ahora. `delayed`: La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |
| `id` | texto | Sí | Identificador del pago, con prefijo `pay_`. |
| `metadata` | objeto | Sí | Los pares que enviaste al crear el pago. |
| `reference` | texto o `null` | Sí | Concepto del pago. |
| `status` | texto | Sí | Estado público. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |

###### Valores de `failure_code`

| Valor | Significado |
|---|---|
| `payment_rejected` | Rechazado sin un motivo más específico. |
| `insufficient_funds` | Saldo insuficiente al ejecutar. |
| `limit_exceeded` | Se superó el tope acumulado de la empresa o un límite del riel. |
| `amount_below_minimum` | El monto quedó por debajo del mínimo aplicable. |
| `account_restricted` | La cuenta tiene una restricción. |
| `in_review` | Una revisión detuvo la ejecución. |
| `quote_changed` | El débito superaría lo permitido. |
| `quote_expired` | La cotización venció antes de ejecutar. |

Con `failed`, `GET /v1/payments/{id}` y `payment.failed` siempre traen un código: `payment_rejected` si no hay uno más específico. En cualquier otro estado, y en la lista `GET /v1/payments`, es `null`. Trata la lista como extensible.

###### Valores de `freshness.status`

| Valor | Significado |
|---|---|
| `current` | `status` sale de una lectura de ahora. |
| `delayed` | La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "processing",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

`processing` con `delayed`: la lectura no concluyó:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "processing",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "delayed",
    "as_of": "2026-11-04T18:00:30.000Z"
  }
}
```

En revisión: no lo repitas:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "in_review",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

`failed`, con su código:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "failed",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": "insufficient_funds",
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

`returned`: se devolvió después de `completed`:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "returned",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

`canceled` con `delayed`: falta la lectura que lo confirme:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "canceled",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "delayed",
    "as_of": "2026-11-04T18:01:30.000Z"
  }
}
```

Variante `payment_completed`:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "completed",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": "1500.00",
  "final_currency": "MXN",
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

Variante `payment_failed`:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "failed",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": "payment_rejected",
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

Variante `payment_returned`:

```json noexec="ejemplo"
{
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "returned",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN",
  "final_amount": null,
  "final_currency": null,
  "reference": "Factura 1042",
  "external_ref": "factura-1042",
  "metadata": {},
  "failure_code": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "freshness": {
    "status": "current",
    "as_of": "2026-11-04T18:00:00.000Z"
  }
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El pago no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "payments:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-pago/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Conciliar](/docs/recetas/conciliar/)
- [Reintentos](/docs/recetas/reintentos/)
- [Ambientes](/docs/ambientes/)
- [Autenticación](/docs/autenticacion/)

### POST /v1/payments/{id}/cancel

Cancelar un pago que aún espera fondos

Cancela un pago que aún espera fondos. Exige `Idempotency-Key` y firma. Si ya no se puede cancelar da `409 not_cancellable`. 202 si la cancelación aún no se confirma: repite con la misma clave.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/payments/{id}/cancel` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `cancelPayment` |
| Acceso | Llave con el scope `payments:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |

- Sin cuerpo: firma el hash de cero bytes.
- Tras `200`, `GET /v1/payments/{id}` responde `canceled` con `freshness.status` en `delayed` hasta que una lectura lo confirme.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto pedido. |
| `beneficiary_id` | texto o `null` | Sí | Beneficiario del pago. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda pedida. |
| `external_ref` | texto o `null` | Sí | Tu referencia. |
| `failure_code` | texto o `null` | Sí | Código del catálogo de errores si el pago falló, null en cualquier otro estado. En `GET /v1/payments` siempre es null: léelo en `GET /v1/payments/{id}` o en `payment.failed`. Trata la lista como extensible. `payment_rejected`: Rechazado sin un motivo más específico. `insufficient_funds`: Saldo insuficiente al ejecutar. `limit_exceeded`: Se superó el tope acumulado de la empresa o un límite del riel. `amount_below_minimum`: El monto quedó por debajo del mínimo aplicable. `account_restricted`: La cuenta tiene una restricción. `in_review`: Una revisión detuvo la ejecución. `quote_changed`: El débito superaría lo permitido. `quote_expired`: La cotización venció antes de ejecutar. |
| `final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `freshness` | objeto | No | De dónde sale `status`. Sólo en `GET /v1/payments/{id}`. |
| `freshness.as_of` | texto o `null` | Sí | Hora de esa lectura, de la última que se aplicó o de la cancelación confirmada. Null si aún no hay ninguna. ISO 8601 en UTC. |
| `freshness.status` | texto | Sí | De dónde sale `status`. `current`: `status` sale de una lectura de ahora. `delayed`: La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |
| `id` | texto | Sí | Identificador del pago, con prefijo `pay_`. |
| `metadata` | objeto | Sí | Los pares que enviaste al crear el pago. |
| `reference` | texto o `null` | Sí | Concepto del pago. |
| `status` | texto | Sí | Estado público. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |

###### Valores de `failure_code`

| Valor | Significado |
|---|---|
| `payment_rejected` | Rechazado sin un motivo más específico. |
| `insufficient_funds` | Saldo insuficiente al ejecutar. |
| `limit_exceeded` | Se superó el tope acumulado de la empresa o un límite del riel. |
| `amount_below_minimum` | El monto quedó por debajo del mínimo aplicable. |
| `account_restricted` | La cuenta tiene una restricción. |
| `in_review` | Una revisión detuvo la ejecución. |
| `quote_changed` | El débito superaría lo permitido. |
| `quote_expired` | La cotización venció antes de ejecutar. |

Con `failed`, `GET /v1/payments/{id}` y `payment.failed` siempre traen un código: `payment_rejected` si no hay uno más específico. En cualquier otro estado, y en la lista `GET /v1/payments`, es `null`. Trata la lista como extensible.

###### Valores de `freshness.status`

| Valor | Significado |
|---|---|
| `current` | `status` sale de una lectura de ahora. |
| `delayed` | La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": null,
  "final_currency": null,
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "metadata": {},
  "reference": "Factura 1042",
  "status": "canceled"
}
```

##### 202 Aceptado: el resultado final llega después

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": null,
  "final_currency": null,
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "metadata": {},
  "reference": "Factura 1042",
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El pago no existe o es de otra empresa. | `false` |
| 409 | [`not_cancellable`](/docs/errores/#not_cancellable) | El pago ya no se puede cancelar. Es la respuesta normal cuando ya avanzó. Un pago `latam` nunca se cancela por esta operación. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "payments:write"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`409 not_cancellable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_cancellable",
    "message": "El pago no se puede cancelar",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_cancellable"
  }
}
```

`422 idempotency_key_reused`:

```json noexec="ejemplo"
{
  "error": {
    "code": "idempotency_key_reused",
    "message": "La clave ya se usó con otro cuerpo",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#idempotency_key_reused"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

`202` significa que la cancelación aún no se confirma: repite con la misma clave. Ante un timeout, repite con la misma clave: una cancelación nunca se da por aceptada.

- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/cancelar-pago/).

#### Guías

- [Pago SPEI](/docs/recetas/pago-spei/)
- [Conceptos](/docs/conceptos/)
- [Autenticación](/docs/autenticacion/)
- [Pago LatAm](/docs/recetas/pago-latam/)

### POST /v1/quotes

Cotización indicativa con vencimiento

Estima cuánto se debitaría por un pago a un beneficiario de tu empresa. Es indicativa y vence. No mueve dinero. Envía `quote_id` al crear el pago para fijar el tope de débito.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/quotes` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `createQuote` |
| Acceso | Llave con el scope `quotes:write` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Sin clave de idempotencia y sin firma: no mueve dinero. Repetirla crea otra cotización.
- Vence a los 60 segundos en producción y a los 300 en la cuenta de prueba. Usa `expires_at`.
- El beneficiario no necesita estar aprobado para cotizar: sí para pagar.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto | Sí | Monto a enviar. |
| `beneficiary_id` | texto | Sí | Beneficiario del pago que quieres cotizar. |
| `currency` | texto | Sí | Moneda del monto. |

```json noexec="ejemplo"
{
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "amount": "1500.00",
  "currency": "MXN"
}
```

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto | Sí | Monto a enviar. |
| `beneficiary_id` | texto | Sí | Beneficiario cotizado. |
| `currency` | texto | Sí | Moneda del monto. |
| `expires_at` | texto | Sí | Vencimiento de la cotización. ISO 8601 en UTC. |
| `id` | texto | Sí | Identificador de la cotización, con prefijo `quo_`. |
| `indicative` | booleano | Sí | Siempre true: no es un precio garantizado. |
| `source_amount` | texto | Sí | Débito estimado. |
| `source_currency` | texto | Sí | Moneda del débito. |

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "currency": "MXN",
  "expires_at": "2026-11-04T18:05:00.000Z",
  "id": "quo_8dZxN3vTk6LqB2sW9mHrY4cP",
  "indicative": true,
  "source_amount": "82.50",
  "source_currency": "USD"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El beneficiario no existe o es de otra empresa. | `false` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`currency_not_supported`](/docs/errores/#currency_not_supported) | `currency` no es la moneda del riel del beneficiario. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | No se pudo calcular el débito. No se creó ninguna cotización. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "quotes:write"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`413 invalid_request`:

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

`422 currency_not_supported`:

```json noexec="ejemplo"
{
  "error": {
    "code": "currency_not_supported",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#currency_not_supported"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

No lleva clave de idempotencia y no mueve dinero: repetirla es seguro.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/crear-cotizacion/).

#### Guías

- [Conceptos](/docs/conceptos/)
- [Pago SPEI](/docs/recetas/pago-spei/)
- [Autenticación](/docs/autenticacion/)
- [Pago ACH](/docs/recetas/pago-ach/)
- [Pago LatAm](/docs/recetas/pago-latam/)

## Cobros

### GET /v1/collection-methods

Métodos de cobro

Métodos de cobro de la empresa, con país, moneda, mínimos y máximos.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/collection-methods` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listCollectionMethods` |
| Acceso | Llave con el scope `collections:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Sólo lista los métodos que puede usar tu empresa. `available: false` significa que el método existe pero hoy no se puede usar.
- `payer_fields` lista lo que el método pide en `payer` al crear el cobro, y `POST /v1/collections` lo aplica: `required`, `options` con los valores admitidos y `phone_prefix`. Vacío significa que no pide nada.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Métodos de cobro disponibles para la empresa. |
| `data[].available` | booleano | Sí | true si se puede usar ahora. |
| `data[].country` | texto o `null` | Sí | País del método. |
| `data[].currency` | texto o `null` | Sí | Moneda del método. |
| `data[].max_amount` | texto o `null` | Sí | Monto máximo. |
| `data[].method` | texto o `null` | Sí | Nombre a usar en `method`. |
| `data[].min_amount` | texto o `null` | Sí | Monto mínimo. |
| `data[].payer_fields` | lista de objeto | Sí | Lo que el método pide en `payer` al crear el cobro. |
| `data[].payer_fields[].name` | texto | Sí | Clave dentro de `payer`. `firstName`: Nombre, o razón social. `lastName`: Apellido. `email`: Correo. `phone`: Teléfono con su prefijo internacional. `documentType`: Tipo de documento. `documentNumber`: Número de documento. |
| `data[].payer_fields[].options` | lista de texto o `null` | Sí | Valores admitidos. null si es texto libre. |
| `data[].payer_fields[].phone_prefix` | texto o `null` | Sí | Prefijo del país, como `+57`. null si no aplica. |
| `data[].payer_fields[].required` | booleano | Sí | true si el cobro se rechaza sin este dato. |

###### Valores de `data[].payer_fields[].name`

| Valor | Significado |
|---|---|
| `firstName` | Nombre, o razón social. |
| `lastName` | Apellido. |
| `email` | Correo. |
| `phone` | Teléfono con su prefijo internacional. |
| `documentType` | Tipo de documento. |
| `documentNumber` | Número de documento. |

El contrato lo declara como texto: trata la lista como extensible.

```json noexec="ejemplo"
{
  "data": [
    {
      "available": true,
      "country": "MX",
      "currency": "MXN",
      "max_amount": "50000.00",
      "method": "spei",
      "min_amount": "10.00",
      "payer_fields": [
        {
          "name": "firstName",
          "options": null,
          "phone_prefix": null,
          "required": true
        },
        {
          "name": "email",
          "options": null,
          "phone_prefix": null,
          "required": true
        },
        {
          "name": "documentType",
          "options": [
            "RFC",
            "CURP"
          ],
          "phone_prefix": null,
          "required": false
        },
        {
          "name": "phone",
          "options": null,
          "phone_prefix": "+52",
          "required": false
        }
      ]
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "collections:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-metodos-de-cobro/).

#### Guías

- [Cobros](/docs/recetas/cobros/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)
- [Paginación](/docs/paginacion/)

### GET /v1/collections

Cobros, con su último estado conocido

Cobros creados por esta API, con el último estado conocido, del más nuevo al más viejo.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/collections` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listCollections` |
| Acceso | Llave con el scope `collections:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].amount` | texto o `null` | Sí | Monto que paga tu cliente. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].currency` | texto o `null` | Sí | Moneda del cobro. |
| `data[].destination_amount` | texto o `null` | Sí | Monto que recibes. |
| `data[].expires_at` | texto o `null` | Sí | Vencimiento del cobro. ISO 8601 en UTC. |
| `data[].external_ref` | texto o `null` | Sí | Tu referencia. |
| `data[].id` | texto | Sí | Identificador del cobro, con prefijo `col_`. |
| `data[].instruction` | objeto o `null` | Sí | Cómo pagar. null mientras no esté lista. |
| `data[].instruction.type` | texto o `null` | Sí | Tipo de instrucción. Depende del método: trata la lista como extensible. `LINK`: `value` es una dirección que se abre para pagar. `QR`: `value` se muestra o se copia, como un QR o un texto de pago. `REFERENCE`: `value` es la referencia que quien paga da en la tienda, en un cobro `efectivo`. `clabe`: `value` es una CLABE. Sólo en sandbox. |
| `data[].instruction.value` | texto o `null` | Sí | Dato que usa quien paga. |
| `data[].metadata` | objeto | Sí | Los pares que enviaste al crear el cobro. |
| `data[].method` | texto o `null` | Sí | Método del cobro. |
| `data[].status` | texto | Sí | Estado del cobro. `processing`: El alta aún no se confirma. `pending`: Espera el pago. `paid`: Pagado. No vuelve atrás. En `efectivo`, la tienda ya confirmó el pago y el abono a tu cuenta llega después. `expired`: Venció sin pago. No vuelve atrás. `failed`: No se pudo crear o completar. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].instruction.type`

| Valor | Significado |
|---|---|
| `LINK` | `value` es una dirección que se abre para pagar. |
| `QR` | `value` se muestra o se copia, como un QR o un texto de pago. |
| `REFERENCE` | `value` es la referencia que quien paga da en la tienda, en un cobro `efectivo`. |
| `clabe` | `value` es una CLABE. Sólo en la cuenta de prueba. |

Depende del método. Trata la lista como extensible.

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma. |
| `pending` | Espera el pago. |
| `paid` | Pagado. No vuelve atrás. |
| `expired` | Venció sin pago. No vuelve atrás. |
| `failed` | No se pudo crear o completar. |

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1250.00",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "destination_amount": null,
      "expires_at": "2026-11-05T18:00:00.000Z",
      "external_ref": "pedido-7731",
      "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
      "instruction": {
        "type": "clabe",
        "value": "999180000000000022"
      },
      "metadata": {},
      "method": "spei",
      "status": "pending"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Instrucción `clabe`: sólo en sandbox.:

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1250.00",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "destination_amount": null,
      "expires_at": "2026-11-05T18:00:00.000Z",
      "external_ref": "pedido-7731",
      "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
      "instruction": {
        "type": "clabe",
        "value": "999180000000000022"
      },
      "metadata": {},
      "method": "spei",
      "status": "pending"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Instrucción `REFERENCE`: quien paga da `value` en la tienda.:

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1250.00",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "destination_amount": null,
      "expires_at": "2026-11-05T18:00:00.000Z",
      "external_ref": "pedido-7731",
      "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
      "instruction": {
        "type": "REFERENCE",
        "value": "999000000000000000000000000022"
      },
      "metadata": {},
      "method": "efectivo",
      "status": "pending"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Instrucción `LINK`: `value` se abre para pagar.:

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1250.00",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "destination_amount": null,
      "expires_at": "2026-11-05T18:00:00.000Z",
      "external_ref": "pedido-7731",
      "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
      "instruction": {
        "type": "LINK",
        "value": "https://empresa-ejemplo.com/pagar/4hQvN8sL"
      },
      "metadata": {},
      "method": "spei",
      "status": "pending"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Instrucción `QR`: `value` se muestra o se copia.:

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1250.00",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "destination_amount": null,
      "expires_at": "2026-11-05T18:00:00.000Z",
      "external_ref": "pedido-7731",
      "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
      "instruction": {
        "type": "QR",
        "value": "00020101021226580014ejemplo0212FICTICIO12345204000053034845802MX"
      },
      "metadata": {},
      "method": "spei",
      "status": "pending"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "collections:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-cobros/).

#### Guías

- [Cobros](/docs/recetas/cobros/)
- [Paginación](/docs/paginacion/)
- [Autenticación](/docs/autenticacion/)

### POST /v1/collections

Crear un cobro

Crea un cobro y devuelve la instrucción para quien paga. Exige `Idempotency-Key` y firma. Envía `amount` o `destination_amount`. 201 si quedó creado; 202 si aún no se confirma: repite con la misma clave.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/collections` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `createCollection` |
| Acceso | Llave con el scope `collections:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |

- Envía `amount`, lo que paga tu cliente, o `destination_amount`, lo que quieres recibir. Al menos uno.
- `payer` lleva los datos de quien paga, y se comprueba contra `payer_fields` de [`GET /v1/collection-methods`](/docs/operaciones/listar-metodos-de-cobro/). `countrySymbol`, en 2 letras, sólo si quien paga es de otro país que la moneda: si no, se deduce de la moneda.
- `202` significa que el alta aún no se confirma: `instruction` y `expires_at` llegan en `null`. Repite con la misma clave, o consulta el `id`.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto | No | Monto que paga tu cliente. Envía éste o `destination_amount`. |
| `currency` | texto | Sí | Moneda del cobro. |
| `description` | texto | No | Concepto del cobro, hasta 140 caracteres imprimibles. Hasta 140 caracteres. |
| `destination_amount` | texto | No | Monto que quieres recibir. Envía éste o `amount`. |
| `expires_at` | texto | No | Vencimiento deseado. Tiene que ser futuro en un cobro `efectivo`, que sin este campo vence a los 3 días. ISO 8601 en UTC. |
| `external_ref` | texto | No | Tu referencia del cobro. Vuelve en el cobro y en sus eventos. Hasta 120 caracteres. |
| `metadata` | objeto | No | Hasta 20 pares clave y valor tuyos. |
| `method` | texto | Sí | Método de `GET /v1/collection-methods`. `efectivo`, disponible bajo solicitud a FITAL, cobra en tiendas de México, en MXN: se paga con una referencia y se envía `amount`, no `destination_amount`. Hasta 40 caracteres. |
| `payer` | objeto | No | Datos de quien paga. `payer_fields` de `GET /v1/collection-methods` dice qué pide cada método: sin un campo `required`, o con un valor fuera de `options`, la respuesta es `400 invalid_request` con el campo en `details.fields`, antes de crear nada. `countrySymbol`, en 2 letras, sólo si quien paga es de otro país que la moneda. |
| `quote_id` | texto | No | Cotización previa del cobro, si la tienes. Hasta 100 caracteres. |

```json noexec="ejemplo"
{
  "currency": "MXN",
  "method": "spei",
  "amount": "1250.00",
  "payer": {
    "firstName": "CLIENTE",
    "lastName": "FICTICIO",
    "email": "pagos@empresa-ejemplo.com"
  },
  "description": "Pedido 7731",
  "external_ref": "pedido-7731"
}
```

#### Respuestas correctas

##### 201 Creado

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto que paga tu cliente. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda del cobro. |
| `destination_amount` | texto o `null` | Sí | Monto que recibes. |
| `expires_at` | texto o `null` | Sí | Vencimiento del cobro. ISO 8601 en UTC. |
| `external_ref` | texto o `null` | Sí | Tu referencia. |
| `id` | texto | Sí | Identificador del cobro, con prefijo `col_`. |
| `instruction` | objeto o `null` | Sí | Cómo pagar. null mientras no esté lista. |
| `instruction.type` | texto o `null` | Sí | Tipo de instrucción. Depende del método: trata la lista como extensible. `LINK`: `value` es una dirección que se abre para pagar. `QR`: `value` se muestra o se copia, como un QR o un texto de pago. `REFERENCE`: `value` es la referencia que quien paga da en la tienda, en un cobro `efectivo`. `clabe`: `value` es una CLABE. Sólo en sandbox. |
| `instruction.value` | texto o `null` | Sí | Dato que usa quien paga. |
| `metadata` | objeto | Sí | Los pares que enviaste al crear el cobro. |
| `method` | texto o `null` | Sí | Método del cobro. |
| `status` | texto | Sí | Estado del cobro. `processing`: El alta aún no se confirma. `pending`: Espera el pago. `paid`: Pagado. No vuelve atrás. En `efectivo`, la tienda ya confirmó el pago y el abono a tu cuenta llega después. `expired`: Venció sin pago. No vuelve atrás. `failed`: No se pudo crear o completar. |

###### Valores de `instruction.type`

| Valor | Significado |
|---|---|
| `LINK` | `value` es una dirección que se abre para pagar. |
| `QR` | `value` se muestra o se copia, como un QR o un texto de pago. |
| `REFERENCE` | `value` es la referencia que quien paga da en la tienda, en un cobro `efectivo`. |
| `clabe` | `value` es una CLABE. Sólo en la cuenta de prueba. |

Depende del método. Trata la lista como extensible.

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma. |
| `pending` | Espera el pago. |
| `paid` | Pagado. No vuelve atrás. |
| `expired` | Venció sin pago. No vuelve atrás. |
| `failed` | No se pudo crear o completar. |

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "clabe",
    "value": "999180000000000022"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Instrucción `clabe`: sólo en sandbox.:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "clabe",
    "value": "999180000000000022"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Instrucción `REFERENCE`: quien paga da `value` en la tienda.:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "REFERENCE",
    "value": "999000000000000000000000000022"
  },
  "metadata": {},
  "method": "efectivo",
  "status": "pending"
}
```

Instrucción `LINK`: `value` se abre para pagar:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "LINK",
    "value": "https://empresa-ejemplo.com/pagar/4hQvN8sL"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Instrucción `QR`: `value` se muestra o se copia:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "QR",
    "value": "00020101021226580014ejemplo0212FICTICIO12345204000053034845802MX"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

##### 202 Aceptado: el resultado final llega después

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": null,
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": null,
  "metadata": {},
  "method": "spei",
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Location` | Ruta del recurso, con `201` y `202`. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Falta `amount` o `destination_amount`, o algún campo no cumple el esquema. También sin un campo `required` de `payer_fields`, o con un valor fuera de `options`: `details.fields` lo nombra como `payer.<campo>`. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`account_restricted`](/docs/errores/#account_restricted) | La cuenta tiene una restricción. | `false` |
| 403 | [`not_enabled`](/docs/errores/#not_enabled) | El método no está activado para tu empresa. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 409 | [`collection_not_ready`](/docs/errores/#collection_not_ready) | El cobro aún no se puede crear porque falta una revisión de tu cuenta. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`rail_unavailable`](/docs/errores/#rail_unavailable) | El país del método no está disponible por ahora. | `true` |
| 422 | [`in_review`](/docs/errores/#in_review) | Una revisión de tu cuenta frena la operación. | `true` |
| 422 | [`method_unavailable`](/docs/errores/#method_unavailable) | El método no está disponible. | `false` |
| 422 | [`amount_out_of_range`](/docs/errores/#amount_out_of_range) | El monto está fuera del rango del método. | `false` |
| 422 | [`collection_rejected`](/docs/errores/#collection_rejected) | El cobro se rechazó. | `false` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 429 | [`sandbox_limit`](/docs/errores/#sandbox_limit) (sólo cuenta de prueba) | Alcanzaste 100 pagos y cobros de hoy. | `false` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 account_restricted`:

```json noexec="ejemplo"
{
  "error": {
    "code": "account_restricted",
    "message": "Tu cuenta tiene una restricción. Contacta a tu ejecutivo FITAL.",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#account_restricted"
  }
}
```

`409 collection_not_ready`:

```json noexec="ejemplo"
{
  "error": {
    "code": "collection_not_ready",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#collection_not_ready"
  }
}
```

`413 invalid_request`:

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

`422 rail_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rail_unavailable",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rail_unavailable"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout, repite con la misma clave. Un `400` por `payer` no crea nada, pero la clave conserva ese cuerpo: el cuerpo corregido necesita otra `Idempotency-Key`. Con la misma responde `422 idempotency_key_reused`.

- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `422 rail_unavailable`: Repite más tarde.
- `422 in_review`: Mira `retryable`. Con `true`, repite con la misma clave cuando la revisión se levante. Con `false`, el rechazo quedó registrado y esa clave lo repite: usa una clave nueva tras la revisión.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/crear-cobro/).

#### Guías

- [Cobros](/docs/recetas/cobros/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Idempotencia](/docs/idempotencia/)
- [Lista de migración](/docs/migracion/)

### POST /v1/collections/estimate

Estimar un cobro

Estima montos y comisiones de un cobro antes de crearlo. Es indicativa y no crea nada, por eso no pide `Idempotency-Key`. Va firmada como las demás escrituras de cobros.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/collections/estimate` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `estimateCollection` |
| Acceso | Llave con el scope `collections:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | No aplica |

- Es indicativa y no crea nada: por eso no pide `Idempotency-Key`. Firma con la última línea vacía. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición).

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto | Sí | Monto a estimar. |
| `currency` | texto | Sí | Moneda del cobro. |
| `method` | texto | Sí | Método de cobro. Hasta 40 caracteres. |

```json noexec="ejemplo"
{
  "currency": "MXN",
  "method": "spei",
  "amount": "1250.00"
}
```

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto estimado. |
| `currency` | texto o `null` | Sí | Moneda del monto. |
| `exchange_rate` | texto o `null` | Sí | Tipo de cambio aplicado, si hay conversión. |
| `expires_at` | texto o `null` | Sí | Vigencia de la estimación. ISO 8601 en UTC. |
| `fee` | texto o `null` | Sí | Comisión. |
| `fee_currency` | texto o `null` | Sí | Moneda de la comisión. |
| `indicative` | booleano | Sí | Siempre true: es una estimación. |
| `max_amount` | texto o `null` | Sí | Monto máximo del método. |
| `min_amount` | texto o `null` | Sí | Monto mínimo del método. |
| `plan_fee_pct` | texto o `null` | Sí | Comisión de tu plan, en porcentaje. |
| `receive_amount` | texto o `null` | Sí | Lo que recibirías. null en `efectivo`: se fija al conciliar el pago. |
| `receive_currency` | texto o `null` | Sí | Moneda de lo que recibirías. |
| `total_cost_pct` | texto o `null` | Sí | Costo total, en porcentaje. |

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "currency": "MXN",
  "exchange_rate": null,
  "expires_at": "2026-11-04T18:05:00.000Z",
  "fee": "25.00",
  "fee_currency": "MXN",
  "indicative": true,
  "max_amount": "50000.00",
  "min_amount": "10.00",
  "plan_fee_pct": "2.00",
  "receive_amount": "1225.00",
  "receive_currency": "MXN",
  "total_cost_pct": "2.00"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`method_unavailable`](/docs/errores/#method_unavailable) | El método no está disponible. | `false` |
| 422 | [`amount_out_of_range`](/docs/errores/#amount_out_of_range) | El monto está fuera del rango del método. | `false` |
| 422 | [`collection_rejected`](/docs/errores/#collection_rejected) | La estimación se rechazó. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "collections:write"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`413 invalid_request`:

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

`422 method_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "method_unavailable",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#method_unavailable"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

No lleva clave de idempotencia y no mueve dinero: repetirla es seguro.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/estimar-cobro/).

#### Guías

- [Cobros](/docs/recetas/cobros/)
- [Autenticación](/docs/autenticacion/)

### GET /v1/collections/{id}

Un cobro

Un cobro de tu empresa, con su estado actual. `paid` y `expired` no vuelven atrás.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/collections/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getCollection` |
| Acceso | Llave con el scope `collections:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto que paga tu cliente. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda del cobro. |
| `destination_amount` | texto o `null` | Sí | Monto que recibes. |
| `expires_at` | texto o `null` | Sí | Vencimiento del cobro. ISO 8601 en UTC. |
| `external_ref` | texto o `null` | Sí | Tu referencia. |
| `id` | texto | Sí | Identificador del cobro, con prefijo `col_`. |
| `instruction` | objeto o `null` | Sí | Cómo pagar. null mientras no esté lista. |
| `instruction.type` | texto o `null` | Sí | Tipo de instrucción. Depende del método: trata la lista como extensible. `LINK`: `value` es una dirección que se abre para pagar. `QR`: `value` se muestra o se copia, como un QR o un texto de pago. `REFERENCE`: `value` es la referencia que quien paga da en la tienda, en un cobro `efectivo`. `clabe`: `value` es una CLABE. Sólo en sandbox. |
| `instruction.value` | texto o `null` | Sí | Dato que usa quien paga. |
| `metadata` | objeto | Sí | Los pares que enviaste al crear el cobro. |
| `method` | texto o `null` | Sí | Método del cobro. |
| `status` | texto | Sí | Estado del cobro. `processing`: El alta aún no se confirma. `pending`: Espera el pago. `paid`: Pagado. No vuelve atrás. En `efectivo`, la tienda ya confirmó el pago y el abono a tu cuenta llega después. `expired`: Venció sin pago. No vuelve atrás. `failed`: No se pudo crear o completar. |

###### Valores de `instruction.type`

| Valor | Significado |
|---|---|
| `LINK` | `value` es una dirección que se abre para pagar. |
| `QR` | `value` se muestra o se copia, como un QR o un texto de pago. |
| `REFERENCE` | `value` es la referencia que quien paga da en la tienda, en un cobro `efectivo`. |
| `clabe` | `value` es una CLABE. Sólo en la cuenta de prueba. |

Depende del método. Trata la lista como extensible.

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma. |
| `pending` | Espera el pago. |
| `paid` | Pagado. No vuelve atrás. |
| `expired` | Venció sin pago. No vuelve atrás. |
| `failed` | No se pudo crear o completar. |

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "clabe",
    "value": "999180000000000022"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Instrucción `clabe`: sólo en sandbox.:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "clabe",
    "value": "999180000000000022"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Instrucción `REFERENCE`: quien paga da `value` en la tienda.:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "REFERENCE",
    "value": "999000000000000000000000000022"
  },
  "metadata": {},
  "method": "efectivo",
  "status": "pending"
}
```

Instrucción `LINK`: `value` se abre para pagar:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "LINK",
    "value": "https://empresa-ejemplo.com/pagar/4hQvN8sL"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Instrucción `QR`: `value` se muestra o se copia:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "QR",
    "value": "00020101021226580014ejemplo0212FICTICIO12345204000053034845802MX"
  },
  "metadata": {},
  "method": "spei",
  "status": "pending"
}
```

Pagado:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "clabe",
    "value": "999180000000000022"
  },
  "metadata": {},
  "method": "spei",
  "status": "paid"
}
```

Venció sin pago:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": "2026-11-05T18:00:00.000Z",
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": {
    "type": "clabe",
    "value": "999180000000000022"
  },
  "metadata": {},
  "method": "spei",
  "status": "expired"
}
```

Alta sin confirmar:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_amount": null,
  "expires_at": null,
  "external_ref": "pedido-7731",
  "id": "col_4hQvN8sLd1ZxK6tB2mYpR7cW",
  "instruction": null,
  "metadata": {},
  "method": "spei",
  "status": "processing"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El cobro no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "collections:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-cobro/).

#### Guías

- [Cobros](/docs/recetas/cobros/)
- [Ambientes](/docs/ambientes/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)

## Depósitos

### GET /v1/deposit-addresses

Direcciones de depósito

Direcciones de depósito de la empresa. Paginación por cursor.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/deposit-addresses` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listDepositAddresses` |
| Acceso | Llave con el scope `deposit_addresses:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].address` | texto o `null` | Sí | Dirección donde depositar. null mientras esté en `processing`. |
| `data[].chain` | texto o `null` | Sí | Red de la dirección, en minúsculas. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].currency` | texto o `null` | Sí | Token que acepta, en mayúsculas. |
| `data[].id` | texto | Sí | Identificador de la dirección, con prefijo `dep_`. |
| `data[].status` | texto | Sí | Estado de la dirección. `processing`: El alta aún no se confirma y los demás campos llegan en null. `active`: Ya recibe depósitos. |
| `data[].warning` | texto o `null` | Sí | Advertencia de uso. Muéstrala a quien vaya a depositar: dice también si en esa red un depósito rechazado no regresa automáticamente. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma y los demás campos llegan en `null`. |
| `active` | Ya recibe depósitos. |

```json noexec="ejemplo"
{
  "data": [
    {
      "address": "0x0000000000000000000000000000000000000000",
      "chain": "polygon",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "USDC",
      "id": "dep_9vLsK3mNc7QxB1tZ5hYdR2fP",
      "status": "active",
      "warning": "Envía sólo USDC por la red polygon. Otro token u otra red puede no acreditarse."
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "deposit_addresses:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-direcciones-de-deposito/).

#### Guías

- [Direcciones de depósito](/docs/recetas/direcciones-deposito/)
- [Paginación](/docs/paginacion/)
- [Autenticación](/docs/autenticacion/)

### POST /v1/deposit-addresses

Crear una dirección de depósito

Crea una dirección de depósito para una red y un token. Exige `Idempotency-Key` y firma. 201 si quedó activa; 202 si aún no se confirma: repite con la misma clave.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /v1/deposit-addresses` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `createDepositAddress` |
| Acceso | Llave con el scope `deposit_addresses:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |

- `202` significa que el alta aún no se confirma: `address` llega en `null`. No la compartas. Repite con la misma clave, o consulta el `id`.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `chain` | texto | Sí | Red de la dirección, por ejemplo `polygon`. No distingue mayúsculas. Hasta 30 caracteres. |
| `currency` | texto | Sí | Token que recibirá, por ejemplo `USDC`. No distingue mayúsculas. Hasta 10 caracteres. |

```json noexec="ejemplo"
{
  "chain": "polygon",
  "currency": "USDC"
}
```

#### Respuestas correctas

##### 201 Creado

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `address` | texto o `null` | Sí | Dirección donde depositar. null mientras esté en `processing`. |
| `chain` | texto o `null` | Sí | Red de la dirección, en minúsculas. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Token que acepta, en mayúsculas. |
| `id` | texto | Sí | Identificador de la dirección, con prefijo `dep_`. |
| `status` | texto | Sí | Estado de la dirección. `processing`: El alta aún no se confirma y los demás campos llegan en null. `active`: Ya recibe depósitos. |
| `warning` | texto o `null` | Sí | Advertencia de uso. Muéstrala a quien vaya a depositar: dice también si en esa red un depósito rechazado no regresa automáticamente. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma y los demás campos llegan en `null`. |
| `active` | Ya recibe depósitos. |

```json noexec="ejemplo"
{
  "address": "0x0000000000000000000000000000000000000000",
  "chain": "polygon",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "USDC",
  "id": "dep_9vLsK3mNc7QxB1tZ5hYdR2fP",
  "status": "active",
  "warning": "Envía sólo USDC por la red polygon. Otro token u otra red puede no acreditarse."
}
```

##### 202 Aceptado: el resultado final llega después

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "address": null,
  "chain": null,
  "created_at": null,
  "currency": null,
  "id": "dep_9vLsK3mNc7QxB1tZ5hYdR2fP",
  "status": "processing",
  "warning": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Location` | Ruta del recurso, con `201` y `202`. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`account_restricted`](/docs/errores/#account_restricted) | La cuenta tiene una restricción. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`in_review`](/docs/errores/#in_review) | Una revisión de tu cuenta frena la operación. | `true` |
| 422 | [`pair_not_supported`](/docs/errores/#pair_not_supported) | Esa combinación de `chain` y `currency` no está disponible. Ver [Cobertura](/docs/cobertura/#redes-y-tokens). | `false` |
| 422 | [`deposit_address_rejected`](/docs/errores/#deposit_address_rejected) | La dirección se rechazó. | `false` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 account_restricted`:

```json noexec="ejemplo"
{
  "error": {
    "code": "account_restricted",
    "message": "Tu cuenta tiene una restricción. Contacta a tu ejecutivo FITAL.",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#account_restricted"
  }
}
```

`409 idempotency_in_progress`:

```json noexec="ejemplo"
{
  "error": {
    "code": "idempotency_in_progress",
    "message": "Operación en curso",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#idempotency_in_progress"
  }
}
```

`413 invalid_request`:

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

`422 in_review`:

```json noexec="ejemplo"
{
  "error": {
    "code": "in_review",
    "message": "La operación está en revisión",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#in_review"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout o una conexión cortada, repite con la misma clave.

- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `422 in_review`: Mira `retryable`. Con `true`, repite con la misma clave cuando la revisión se levante. Con `false`, el rechazo quedó registrado y esa clave lo repite: usa una clave nueva tras la revisión.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/crear-direccion-de-deposito/).

#### Guías

- [Direcciones de depósito](/docs/recetas/direcciones-deposito/)
- [Cobertura](/docs/cobertura/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)

### GET /v1/deposit-addresses/{id}

Una dirección de depósito

Una dirección de depósito de tu empresa. Si el alta quedó sin confirmar responde `processing`, con los datos en null.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/deposit-addresses/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getDepositAddress` |
| Acceso | Llave con el scope `deposit_addresses:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `address` | texto o `null` | Sí | Dirección donde depositar. null mientras esté en `processing`. |
| `chain` | texto o `null` | Sí | Red de la dirección, en minúsculas. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Token que acepta, en mayúsculas. |
| `id` | texto | Sí | Identificador de la dirección, con prefijo `dep_`. |
| `status` | texto | Sí | Estado de la dirección. `processing`: El alta aún no se confirma y los demás campos llegan en null. `active`: Ya recibe depósitos. |
| `warning` | texto o `null` | Sí | Advertencia de uso. Muéstrala a quien vaya a depositar: dice también si en esa red un depósito rechazado no regresa automáticamente. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | El alta aún no se confirma y los demás campos llegan en `null`. |
| `active` | Ya recibe depósitos. |

```json noexec="ejemplo"
{
  "address": "0x0000000000000000000000000000000000000000",
  "chain": "polygon",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "USDC",
  "id": "dep_9vLsK3mNc7QxB1tZ5hYdR2fP",
  "status": "active",
  "warning": "Envía sólo USDC por la red polygon. Otro token u otra red puede no acreditarse."
}
```

Alta sin confirmar:

```json noexec="ejemplo"
{
  "address": null,
  "chain": null,
  "created_at": null,
  "currency": null,
  "id": "dep_9vLsK3mNc7QxB1tZ5hYdR2fP",
  "status": "processing",
  "warning": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | La dirección no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "deposit_addresses:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-direccion-de-deposito/).

#### Guías

- [Direcciones de depósito](/docs/recetas/direcciones-deposito/)
- [Autenticación](/docs/autenticacion/)

### GET /v1/deposits

Depósitos recibidos y quién pagó

Depósitos recibidos y quién pagó, del más nuevo al más viejo. `payer` llega en null si no hay datos legibles.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/deposits` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listDeposits` |
| Acceso | Llave con el scope `deposits:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].amount` | texto o `null` | Sí | Monto recibido. |
| `data[].concept` | texto o `null` | Sí | Concepto que escribió quien pagó. |
| `data[].credited_amount` | texto o `null` | Sí | Monto acreditado a tu saldo. |
| `data[].credited_currency` | texto o `null` | Sí | Moneda acreditada. |
| `data[].currency` | texto o `null` | Sí | Moneda recibida. |
| `data[].id` | texto | Sí | Identificador del depósito, con prefijo `dpt_`. |
| `data[].payer` | objeto o `null` | Sí | Quién pagó. null si no hay datos legibles: nunca se inventa. |
| `data[].payer.address` | texto o `null` | Sí | Dirección de origen. null si no se conoce. |
| `data[].payer.bank` | texto o `null` | Sí | Banco de origen. null si no se conoce. |
| `data[].payer.clabe` | texto o `null` | Sí | CLABE de origen. null si no se conoce. |
| `data[].payer.name` | texto o `null` | Sí | Nombre de quien pagó. null si no se conoce. |
| `data[].payer.rfc` | texto o `null` | Sí | RFC de quien pagó. null si no se conoce. |
| `data[].receipt_url` | texto o `null` | Sí | Enlace https al comprobante, si existe. |
| `data[].received_at` | texto o `null` | Sí | Fecha del depósito. ISO 8601 en UTC. |
| `data[].reference` | texto o `null` | Sí | Referencia del depósito. |
| `data[].status` | texto | Sí | Estado del depósito. `in_review`: El depósito está en revisión. Aún no es dinero disponible. `received`: El dinero ya está acreditado. `returned`: El depósito se devolvió. |
| `data[].tracking_key` | texto o `null` | Sí | Clave de rastreo del banco. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `in_review` | El depósito está en revisión. Aún no es dinero disponible. |
| `received` | El dinero ya está acreditado. |
| `returned` | El depósito se devolvió. |

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1250.00",
      "concept": "Pedido 7731",
      "credited_amount": "1250.00",
      "credited_currency": "MXN",
      "currency": "MXN",
      "id": "dpt_7bTcF2qXe9WmL4sD1nHrY6vK",
      "payer": {
        "address": null,
        "bank": "Banco de ejemplo",
        "clabe": "999180000000000022",
        "name": "CLIENTE FICTICIO SA DE CV",
        "rfc": "XAXX010101000"
      },
      "receipt_url": null,
      "received_at": "2026-11-04T18:00:00.000Z",
      "reference": "7731",
      "status": "received",
      "tracking_key": "CLAVE-DE-RASTREO-DEMO"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "deposits:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-depositos/).

#### Guías

- [Quién pagó](/docs/recetas/quien-pago/)
- [Conciliar](/docs/recetas/conciliar/)
- [Paginación](/docs/paginacion/)
- [Ambientes](/docs/ambientes/)
- [Autenticación](/docs/autenticacion/)

### GET /v1/deposits/{id}

Un depósito

Un depósito de tu empresa. El id va sellado por empresa: el de otra da 404.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/deposits/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getDeposit` |
| Acceso | Llave con el scope `deposits:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto recibido. |
| `concept` | texto o `null` | Sí | Concepto que escribió quien pagó. |
| `credited_amount` | texto o `null` | Sí | Monto acreditado a tu saldo. |
| `credited_currency` | texto o `null` | Sí | Moneda acreditada. |
| `currency` | texto o `null` | Sí | Moneda recibida. |
| `id` | texto | Sí | Identificador del depósito, con prefijo `dpt_`. |
| `payer` | objeto o `null` | Sí | Quién pagó. null si no hay datos legibles: nunca se inventa. |
| `payer.address` | texto o `null` | Sí | Dirección de origen. null si no se conoce. |
| `payer.bank` | texto o `null` | Sí | Banco de origen. null si no se conoce. |
| `payer.clabe` | texto o `null` | Sí | CLABE de origen. null si no se conoce. |
| `payer.name` | texto o `null` | Sí | Nombre de quien pagó. null si no se conoce. |
| `payer.rfc` | texto o `null` | Sí | RFC de quien pagó. null si no se conoce. |
| `receipt_url` | texto o `null` | Sí | Enlace https al comprobante, si existe. |
| `received_at` | texto o `null` | Sí | Fecha del depósito. ISO 8601 en UTC. |
| `reference` | texto o `null` | Sí | Referencia del depósito. |
| `status` | texto | Sí | Estado del depósito. `in_review`: El depósito está en revisión. Aún no es dinero disponible. `received`: El dinero ya está acreditado. `returned`: El depósito se devolvió. |
| `tracking_key` | texto o `null` | Sí | Clave de rastreo del banco. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `in_review` | El depósito está en revisión. Aún no es dinero disponible. |
| `received` | El dinero ya está acreditado. |
| `returned` | El depósito se devolvió. |

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "concept": "Pedido 7731",
  "credited_amount": "1250.00",
  "credited_currency": "MXN",
  "currency": "MXN",
  "id": "dpt_7bTcF2qXe9WmL4sD1nHrY6vK",
  "payer": {
    "address": null,
    "bank": "Banco de ejemplo",
    "clabe": "999180000000000022",
    "name": "CLIENTE FICTICIO SA DE CV",
    "rfc": "XAXX010101000"
  },
  "receipt_url": null,
  "received_at": "2026-11-04T18:00:00.000Z",
  "reference": "7731",
  "status": "received",
  "tracking_key": "CLAVE-DE-RASTREO-DEMO"
}
```

En revisión: aún no es dinero disponible:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "concept": "Pedido 7731",
  "credited_amount": null,
  "credited_currency": null,
  "currency": "MXN",
  "id": "dpt_7bTcF2qXe9WmL4sD1nHrY6vK",
  "payer": {
    "address": null,
    "bank": "Banco de ejemplo",
    "clabe": "999180000000000022",
    "name": "CLIENTE FICTICIO SA DE CV",
    "rfc": "XAXX010101000"
  },
  "receipt_url": null,
  "received_at": "2026-11-04T18:00:00.000Z",
  "reference": "7731",
  "status": "in_review",
  "tracking_key": "CLAVE-DE-RASTREO-DEMO"
}
```

Sin datos legibles del pagador:

```json noexec="ejemplo"
{
  "amount": "1250.00",
  "concept": "Pedido 7731",
  "credited_amount": "1250.00",
  "credited_currency": "MXN",
  "currency": "MXN",
  "id": "dpt_7bTcF2qXe9WmL4sD1nHrY6vK",
  "payer": null,
  "receipt_url": null,
  "received_at": "2026-11-04T18:00:00.000Z",
  "reference": "7731",
  "status": "received",
  "tracking_key": "CLAVE-DE-RASTREO-DEMO"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El depósito no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "deposits:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-deposito/).

#### Guías

- [Quién pagó](/docs/recetas/quien-pago/)
- [Autenticación](/docs/autenticacion/)

## Movimientos

### GET /v1/movements

Movimientos de la cuenta

Movimientos de la cuenta, de esta API y de otros orígenes, en el orden en que cambiaron. `payment_id` y `external_ref` dicen de qué pago sale cada uno. Al llegar al final `has_more` es `false` y `next_cursor` sigue presente: guárdalo y úsalo después para recibir sólo lo nuevo. Ese recorrido siguiente empieza en la hora en que abrió el anterior y repasa 2 minutos más, para no perder lo que confirmó tarde, y una fila que cambia mientras recorres vuelve a salir. Por eso un mismo id puede repetirse: quédate con la copia de `updated_at` más reciente. Con `from` y `to`, `from` debe ser anterior.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/movements` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listMovements` |
| Acceso | Llave con el scope `movements:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Una fila que cambia mientras recorres puede repetirse, y nunca se omite otra. Deduplica por `id`. `next_cursor` puede venir con valor aunque `has_more` sea `false`: guárdalo. Ver [Paginación](/docs/paginacion/#movimientos-un-cursor-que-no-termina).

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |
| `from` | consulta | texto | No | Desde esta fecha de creación, incluida. ISO 8601 en UTC. |
| `to` | consulta | texto | No | Hasta esta fecha de creación, sin incluirla. ISO 8601 en UTC. |
| `type` | consulta | texto | No | Filtra por categoría. `payment`: Salida por un riel de pago. `deposit`: Entrada de dinero. `crypto`: Envío o recepción de un token. `internal`: Movimiento interno de la cuenta. `card`: Operación con tarjeta. `other`: Cualquier otro movimiento. |
| `currency` | consulta | texto | No | Filtra por cualquiera de las monedas del movimiento. |

##### Valores de `type`

| Valor | Significado |
|---|---|
| `payment` | Salida por un riel de pago. |
| `deposit` | Entrada de dinero. |
| `crypto` | Envío o recepción de un token. |
| `internal` | Movimiento interno de la cuenta. |
| `card` | Operación con tarjeta. |
| `other` | Cualquier otro movimiento. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].amount` | texto o `null` | Sí | Monto del movimiento. |
| `data[].completed_at` | texto o `null` | Sí | Fecha en que terminó. ISO 8601 en UTC. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].currency` | texto o `null` | Sí | Moneda del monto. |
| `data[].destination_currency` | texto o `null` | Sí | Moneda de destino. |
| `data[].external_ref` | texto o `null` | Sí | Tu `external_ref` del pago o cobro del que sale este movimiento. null si no tiene. |
| `data[].final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `data[].final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `data[].id` | texto | Sí | Identificador del movimiento, con prefijo `mov_`. |
| `data[].payment_id` | texto o `null` | Sí | Pago de esta API del que sale este movimiento, con prefijo `pay_`. null si no salió de un pago creado por esta API. |
| `data[].read_status` | texto | Sí | Cuánto se pudo leer. `complete`: Se leyó el detalle completo. `partial`: No se pudo leer el detalle completo. El estado no avanza con una lectura parcial. |
| `data[].status` | texto | Sí | Estado público. Sólo `completed`, `failed`, `returned` y `canceled` son terminales. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |
| `data[].type` | texto | Sí | Categoría del movimiento. `payment`: Salida por un riel de pago. `deposit`: Entrada de dinero. `crypto`: Envío o recepción de un token. `internal`: Movimiento interno de la cuenta. `card`: Operación con tarjeta. `other`: Cualquier otro movimiento. |
| `data[].updated_at` | texto o `null` | Sí | Última actualización. ISO 8601 en UTC. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].read_status`

| Valor | Significado |
|---|---|
| `complete` | Se leyó el detalle completo. |
| `partial` | No se pudo leer el detalle completo. El estado no avanza con una lectura parcial. |

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

###### Valores de `data[].type`

| Valor | Significado |
|---|---|
| `payment` | Salida por un riel de pago. |
| `deposit` | Entrada de dinero. |
| `crypto` | Envío o recepción de un token. |
| `internal` | Movimiento interno de la cuenta. |
| `card` | Operación con tarjeta. |
| `other` | Cualquier otro movimiento. |

```json noexec="ejemplo"
{
  "data": [
    {
      "amount": "1500.00",
      "completed_at": "2026-11-04T18:01:30.000Z",
      "created_at": "2026-11-04T18:00:00.000Z",
      "currency": "MXN",
      "destination_currency": "MXN",
      "external_ref": "pedido-7731",
      "final_amount": "1500.00",
      "final_currency": "MXN",
      "id": "mov_2hKsV9tBq4XzM7nD1cYpL6rF",
      "payment_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
      "read_status": "complete",
      "status": "completed",
      "type": "payment",
      "updated_at": "2026-11-04T18:01:30.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún filtro no cumple el esquema, `from` no es anterior a `to`, o el cursor es inválido o de otra consulta. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "movements:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-movimientos/).

#### Guías

- [Movimientos y recibos](/docs/recetas/movimientos-y-recibos/)
- [Conciliar](/docs/recetas/conciliar/)
- [Paginación](/docs/paginacion/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)
- [Montos y fechas](/docs/montos-y-fechas/)

### GET /v1/movements/{id}

Un movimiento

Un movimiento de tu empresa. El id va sellado por empresa: el de otra da 404.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/movements/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getMovement` |
| Acceso | Llave con el scope `movements:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto del movimiento. |
| `completed_at` | texto o `null` | Sí | Fecha en que terminó. ISO 8601 en UTC. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda del monto. |
| `destination_currency` | texto o `null` | Sí | Moneda de destino. |
| `external_ref` | texto o `null` | Sí | Tu `external_ref` del pago o cobro del que sale este movimiento. null si no tiene. |
| `final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `id` | texto | Sí | Identificador del movimiento, con prefijo `mov_`. |
| `payment_id` | texto o `null` | Sí | Pago de esta API del que sale este movimiento, con prefijo `pay_`. null si no salió de un pago creado por esta API. |
| `read_status` | texto | Sí | Cuánto se pudo leer. `complete`: Se leyó el detalle completo. `partial`: No se pudo leer el detalle completo. El estado no avanza con una lectura parcial. |
| `status` | texto | Sí | Estado público. Sólo `completed`, `failed`, `returned` y `canceled` son terminales. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |
| `type` | texto | Sí | Categoría del movimiento. `payment`: Salida por un riel de pago. `deposit`: Entrada de dinero. `crypto`: Envío o recepción de un token. `internal`: Movimiento interno de la cuenta. `card`: Operación con tarjeta. `other`: Cualquier otro movimiento. |
| `updated_at` | texto o `null` | Sí | Última actualización. ISO 8601 en UTC. |

###### Valores de `read_status`

| Valor | Significado |
|---|---|
| `complete` | Se leyó el detalle completo. |
| `partial` | No se pudo leer el detalle completo. El estado no avanza con una lectura parcial. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

###### Valores de `type`

| Valor | Significado |
|---|---|
| `payment` | Salida por un riel de pago. |
| `deposit` | Entrada de dinero. |
| `crypto` | Envío o recepción de un token. |
| `internal` | Movimiento interno de la cuenta. |
| `card` | Operación con tarjeta. |
| `other` | Cualquier otro movimiento. |

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "completed_at": "2026-11-04T18:01:30.000Z",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_currency": "MXN",
  "external_ref": "pedido-7731",
  "final_amount": "1500.00",
  "final_currency": "MXN",
  "id": "mov_2hKsV9tBq4XzM7nD1cYpL6rF",
  "payment_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "read_status": "complete",
  "status": "completed",
  "type": "payment",
  "updated_at": "2026-11-04T18:01:30.000Z"
}
```

Lectura parcial: el estado no avanza:

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "completed_at": null,
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "destination_currency": "MXN",
  "external_ref": "pedido-7731",
  "final_amount": null,
  "final_currency": null,
  "id": "mov_2hKsV9tBq4XzM7nD1cYpL6rF",
  "payment_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "read_status": "partial",
  "status": "processing",
  "type": "payment",
  "updated_at": "2026-11-04T18:01:30.000Z"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El movimiento no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "movements:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-movimiento/).

#### Guías

- [Movimientos y recibos](/docs/recetas/movimientos-y-recibos/)
- [Autenticación](/docs/autenticacion/)

### GET /v1/movements/{id}/receipt

Recibo de un movimiento terminal

Recibo de un movimiento terminal. Con `Accept: application/json` responde datos; si no, un PDF. Antes de terminar da `409 receipt_not_available`.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/movements/{id}/receipt` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getMovementReceipt` |
| Acceso | Llave con el scope `movements:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Con `Accept: application/json` responde datos. Con cualquier otro valor responde `application/pdf`.
- `completed_at` es `null` si el movimiento no terminó en `completed`: un recibo de `failed`, `returned` o `canceled` no trae fecha de completado.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |
| `Accept` | No | `application/json` para recibir datos. Sin ese valor, responde un PDF. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto del movimiento. |
| `bank` | texto o `null` | Sí | Banco de destino. |
| `beneficiary` | texto o `null` | Sí | Quién recibió. |
| `completed_at` | texto o `null` | Sí | Fecha en que terminó. ISO 8601 en UTC. |
| `concept` | texto o `null` | Sí | Concepto. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda del monto. |
| `external_ref` | texto o `null` | Sí | Tu `external_ref` del pago o cobro del que sale este recibo. null si no tiene. |
| `final_amount` | texto o `null` | Sí | Monto final. |
| `final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `id` | texto | Sí | Identificador del movimiento. |
| `payment_id` | texto o `null` | Sí | Pago de esta API del que sale este recibo, con prefijo `pay_`. null si no salió de un pago creado por esta API. |
| `status` | texto | Sí | Estado público. Sólo `completed`, `failed`, `returned` y `canceled` son terminales. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |
| `type` | texto | Sí | Categoría del movimiento. `payment`: Salida por un riel de pago. `deposit`: Entrada de dinero. `crypto`: Envío o recepción de un token. `internal`: Movimiento interno de la cuenta. `card`: Operación con tarjeta. `other`: Cualquier otro movimiento. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

###### Valores de `type`

| Valor | Significado |
|---|---|
| `payment` | Salida por un riel de pago. |
| `deposit` | Entrada de dinero. |
| `crypto` | Envío o recepción de un token. |
| `internal` | Movimiento interno de la cuenta. |
| `card` | Operación con tarjeta. |
| `other` | Cualquier otro movimiento. |

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "bank": "Banco de ejemplo",
  "beneficiary": "PROVEEDOR FICTICIO SA DE CV",
  "completed_at": "2026-11-04T18:01:30.000Z",
  "concept": "Factura 1042",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "final_amount": "1500.00",
  "final_currency": "MXN",
  "id": "mov_2hKsV9tBq4XzM7nD1cYpL6rF",
  "payment_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "completed",
  "type": "payment"
}
```

`failed`: `completed_at` en `null`:

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "bank": "Banco de ejemplo",
  "beneficiary": "PROVEEDOR FICTICIO SA DE CV",
  "completed_at": null,
  "concept": "Factura 1042",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "final_amount": null,
  "final_currency": null,
  "id": "mov_2hKsV9tBq4XzM7nD1cYpL6rF",
  "payment_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "status": "failed",
  "type": "payment"
}
```

Sin `Accept: application/json`, responde estos mismos datos como `application/pdf`.

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El movimiento no existe o es de otra empresa. | `false` |
| 409 | [`receipt_not_available`](/docs/errores/#receipt_not_available) | El movimiento aún no es terminal, o no tiene recibo. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "movements:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`409 receipt_not_available`:

```json noexec="ejemplo"
{
  "error": {
    "code": "receipt_not_available",
    "message": "El movimiento aún no tiene recibo",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#receipt_not_available"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/descargar-recibo/).

#### Guías

- [Movimientos y recibos](/docs/recetas/movimientos-y-recibos/)
- [Autenticación](/docs/autenticacion/)

## Eventos

### GET /v1/events

Eventos de la empresa

Eventos de la empresa, del más nuevo al más viejo. Sirve para recuperar lo que un webhook no entregó: pide sin cursor y recorre hasta un evento que ya procesaste. `next_cursor` sólo camina hacia el pasado y termina en null, así que no lo guardes entre consultas. Filtra con `type`.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/events` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listEvents` |
| Acceso | Llave con el scope `events:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Va del más nuevo al más viejo, así que `next_cursor` sólo camina hacia el pasado y termina en `null`. Para ver lo nuevo, pide la lista sin `cursor` y recorre hasta un evento que ya procesaste. Ver [El evento siempre queda guardado](/docs/webhooks/#el-evento-siempre-queda-guardado).

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `limit` | consulta | texto | No | Filas por página, de 1 a 100. Por omisión, 25. |
| `cursor` | consulta | texto | No | Valor de `next_cursor` de la página anterior. Hasta 1000 caracteres. |
| `type` | consulta | texto | No | Filtra por tipo de evento, por ejemplo `payment.completed`. Hasta 60 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Filas de esta página. |
| `data[].api_version` | texto | Sí | Versión con que se generó el evento. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].data` | objeto | Sí | Contenido del evento. Su forma depende de `type`. |
| `data[].id` | texto | Sí | Identificador del evento, con prefijo `evt_`. Va firmado: úsalo para ignorar repetidos. |
| `data[].resource_id` | texto | Sí | Recurso al que se refiere. |
| `data[].sequence` | entero | Sí | Orden dentro del recurso: un valor mayor es más reciente. |
| `data[].type` | texto | Sí | Tipo de evento. Ignora los que no conozcas. `payment.processing`: El pago se aceptó y sigue en curso, o se reabrió. `payment.in_review`: El pago está en revisión. No lo repitas. `payment.completed`: El pago se confirmó. `payment.failed`: El pago no se hizo y no queda dinero en tránsito. `payment.returned`: El pago se devolvió. Puede llegar después de `payment.completed`. `payment.canceled`: El pago se canceló. `deposit.received`: El dinero ya está acreditado. `deposit.in_review`: El depósito está en revisión. `deposit.returned`: El depósito se devolvió. `collection.paid`: El cobro se pagó. En `efectivo`, la tienda ya confirmó el pago y el abono a tu cuenta llega después. `collection.expired`: El cobro venció sin pago. `security.ip_allowlist.changed`: Cambió la lista de IP de una llave de producción. `system.egress_ips.changing`: Cambiará una IP de salida de webhooks. Actualiza tu firewall antes de `effective_at`. |
| `has_more` | booleano | Sí | true si hay más filas después de esta página. |
| `next_cursor` | texto o `null` | Sí | Cursor de la página siguiente. null si no hay. |

###### Valores de `data[].data`

Cambia según `type`. Ver el [catálogo de eventos](/docs/webhooks/eventos/).

###### Valores de `data[].type`

Ver el [catálogo de eventos](/docs/webhooks/eventos/). Cada tipo está en el contrato, y aun así ignora los que no conozcas: un tipo nuevo no rompe la versión.

```json noexec="ejemplo"
{
  "data": [
    {
      "api_version": "2026-11-01",
      "created_at": "2026-11-04T18:01:30.000Z",
      "data": {
        "amount": "1500.00",
        "currency": "MXN",
        "destination_currency": "MXN",
        "external_ref": "pedido-7731",
        "failure_code": null,
        "final_amount": "1500.00",
        "final_currency": "MXN",
        "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
        "status": "completed",
        "updated_at": "2026-11-04T18:01:30.000Z"
      },
      "id": "evt_5mPqW2xZr8TnC1vH7kLdS4bJ",
      "resource_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
      "sequence": 2,
      "type": "payment.completed"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "events:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-eventos/).

#### Guías

- [Webhooks](/docs/webhooks/)
- [Catálogo de eventos](/docs/webhooks/eventos/)
- [Paginación](/docs/paginacion/)
- [Autenticación](/docs/autenticacion/)
- [Lista de migración](/docs/migracion/)
- [Conciliar](/docs/recetas/conciliar/)
- [Salud y estado](/docs/salud-y-estado/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)
- [IP de webhooks](/docs/seguridad/ip-webhooks/)

### GET /v1/events/{id}

Un evento

Un evento de tu empresa, con el mismo contenido que entrega el webhook.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/events/{id}` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getEvent` |
| Acceso | Llave con el scope `events:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `api_version` | texto | Sí | Versión con que se generó el evento. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data` | objeto | Sí | Contenido del evento. Su forma depende de `type`. |
| `id` | texto | Sí | Identificador del evento, con prefijo `evt_`. Va firmado: úsalo para ignorar repetidos. |
| `resource_id` | texto | Sí | Recurso al que se refiere. |
| `sequence` | entero | Sí | Orden dentro del recurso: un valor mayor es más reciente. |
| `type` | texto | Sí | Tipo de evento. Ignora los que no conozcas. `payment.processing`: El pago se aceptó y sigue en curso, o se reabrió. `payment.in_review`: El pago está en revisión. No lo repitas. `payment.completed`: El pago se confirmó. `payment.failed`: El pago no se hizo y no queda dinero en tránsito. `payment.returned`: El pago se devolvió. Puede llegar después de `payment.completed`. `payment.canceled`: El pago se canceló. `deposit.received`: El dinero ya está acreditado. `deposit.in_review`: El depósito está en revisión. `deposit.returned`: El depósito se devolvió. `collection.paid`: El cobro se pagó. En `efectivo`, la tienda ya confirmó el pago y el abono a tu cuenta llega después. `collection.expired`: El cobro venció sin pago. `security.ip_allowlist.changed`: Cambió la lista de IP de una llave de producción. `system.egress_ips.changing`: Cambiará una IP de salida de webhooks. Actualiza tu firewall antes de `effective_at`. |

###### Valores de `data`

Cambia según `type`. Ver el [catálogo de eventos](/docs/webhooks/eventos/).

###### Valores de `type`

Ver el [catálogo de eventos](/docs/webhooks/eventos/). Cada tipo está en el contrato, y aun así ignora los que no conozcas: un tipo nuevo no rompe la versión.

```json noexec="ejemplo"
{
  "api_version": "2026-11-01",
  "created_at": "2026-11-04T18:01:30.000Z",
  "data": {
    "amount": "1500.00",
    "currency": "MXN",
    "destination_currency": "MXN",
    "external_ref": "pedido-7731",
    "failure_code": null,
    "final_amount": "1500.00",
    "final_currency": "MXN",
    "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
    "status": "completed",
    "updated_at": "2026-11-04T18:01:30.000Z"
  },
  "id": "evt_5mPqW2xZr8TnC1vH7kLdS4bJ",
  "resource_id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "sequence": 2,
  "type": "payment.completed"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El evento no existe o es de otra empresa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "events:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-evento/).

#### Guías

- [Catálogo de eventos](/docs/webhooks/eventos/)
- [Autenticación](/docs/autenticacion/)

## Webhooks

### GET /v1/webhook-endpoints

Destinos de webhooks

Destinos de webhooks de la empresa, sólo para consultarlos. El alta, la edición, la baja, la rotación del secreto y la reactivación se hacen en el panel: v1 no tiene API para eso.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/webhook-endpoints` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `listWebhookEndpoints` |
| Acceso | Llave con el scope `webhooks:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Sólo consulta. El alta, la edición, la baja, la rotación del secreto y la reactivación de un destino se hacen en el panel, también en `v1`: un destino decide a dónde sale tu información, y el panel exige sesión, rol y bitácora. Ver [Dar de alta un destino](/docs/webhooks/#dar-de-alta-un-destino).

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `data` | lista de objeto | Sí | Destinos de webhooks de la empresa. |
| `data[].created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `data[].events` | lista de texto | Sí | Tipos suscritos. Vacío: todos. |
| `data[].id` | texto | Sí | Identificador del destino. |
| `data[].status` | texto | Sí | Estado del destino. `active`: Recibe eventos. `disabled`: Dejó de recibir eventos tras 3 días seguidos de fallos. Se reactiva en el panel. `paused`: Valor reservado. Hoy ningún flujo lo asigna. |
| `data[].url` | texto | Sí | URL https que recibe los eventos. |

###### Valores de `data[].status`

| Valor | Significado |
|---|---|
| `active` | Recibe eventos. |
| `disabled` | Dejó de recibir eventos tras 3 días seguidos de fallos. Se reactiva en el panel. |
| `paused` | Valor reservado. Hoy ningún flujo lo asigna. |

```json noexec="ejemplo"
{
  "data": [
    {
      "created_at": "2026-11-04T18:00:00.000Z",
      "events": [
        "payment.completed",
        "payment.failed"
      ],
      "id": "3f0c2d1e-7a54-4b1f-9c0e-1a2b3c4d5e6f",
      "status": "active",
      "url": "https://empresa-ejemplo.com/webhooks/fital"
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "webhooks:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/listar-destinos-de-webhook/).

#### Guías

- [Webhooks](/docs/webhooks/)
- [Inicio rápido de webhooks](/docs/webhooks/inicio-rapido/)
- [Autenticación](/docs/autenticacion/)
- [Paginación](/docs/paginacion/)

## Sistema

### GET /v1/health

Salud pública del servicio

Estado general del servicio, sin llave y sin gastar cuota. Sale de una medición en segundo plano: si tiene más de 15 s responde `degraded`, aunque dijera `ok`. Con `down` el código es 503.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/health` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getHealth` |
| Acceso | Público, sin llave |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

- Esta operación no usa la forma de [Errores](/docs/errores/): con `down` responde `503` con el mismo cuerpo que un `200`. Decide por `status`.

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|

Sin llave ni otros encabezados obligatorios.

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `api_version` | texto | Sí | Versión de la API. `v1`: la única publicada. |
| `status` | texto | Sí | Estado general del servicio. `ok`: El servicio funciona. `degraded`: La medición es vieja o hay problemas parciales. Responde `200`. `down`: El servicio no funciona. Responde `503`. |

###### Valores de `api_version`

| Valor | Significado |
|---|---|
| `v1` | Versión de la API. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `ok` | El servicio funciona. |
| `degraded` | La medición tiene más de 15 segundos, o hay problemas parciales. Responde `200`. |
| `down` | El servicio no funciona. Responde `503`. |

```json noexec="ejemplo"
{
  "status": "ok",
  "api_version": "v1"
}
```

Medición vieja o problema parcial: sigue en `200`:

```json noexec="ejemplo"
{
  "status": "degraded",
  "api_version": "v1"
}
```

##### 503 Servicio no disponible

Misma forma que la respuesta anterior.

```json noexec="ejemplo"
{
  "status": "down",
  "api_version": "v1"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |

#### Reintentos

Con `503`, repite con espera exponencial.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-salud/).

#### Guías

- [Salud y estado](/docs/salud-y-estado/)
- [Autenticación](/docs/autenticacion/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)

### GET /v1/meta/egress-ips

IP de salida de webhooks

Direcciones desde las que FITAL envía webhooks, para tu firewall. Pública, con un minuto de caché. La firma HMAC sigue siendo la protección principal.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/meta/egress-ips` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getEgressIps` |
| Acceso | Público, sin llave |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|

Sin llave ni otros encabezados obligatorios.

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `addresses` | lista de objeto | Sí | Direcciones vigentes o anunciadas. |
| `addresses[].active_from` | texto o `null` | Sí | Desde cuándo se usa. |
| `addresses[].ip` | texto | Sí | Dirección IP de salida. |
| `addresses[].retiring_at` | texto o `null` | Sí | Cuándo deja de usarse. null si no tiene fecha de retiro. |
| `note` | texto | Sí | Recordatorio: autoriza por firma, no sólo por IP. |

```json noexec="ejemplo"
{
  "addresses": [
    {
      "active_from": "2026-11-01T00:00:00.000Z",
      "ip": "198.51.100.10",
      "retiring_at": null
    }
  ],
  "note": "La firma HMAC es la protección principal"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste el límite por IP. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. | `true` |

##### Ejemplos de error

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-ip-de-salida/).

#### Guías

- [IP de webhooks](/docs/seguridad/ip-webhooks/)
- [Autenticación](/docs/autenticacion/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)

### GET /v1/status

Estado por capacidad

Estado de pagos, cobros, webhooks y API para tu empresa. Acepta cualquier llave válida. `not_enabled` indica que tu empresa no tiene esa capacidad y `unknown` que no hay medición reciente.

| Dato | Valor |
|---|---|
| Método y ruta | `GET /v1/status` |
| Servidor | `https://widget.fital.xyz` |
| `operationId` | `getStatus` |
| Acceso | Cualquier llave válida |
| Firma de petición | No aplica |
| Idempotencia | No aplica |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `capabilities` | lista de objeto | Sí | Una fila por capacidad. |
| `capabilities[].id` | texto | Sí | Identificador de la capacidad. `payments_mxn`: Pagos por SPEI. `payments_latam`: Pagos por el riel `latam`. `payments_pix`: Pagos por PIX. `collections`: Cobros. `webhooks`: Entrega de webhooks. `api`: La API. |
| `capabilities[].name` | texto | Sí | Nombre legible. |
| `capabilities[].since` | texto o `null` | Sí | Desde cuándo está en ese estado. null si no se sabe. |
| `capabilities[].status` | texto | Sí | Estado de la capacidad. `operational`: Funciona con normalidad. `degraded`: Funciona con problemas. `outage`: No funciona. `unknown`: Sin medición reciente. `not_enabled`: No está habilitada para tu empresa. |
| `updated_at` | texto | Sí | Momento en que se armó esta respuesta. |

###### Valores de `capabilities[].id`

| Valor | Significado |
|---|---|
| `payments_mxn` | Pagos por SPEI. |
| `payments_latam` | Pagos por el riel `latam`. |
| `payments_pix` | Pagos por PIX. |
| `collections` | Cobros. |
| `webhooks` | Entrega de webhooks. |
| `api` | La API. |

El contrato lo declara como texto: trata la lista como extensible.

###### Valores de `capabilities[].status`

| Valor | Significado |
|---|---|
| `operational` | Funciona con normalidad. |
| `degraded` | Funciona con problemas. |
| `outage` | No funciona. |
| `unknown` | Sin medición reciente. |
| `not_enabled` | No está habilitada para tu empresa. |

```json noexec="ejemplo"
{
  "capabilities": [
    {
      "id": "payments_mxn",
      "name": "Pagos MXN",
      "since": "2026-11-04T10:00:00.000Z",
      "status": "operational"
    },
    {
      "id": "payments_pix",
      "name": "Pagos PIX",
      "since": null,
      "status": "not_enabled"
    }
  ],
  "updated_at": "2026-11-04T18:00:00.000Z"
}
```

Variante `status_degraded`:

```json noexec="ejemplo"
{
  "updated_at": "2026-11-04T18:00:00.000Z",
  "capabilities": [
    {
      "id": "payments_mxn",
      "name": "Pagos MXN",
      "status": "degraded",
      "since": "2026-11-04T10:00:00.000Z"
    },
    {
      "id": "webhooks",
      "name": "Webhooks",
      "status": "operational",
      "since": "2026-11-04T09:00:00.000Z"
    },
    {
      "id": "api",
      "name": "API",
      "status": "operational",
      "since": "2026-11-04T09:00:00.000Z"
    }
  ]
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) (sólo cuenta de prueba) | El encabezado `X-Fital-Scenario` trae un escenario desconocido. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 403 | [`kyb_required`](/docs/errores/#kyb_required) | La empresa no tiene KYB aprobado o aún no está inscrita. | `false` |
| 403 | [`account_not_active`](/docs/errores/#account_not_active) | La cuenta no está activa. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_request",
    "message": "Petición inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "fields": {
        "limit": "Entre 1 y 100"
      }
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_request"
  }
}
```

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 ip_not_allowed`:

```json noexec="ejemplo"
{
  "error": {
    "code": "ip_not_allowed",
    "message": "IP no permitida para esta llave",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#ip_not_allowed"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Es una lectura: repetirla no tiene efecto.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/consultar-estado/).

#### Guías

- [Salud y estado](/docs/salud-y-estado/)
- [Autenticación](/docs/autenticacion/)
- [Cobertura](/docs/cobertura/)
- [Pago PIX](/docs/recetas/pago-pix/)

## Sandbox

### POST /sandbox/v1/payments/{id}/advance

Avanzar un pago ficticio

Lleva un pago de sandbox a otro estado sin esperar su guion, y emite el evento firmado de ese cambio. Exige `Idempotency-Key` y firma. Sólo existe en sandbox: en producción da 404.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /sandbox/v1/payments/{id}/advance` |
| Servidor | `https://widget.fital.xyz/sandbox` |
| `operationId` | `advanceSandboxPayment` |
| Acceso | Llave con el scope `payments:write` |
| Firma de petición | **Obligatoria** en producción y en la cuenta de prueba. El simulador público no la valida. Ver [Firma de petición](/docs/autenticacion/#firma-de-petición) |
| Idempotencia | **Obligatoria**, con `Idempotency-Key`. Ver [Idempotencia](/docs/idempotencia/) |
| Ambiente | Sólo existe en la cuenta de prueba. En producción responde `404` |

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `Idempotency-Key` | Sí | Clave única de esta operación, de 8 a 200 caracteres. Repetirla con el mismo cuerpo devuelve la misma respuesta durante 7 días. Con otro cuerpo da `422 idempotency_key_reused`, también pasado ese plazo. |
| `X-Fital-Timestamp` | Sí | Hora Unix en segundos. Se acepta hasta 300 s de diferencia. |
| `X-Fital-Signature` | Sí | Firma `v1=` de la petición con el secreto de firma de la llave. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Parámetros

| Parámetro | En | Tipo | Obligatorio | Descripción |
|---|---|---|---|---|
| `id` | ruta | texto | Sí | Identificador del recurso. Hasta 250 caracteres. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `status` | texto | Sí | Estado al que quieres llevar el pago ficticio. `completed`: Termina el pago. Viene de `processing` o `in_review`. `failed`: Falla el pago. Viene de `processing` o `in_review`. `returned`: Devuelve el pago. Viene de `completed`. `in_review`: Lo pasa a revisión. Viene de `processing`. |

```json noexec="ejemplo"
{
  "status": "completed"
}
```

##### Valores de `status`

| Valor | Significado |
|---|---|
| `completed` | Termina el pago. Viene de `processing` o `in_review`. |
| `failed` | Falla el pago. Viene de `processing` o `in_review`. |
| `returned` | Devuelve el pago. Viene de `completed`. |
| `in_review` | Lo pasa a revisión. Viene de `processing`. |

#### Respuestas correctas

##### 200 Correcto

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `amount` | texto o `null` | Sí | Monto pedido. |
| `beneficiary_id` | texto o `null` | Sí | Beneficiario del pago. |
| `created_at` | texto o `null` | Sí | Fecha de creación. ISO 8601 en UTC. |
| `currency` | texto o `null` | Sí | Moneda pedida. |
| `external_ref` | texto o `null` | Sí | Tu referencia. |
| `failure_code` | texto o `null` | Sí | Código del catálogo de errores si el pago falló, null en cualquier otro estado. En `GET /v1/payments` siempre es null: léelo en `GET /v1/payments/{id}` o en `payment.failed`. Trata la lista como extensible. `payment_rejected`: Rechazado sin un motivo más específico. `insufficient_funds`: Saldo insuficiente al ejecutar. `limit_exceeded`: Se superó el tope acumulado de la empresa o un límite del riel. `amount_below_minimum`: El monto quedó por debajo del mínimo aplicable. `account_restricted`: La cuenta tiene una restricción. `in_review`: Una revisión detuvo la ejecución. `quote_changed`: El débito superaría lo permitido. `quote_expired`: La cotización venció antes de ejecutar. |
| `final_amount` | texto o `null` | Sí | Monto final, cuando se conoce. |
| `final_currency` | texto o `null` | Sí | Moneda del monto final. |
| `freshness` | objeto | No | De dónde sale `status`. Sólo en `GET /v1/payments/{id}`. |
| `freshness.as_of` | texto o `null` | Sí | Hora de esa lectura, de la última que se aplicó o de la cancelación confirmada. Null si aún no hay ninguna. ISO 8601 en UTC. |
| `freshness.status` | texto | Sí | De dónde sale `status`. `current`: `status` sale de una lectura de ahora. `delayed`: La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |
| `id` | texto | Sí | Identificador del pago, con prefijo `pay_`. |
| `metadata` | objeto | Sí | Los pares que enviaste al crear el pago. |
| `reference` | texto o `null` | Sí | Concepto del pago. |
| `status` | texto | Sí | Estado público. `processing`: Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. `in_review`: En revisión. No lo repitas: el pago ya existe. `completed`: Se pagó. Aún puede pasar a `returned`. `failed`: No se hizo y no queda dinero en tránsito. `returned`: Se devolvió y el saldo ya volvió. `canceled`: Se canceló antes de ejecutarse. |

###### Valores de `failure_code`

| Valor | Significado |
|---|---|
| `payment_rejected` | Rechazado sin un motivo más específico. |
| `insufficient_funds` | Saldo insuficiente al ejecutar. |
| `limit_exceeded` | Se superó el tope acumulado de la empresa o un límite del riel. |
| `amount_below_minimum` | El monto quedó por debajo del mínimo aplicable. |
| `account_restricted` | La cuenta tiene una restricción. |
| `in_review` | Una revisión detuvo la ejecución. |
| `quote_changed` | El débito superaría lo permitido. |
| `quote_expired` | La cotización venció antes de ejecutar. |

Con `failed`, `GET /v1/payments/{id}` y `payment.failed` siempre traen un código: `payment_rejected` si no hay uno más específico. En cualquier otro estado, y en la lista `GET /v1/payments`, es `null`. Trata la lista como extensible.

###### Valores de `freshness.status`

| Valor | Significado |
|---|---|
| `current` | `status` sale de una lectura de ahora. |
| `delayed` | La lectura no concluyó y `status` es lo último que se sabe. No lo tomes como un resultado nuevo. |

###### Valores de `status`

| Valor | Significado |
|---|---|
| `processing` | Aceptado y en curso, o aún sin confirmar. Consúltalo hasta un estado terminal. |
| `in_review` | En revisión. No lo repitas: el pago ya existe. |
| `completed` | Se pagó. Aún puede pasar a `returned`. |
| `failed` | No se hizo y no queda dinero en tránsito. |
| `returned` | Se devolvió y el saldo ya volvió. |
| `canceled` | Se canceló antes de ejecutarse. |

```json noexec="ejemplo"
{
  "amount": "1500.00",
  "beneficiary_id": "ben_3kTMd9wQp2LxZ7vR1nYcA4bE",
  "created_at": "2026-11-04T18:00:00.000Z",
  "currency": "MXN",
  "external_ref": "pedido-7731",
  "failure_code": null,
  "final_amount": "1500.00",
  "final_currency": "MXN",
  "id": "pay_6nRtY1wKs3QzD8cL5mVhF2gB",
  "livemode": false,
  "metadata": {},
  "reference": null,
  "status": "completed"
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Idempotent-Replayed` | `true` si la respuesta repite una anterior con esa clave. |
| `Retry-After` | Segundos a esperar, con `429`, con `409 idempotency_in_progress` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 400 | [`idempotency_key_required`](/docs/errores/#idempotency_key_required) | Falta `Idempotency-Key`. | `false` |
| 400 | [`idempotency_key_invalid`](/docs/errores/#idempotency_key_invalid) | La clave no mide entre 8 y 200 caracteres, o viene duplicada. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 401 | [`invalid_signature`](/docs/errores/#invalid_signature) | Falta la firma, no coincide o tiene más de 5 minutos. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 404 | [`not_found`](/docs/errores/#not_found) | El pago no existe o es de otra empresa. | `false` |
| 409 | [`sandbox_invalid_transition`](/docs/errores/#sandbox_invalid_transition) | Un pago real no puede hacer ese cambio. | `false` |
| 409 | [`idempotency_in_progress`](/docs/errores/#idempotency_in_progress) | Una petición anterior con esa clave sigue en curso. Llega con `Retry-After`. | `true` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 422 | [`insufficient_funds`](/docs/errores/#insufficient_funds) | Llevar a `completed` un pago aún sin débito requiere saldo ficticio. | `false` |
| 422 | [`idempotency_key_reused`](/docs/errores/#idempotency_key_reused) | Usaste esa clave con otro cuerpo, también pasados 7 días. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "payments:write"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`404 not_found`:

```json noexec="ejemplo"
{
  "error": {
    "code": "not_found",
    "message": "No encontrado",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#not_found"
  }
}
```

`409 sandbox_invalid_transition`:

```json noexec="ejemplo"
{
  "error": {
    "code": "sandbox_invalid_transition",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#sandbox_invalid_transition"
  }
}
```

`413 invalid_request`:

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

`422 insufficient_funds`:

```json noexec="ejemplo"
{
  "error": {
    "code": "insufficient_funds",
    "message": "Operación rechazada",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#insufficient_funds"
  }
}
```

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

Ante un timeout o una conexión cortada, repite con la misma clave.

- `409 idempotency_in_progress`: Espera `Retry-After` y repite con la misma clave.
- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/avanzar-pago-de-prueba/).

#### Guías

- [Inicio rápido de la cuenta de prueba](/docs/sandbox/inicio-rapido/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)

### POST /sandbox/v1/webhooks/trigger

Emitir un evento de prueba

Emite un evento ficticio del tipo que pidas hacia tus destinos de sandbox, con firma y entrega reales. Sirve para probar tu receptor sin crear operaciones. Sólo existe en sandbox: en producción da 404.

| Dato | Valor |
|---|---|
| Método y ruta | `POST /sandbox/v1/webhooks/trigger` |
| Servidor | `https://widget.fital.xyz/sandbox` |
| `operationId` | `triggerSandboxWebhook` |
| Acceso | Llave con el scope `webhooks:read` |
| Firma de petición | No aplica |
| Idempotencia | No aplica |
| Ambiente | Sólo existe en la cuenta de prueba. En producción responde `404` |

- Sin clave de idempotencia y sin firma de petición: cada llamada emite otro evento. Necesita el scope `webhooks:read`.
- `data` tiene la forma real de ese tipo de evento, con datos ficticios, más `simulated: true` y `livemode: false`. Ver el [catálogo de eventos](/docs/webhooks/eventos/).

#### Encabezados de la petición

| Encabezado | Obligatorio | Descripción |
|---|---|---|
| `Authorization` | Sí | `Bearer` y tu llave. |
| `Content-Type` | Sí | `application/json`. |
| `X-Fital-Scenario` | No | Sólo simuladores: elige la respuesta que quieres practicar. |

#### Cuerpo

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `resource_id` | texto | No | Recurso al que se refiere. Sin este campo se usa uno ficticio. |
| `type` | texto | Sí | Tipo de evento a emitir. `payment.processing`: El pago se aceptó y sigue en curso, o se reabrió. `payment.in_review`: El pago está en revisión. No lo repitas. `payment.completed`: El pago se confirmó. `payment.failed`: El pago no se hizo y no queda dinero en tránsito. `payment.returned`: El pago se devolvió. Puede llegar después de `payment.completed`. `payment.canceled`: El pago se canceló. `deposit.received`: El dinero ya está acreditado. `deposit.in_review`: El depósito está en revisión. `deposit.returned`: El depósito se devolvió. `collection.paid`: El cobro se pagó. En `efectivo`, la tienda ya confirmó el pago y el abono a tu cuenta llega después. `collection.expired`: El cobro venció sin pago. `security.ip_allowlist.changed`: Cambió la lista de IP de una llave de producción. `system.egress_ips.changing`: Cambiará una IP de salida de webhooks. Actualiza tu firewall antes de `effective_at`. |

```json noexec="ejemplo"
{
  "type": "payment.completed"
}
```

##### Valores de `type`

Ver el [catálogo de eventos](/docs/webhooks/eventos/).

#### Respuestas correctas

##### 202 Aceptado: el resultado final llega después

| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
| `event_id` | texto | Sí | Identificador del evento emitido. |
| `resource_id` | texto | Sí | Recurso del evento. |
| `sequence` | entero | Sí | Orden dentro del recurso. |

```json noexec="ejemplo"
{
  "event_id": "evt_5mPqW2xZr8TnC1vH7kLdS4bJ",
  "livemode": false,
  "resource_id": "sim_7f3a9c21d4e8b605",
  "sequence": 1
}
```

#### Encabezados de la respuesta

| Encabezado | Contenido |
|---|---|
| `X-Request-Id` | Identificador de la petición. Cítalo con soporte. |
| `Retry-After` | Segundos a esperar, con `429` y a veces con `503`. |

#### Errores

Todos usan la forma de [Errores](/docs/errores/): `error.code`, `message`, `request_id`, `details`, `retryable` y `docs_url`. Decide por `code`.

| HTTP | Código | Cuándo sale | `retryable` |
|---|---|---|---|
| 400 | [`invalid_request`](/docs/errores/#invalid_request) | Algún parámetro o campo no cumple el esquema. Mira `details.fields`. | `false` |
| 401 | [`invalid_api_key`](/docs/errores/#invalid_api_key) | La llave falta, está mal formada, venció o fue revocada, o la empresa está suspendida. | `false` |
| 401 | [`wrong_environment`](/docs/errores/#wrong_environment) | La llave es de otro ambiente. | `false` |
| 403 | [`forbidden_scope`](/docs/errores/#forbidden_scope) | La llave no tiene el scope de esta operación. `details.required_scope` lo nombra. | `false` |
| 403 | [`ip_not_allowed`](/docs/errores/#ip_not_allowed) | La IP de origen no está activa en la lista de esa llave. | `false` |
| 413 | [`invalid_request`](/docs/errores/#invalid_request) | El cuerpo supera 64 KB. | `false` |
| 429 | [`rate_limited`](/docs/errores/#rate_limited) | Superaste un límite de peticiones. Llega con `Retry-After`. | `true` |
| 500 | [`internal_error`](/docs/errores/#internal_error) | Fallo nuestro. El resultado de la petición no se conoce. | `true` |
| 503 | [`service_unavailable`](/docs/errores/#service_unavailable) | El servicio no está disponible por ahora. El resultado de la petición no se conoce. | `true` |

##### Ejemplos de error

`400 invalid_request`:

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

`401 invalid_api_key`:

```json noexec="ejemplo"
{
  "error": {
    "code": "invalid_api_key",
    "message": "Credencial inválida",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#invalid_api_key"
  }
}
```

`403 forbidden_scope`:

```json noexec="ejemplo"
{
  "error": {
    "code": "forbidden_scope",
    "message": "La llave no tiene permiso para esta operación",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {
      "required_scope": "webhooks:read"
    },
    "retryable": false,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#forbidden_scope"
  }
}
```

`413 invalid_request`:

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

`429 rate_limited`:

```json noexec="ejemplo"
{
  "error": {
    "code": "rate_limited",
    "message": "Demasiadas peticiones",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#rate_limited"
  }
}
```

`500 internal_error`:

```json noexec="ejemplo"
{
  "error": {
    "code": "internal_error",
    "message": "Error interno",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#internal_error"
  }
}
```

`503 service_unavailable`:

```json noexec="ejemplo"
{
  "error": {
    "code": "service_unavailable",
    "message": "Servicio no disponible",
    "request_id": "0123456789abcdef0123456789abcdef",
    "details": {},
    "retryable": true,
    "docs_url": "https://merchants.fital.xyz/docs/errores/#service_unavailable"
  }
}
```

#### Reintentos

No lleva clave de idempotencia y no mueve dinero: repetirla es seguro.

- `429 rate_limited`: Espera lo que indique `Retry-After` y repite.
- `500 internal_error`: Repite con espera exponencial.
- `503 service_unavailable`: Repite con espera exponencial. Respeta `Retry-After` si viene.

Ver [Reintentos](/docs/recetas/reintentos/) y [Límites](/docs/limites/).

#### Muestras

En curl, Node, Python y PHP: ver [la página de esta operación](/docs/operaciones/emitir-evento-de-prueba/).

#### Guías

- [Inicio rápido de webhooks](/docs/webhooks/inicio-rapido/)
- [Cuenta de prueba](/docs/sandbox/cuenta-de-prueba/)
- [Lista de migración](/docs/migracion/)
- [Catálogo de eventos](/docs/webhooks/eventos/)
