> 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.

# Move money

> One endpoint for bank payouts, p2p transfers, and stablecoin sends

All money leaving your balance moves through **a single endpoint**: `POST /api/transfers`. You say where the money goes with the `destination` object, and Recurrente creates the right movement — a bank payout, a transfer to another Recurrente account, a send to a phone number, or an on-chain stablecoin send.

> **Warning**
>
> This endpoint is LIVE-only. A Sandbox balance is fake, non-transferable, and
> non-redeemable, so `POST /api/transfers` rejects every destination without debiting
> the ledger.

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

## Destinations

In most cases `destination` is simply **the destination's identifier as a string** — paste whichever one you hold, and its format tells us where the money goes:

| `destination`    | Where the money goes                     | Record created |
| ---------------- | ---------------------------------------- | -------------- |
| `"ba_abc123"`    | Payout to that bank account of yours     | `wi_`          |
| `"ac_xyz789"`    | Transfer to that Recurrente account      | `tr_`          |
| `"@mystore"`     | Transfer to the account with that handle | `tr_`          |
| `"co_maria123"`  | Transfer to that saved contact's phone   | `tr_`          |
| `"+50255667788"` | Send to that phone number                | `tr_`          |

```json
{ "amount_in_cents": 50000, "destination": "ba_abc123" }
{ "amount_in_cents": 10000, "currency": "GTQ", "destination": "@mystore", "note": "Service payment" }
{ "amount_in_cents": 5000, "currency": "GTQ", "destination": "+50255667788" }
```

The only destination that needs the **object form** is stablecoin, because a raw address alone doesn't say which token or network to send on:

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

The object form also exists for the other types if you prefer being explicit: `{ "type": "bank_account", "id": "ba_..." }`, `{ "type": "account", "id": "ac_... | @handle" }`, `{ "type": "phone_number", "number": "..." }`.

Per-destination notes:

* **Payouts (`ba_`)** — also accept `is_instant` (instant payout, subject to eligibility and a fee) and `should_perform_currency_conversion` (converts your balance into the destination account's currency). If you omit `currency`, the bank account's currency is used.
* **Accounts (`ac_` / `@handle`)** — the transfer is free and must pass evaluation before being credited.
* **Phones and contacts** — after approval, the movement stays `unclaimed` until the recipient signs up and completes KYC. You can cancel it while unclaimed.
* **Stablecoin** — requires completing stablecoin verification in the app. Set the stablecoin in `destination.currency` and the network in `destination.chain`.

## States

Every movement has a canonical `status` — the same vocabulary regardless of destination — plus a `status_detail` with the underlying record's raw state.

| `status`     | Meaning                                                                                                               |
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
| `pending`    | Created, awaiting processing or review                                                                                |
| `in_review`  | Under manual or risk review                                                                                           |
| `processing` | Approved, being prepared for sending                                                                                  |
| `sent`       | Funds sent through their rail. For local bank payouts, this is a terminal success state: do not wait for `completed`. |
| `unclaimed`  | Phone send waiting for the recipient to claim it                                                                      |
| `completed`  | Money arrived at its destination                                                                                      |
| `failed`     | Rejected or failed; the balance is restored                                                                           |
| `cancelled`  | Cancelled before sending; the balance is restored                                                                     |

P2P transfers, bank payouts and stablecoin sends are **asynchronous**: the `POST` response carries the initial state, and you can follow progress with `GET /api/transfers/{id}` or webhooks.

P2P transfers stay `pending` during automatic evaluation. Accounts configured to review every transfer go directly to `in_review`. `in_review` means a human must decide. The amount is reserved until approval. Rejection changes the status to `cancelled` and returns the amount to the sender.

Automatic splits between accounts can also enter `in_review`. If P2P is disabled, the split is reserved for review. Their transfer setup becomes `completed` on approval or `cancelled` on rejection. Resolve the review before refunding the payment that funded the split.

You can cancel a P2P transfer only while it is `unclaimed`. A successful POST does not mean the recipient has received the funds. Do not send a replacement while the original is pending or in review.

### Bank payouts: when to finish your flow

For a bank payout, treat `sent` as terminal success. Some historical payouts or rails with an additional confirmation may expose `completed`, but your flow must not block waiting for it after receiving `sent`.

| Transition                           | Webhook event                              | What your integration should do                                            |
| ------------------------------------ | ------------------------------------------ | -------------------------------------------------------------------------- |
| `pending` / `in_review` / `approved` | `withdrawal.create` or `withdrawal.update` | Keep the payout pending; it may require review.                            |
| `sent`                               | `withdrawal.update`                        | Mark the payout successful and continue your downstream flow.              |
| `completed`                          | `withdrawal.update` (when applicable)      | Keep as an additional confirmation; it is not required to finish the flow. |
| `rejected` / `cancelled`             | `withdrawal.update`                        | Mark it unsuccessful and handle the reversal or retry.                     |

## Idempotency

Send an `Idempotency-Key` header with every `POST`. Retrying with the same key and body returns the original response without creating a second movement.

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

## Listing and filtering

`GET /api/transfers` returns all your movements — payouts, p2p, and stablecoin — newest first. Filter by destination type with `types[]`:

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

`GET /api/transfers/{id}` and `POST /api/transfers/{id}/cancel` accept any movement ID (`tr_`, `wi_`, `sw_`).

## Connected accounts

If you run a platform with [connected accounts](/guides-english/guides/connected-accounts), pass `account_id` to move money for a verified child account. Child accounts support `bank_account` destinations, plus transfers within their own platform family — to the parent account (commission sweeps) or to sibling accounts (marketplace settlements). Out-of-family accounts, phone sends, and crypto are not available to children:

```json
POST /api/transfers
{
  "account_id": "ac_child123",
  "amount_in_cents": 50000,
  "destination": "ba_childbank"
}
```

## Webhooks

Every movement type emits webhooks as it progresses: `transfer.sent`/`transfer.received` (p2p), `transfer.pending`/`transfer.in_review`/`transfer.cancelled` (p2p review), `withdrawal.create`/`withdrawal.update` (payouts), and `swap.create`/`swap.update` (stablecoin). Payout and stablecoin events include a `transfer` key carrying the same unified shape the API returns, alongside their historical key (`withdrawal`/`swap`). If you integrate against the unified endpoint, always read `payload.transfer`. For a payout, use the payload's `status` and finish your flow on `sent`; do not wait for a later `completed` event.

## Requirements

To move money through the API you need:

1. An API key with **money movement enabled** (Settings → API Keys).
2. A **verified** account (KYC completed).
3. For stablecoin: completed **stablecoin verification** in the app.

## What about the withdrawals endpoints?

`/api/withdrawals` and the nested connected-account withdrawals keep working indefinitely for existing integrations, but they are marked deprecated: new integrations should always use `/api/transfers`.