> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recurrente.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recurrente.com/_mcp/server.

# Mover dinero

> Un solo endpoint para retiros bancarios, transferencias p2p y stablecoin

Todo el dinero que sale de tu balance se mueve con **un solo endpoint**: `POST /api/transfers`. Indicas a dónde va el dinero con el objeto `destination`, y Recurrente crea el movimiento correcto — un retiro bancario, una transferencia a otra cuenta de Recurrente, un envío a un teléfono, o un envío de stablecoin on-chain.

> **Warning**
>
> Este endpoint está disponible únicamente en LIVE. El balance de un Sandbox es falso,
> no transferible y no retirable, por lo que `POST /api/transfers` rechaza cualquier
> destino sin debitar el ledger.

```json
POST /api/transfers
{
  "amount_in_cents": 50000,
  "currency": "GTQ",
  "destination": "ba_abc123"
}
```

## Destinos

En la mayoría de los casos, `destination` es simplemente **el identificador del destino como string** — pega el que tengas y el formato indica a dónde va el dinero:

| `destination`    | A dónde va el dinero                               | Registro creado |
| ---------------- | -------------------------------------------------- | --------------- |
| `"ba_abc123"`    | Retiro a esa cuenta bancaria tuya                  | `wi_`           |
| `"ac_xyz789"`    | Transferencia a esa cuenta de Recurrente           | `tr_`           |
| `"@mitienda"`    | Transferencia a la cuenta con ese handle           | `tr_`           |
| `"co_maria123"`  | Transferencia al teléfono de ese contacto guardado | `tr_`           |
| `"+50255667788"` | Envío a ese teléfono                               | `tr_`           |

```json
{ "amount_in_cents": 50000, "destination": "ba_abc123" }
{ "amount_in_cents": 10000, "currency": "GTQ", "destination": "@mitienda", "note": "Pago de servicios" }
{ "amount_in_cents": 5000, "currency": "GTQ", "destination": "+50255667788" }
```

El único destino que necesita la **forma de objeto** es stablecoin, porque una dirección cruda no dice qué token ni en qué red enviarlo:

```json
{
  "amount_in_cents": 10000,
  "currency": "USD",
  "destination": { "type": "crypto_address", "address": "0x1234…", "chain": "base", "currency": "USDC" }
}
```

La forma de objeto también existe para los demás tipos si prefieres ser explícito: `{ "type": "bank_account", "id": "ba_..." }`, `{ "type": "account", "id": "ac_... | @handle" }`, `{ "type": "phone_number", "number": "..." }`.

Notas por destino:

* **Retiros (`ba_`)** — acepta además `is_instant` (retiro instantáneo, sujeto a elegibilidad y comisión) y `should_perform_currency_conversion` (convierte el balance a la moneda de la cuenta destino). Si omites `currency`, se usa la moneda de la cuenta bancaria.
* **Cuentas (`ac_` / `@handle`)** — la transferencia no tiene comisión y requiere evaluación antes de acreditarse.
* **Teléfonos y contactos** — después de aprobarse, el movimiento queda `unclaimed` hasta que el destinatario se registre y complete KYC. Puedes cancelarlo mientras no haya sido reclamado.
* **Stablecoin** — requiere completar la verificación de stablecoin en la app. Indica la stablecoin en `destination.currency` y la red en `destination.chain`.

## Estados

Cada movimiento tiene un `status` canónico — el mismo vocabulario sin importar el destino — y un `status_detail` con el estado crudo del registro subyacente.

| `status`     | Significado                                                                                                               |
| ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `pending`    | Creado, esperando procesamiento o revisión                                                                                |
| `in_review`  | En revisión manual o de riesgo                                                                                            |
| `processing` | Aprobado, en preparación para enviarse                                                                                    |
| `sent`       | Fondos enviados por su rail. Para retiros bancarios locales, es un resultado terminal exitoso: ya no esperes `completed`. |
| `unclaimed`  | Envío a teléfono esperando que el destinatario lo reclame                                                                 |
| `completed`  | El dinero llegó a su destino                                                                                              |
| `failed`     | Rechazado o falló; el balance se restaura                                                                                 |
| `cancelled`  | Cancelado antes de enviarse; el balance se restaura                                                                       |

Los P2P, retiros y envíos de stablecoin son **asíncronos**: la respuesta del `POST` trae el estado inicial, y puedes seguir el avance consultando `GET /api/transfers/{id}` o con webhooks.

Los P2P quedan en `pending` durante la evaluación automática. Una cuenta configurada para revisar todos los envíos pasa directamente a `in_review`. `in_review` significa que debe decidir una persona. El monto queda reservado hasta la aprobación. Si se rechazan, pasan a `cancelled` y el monto vuelve al remitente.

Los repartos automáticos entre cuentas también pueden quedar `in_review`. Si los P2P están deshabilitados, el reparto se reserva para revisión. Su configuración pasa a `completed` al aprobarlos o `cancelled` al rechazarlos. Hay que resolver la revisión antes de reembolsar el pago que originó el reparto.

Solo puedes cancelar un P2P mientras esté `unclaimed`. Un POST exitoso no significa que el destinatario recibió el dinero. No crees otro envío para reemplazar uno pendiente o en revisión.

### Retiros bancarios: cuándo terminar el flujo

Para un retiro bancario, procesa `sent` como éxito terminal. Algunos retiros históricos o rails con confirmación adicional pueden mostrar `completed`, pero no debes bloquear tu flujo esperando ese estado después de recibir `sent`.

| Transición                           | Evento webhook                            | Qué debe hacer tu integración                                                  |
| ------------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------ |
| `pending` / `in_review` / `approved` | `withdrawal.create` o `withdrawal.update` | Mantener el retiro pendiente; puede requerir revisión.                         |
| `sent`                               | `withdrawal.update`                       | Marcar el retiro como exitoso y continuar tu flujo posterior.                  |
| `completed`                          | `withdrawal.update` (si aplica)           | Conservarlo como confirmación adicional; no es requisito para cerrar el flujo. |
| `rejected` / `cancelled`             | `withdrawal.update`                       | Marcarlo como no exitoso y manejar la reversión o el reintento.                |

## Idempotencia

Envía un header `Idempotency-Key` en cada `POST`. Si reintentas con la misma llave y el mismo cuerpo, recibirás la respuesta original sin crear un segundo movimiento.

```bash
curl -X POST https://app.recurrente.com/api/transfers \
  -H "X-SECRET-KEY: sk_live_…" \
  -H "Idempotency-Key: pago-nomina-2026-07" \
  -H "Content-Type: application/json" \
  -d '{"amount_in_cents": 50000, "currency": "GTQ", "destination": "ba_abc123"}'
```

## Listar y filtrar

`GET /api/transfers` devuelve todos tus movimientos — retiros, p2p y stablecoin — del más reciente al más antiguo. Filtra por tipo de destino con `types[]`:

```
GET /api/transfers?types[]=bank_account&types[]=crypto_address
```

`GET /api/transfers/{id}` y `POST /api/transfers/{id}/cancel` aceptan cualquier ID de movimiento (`tr_`, `wi_`, `sw_`).

## Cuentas conectadas

Si operas una plataforma con [cuentas conectadas](/guias-espanol/guias/cuentas-conectadas), pasa `account_id` para mover dinero de una cuenta hija verificada. Las cuentas hijas admiten destinos `bank_account`, y transferencias dentro de su misma plataforma — a la cuenta madre (comisiones) o a cuentas hermanas (liquidaciones de marketplace). Los destinos fuera de la familia, teléfonos y cripto no están disponibles para cuentas hijas:

```json
POST /api/transfers
{
  "account_id": "ac_hija123",
  "amount_in_cents": 50000,
  "destination": "ba_delahija"
}
```

## Webhooks

Cada tipo de movimiento emite webhooks al avanzar: `transfer.sent`/`transfer.received` (p2p), `transfer.pending`/`transfer.in_review`/`transfer.cancelled` (revisión de p2p), `withdrawal.create`/`withdrawal.update` (retiros) y `swap.create`/`swap.update` (stablecoin). Los eventos de retiros y stablecoin incluyen una llave `transfer` con la misma forma unificada que devuelve la API, además de su llave histórica (`withdrawal`/`swap`). Si integras con el endpoint unificado, lee siempre `payload.transfer`. En un retiro, usa el `status` del payload y cierra el flujo al recibir `sent`; no esperes un evento `completed` posterior.

## Requisitos

Para mover dinero por API necesitas:

1. Una llave API con **movimiento de dinero habilitado** (Configuración → Llaves API).
2. Tu cuenta **verificada** (KYC completado).
3. Para stablecoin: haber completado la **verificación de stablecoin** en la app.

## ¿Y los endpoints de retiros?

`/api/withdrawals` y los retiros anidados de cuentas conectadas siguen funcionando indefinidamente para integraciones existentes, pero están marcados como obsoletos: para integraciones nuevas usa siempre `/api/transfers`.