# Send money

The Send Money API lets you pay out from your IntaSend wallet to M-Pesa, bank accounts,
M-Pesa tills and paybills, cross-border mobile money, and other IntaSend accounts.

A payout request is a **batch** (a "file") containing one or more **transactions**. Every
transaction in a batch uses the same `provider`, `currency` and `country`.

## The flow

```text
 initiate ──► (preview) ──► approve ──► balance check ──► sending ──► complete
    │                          ▲
    └── requires_approval: "NO" skips the manual approve step
```

1. **[Initiate](https://developers.intasend.com/guides/send-money/overview/#1-initiate)** a batch with `POST /api/v1/send-money/initiate/`.
2. **[Approve](https://developers.intasend.com/guides/send-money/overview/#2-approve)** it with `POST /api/v1/send-money/approve/`. You can skip this step with `requires_approval: "NO"`.
3. Some bank routes also need an **[OTP confirmation](https://developers.intasend.com/guides/send-money/approval-and-otp/)**.
4. Track the batch with **[status](https://developers.intasend.com/guides/send-money/overview/#3-check-status)**, or receive the result on your `callback_url` or a [webhook](https://developers.intasend.com/guides/webhooks/).

> **Permissions**
Initiating requires an API key (or user) with the **Level-2** or **Administrator** role.
Approving requires **Level-3** or **Administrator**. With this split, one key can prepare
payouts and a different key approves them.

## 1. Initiate

**cURL**

```bash
curl -X POST https://api.intasend.com/api/v1/send-money/initiate/ \
  -H "Authorization: Bearer $INTASEND_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "KES",
    "provider": "MPESA-B2C",
    "requires_approval": "YES",
    "callback_url": "https://example.com/intasend/payouts",
    "batch_reference": "payroll-2026-09",
    "transactions": [
      {"name": "Jane Doe", "account": "254712345678", "amount": "150", "narrative": "Salary"}
    ]
  }'
```

**Python**

```python

resp = requests.post(
    "https://api.intasend.com/api/v1/send-money/initiate/",
    headers={"Authorization": f"Bearer {os.environ['INTASEND_SECRET_KEY']}"},
    json={
        "currency": "KES",
        "provider": "MPESA-B2C",
        "requires_approval": "YES",
        "transactions": [
            {"name": "Jane Doe", "account": "254712345678", "amount": "150", "narrative": "Salary"}
        ],
    },
)
batch = resp.json()
print(batch["tracking_id"], batch["status"])
```

**Node.js**

```js
const res = await fetch('https://api.intasend.com/api/v1/send-money/initiate/', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.INTASEND_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    currency: 'KES',
    provider: 'MPESA-B2C',
    requires_approval: 'YES',
    transactions: [{name: 'Jane Doe', account: '254712345678', amount: '150', narrative: 'Salary'}],
  }),
});
const batch = await res.json();
```

### Request fields

| Field | Required | Description |
|---|---|---|
| `currency` | Yes | Wallet currency to pay from. One of `KES`, `USD`, `EUR`, `GBP`, `GHS`, `NGN`, `UGX`, `TZS`, `XAF`, `XOF`, `RWF`, `CDF`. |
| `provider` | Yes | Payout rail. See [Providers](https://developers.intasend.com/guides/send-money/providers/). |
| `country` | No | ISO-2 destination country. Defaults to `KE`. It is required for cross-border payouts; see [Countries](https://developers.intasend.com/countries). |
| `wallet_id` | No | Wallet to debit. It must match `currency`. Defaults to your settlement wallet for that currency. A working wallet must have `can_disburse` enabled. |
| `requires_approval` | No | `"YES"` (default) or `"NO"`. With `"NO"`, the batch is approved automatically. |
| `callback_url` | No | URL that receives the final batch result. See [Callbacks](https://developers.intasend.com/guides/send-money/overview/#callbacks). |
| `batch_reference` | No | Your own reference for the batch (max 70 characters). |
| `transactions` | Yes | List of transactions (below). |

### Transaction fields

| Field | Required | Description |
|---|---|---|
| `account` | Yes | The destination: phone number, bank account, till, paybill or meter number. **Digits only**: no `+`, dashes or letters. Max 24 characters. |
| `amount` | Yes | Amount to send, up to 2 decimal places. |
| `name` | Depends | Beneficiary name. Required for bank transfers (RTGS/EFT, NG and UG banks). Letters, numbers, spaces, `-`, `_` and `:` only. |
| `narrative` | No | Payment description or remarks. Same character rules as `name`. |
| `bank_code` | Depends | Required for bank payouts. See [bank codes](https://developers.intasend.com/guides/send-money/validate-accounts/#bank-codes). |
| `operator_code` | Depends | Mobile network operator. Required in Tanzania (`VODACOM`, `AIRTEL`, `TIGO`, `HALOTEL`). |
| `account_type` | Depends | `TillNumber` or `PayBill`. Required for `MPESA-B2B`. |
| `account_reference` | Depends | Paybill account number. Required when `account_type` is `PayBill`. Max 20 characters. |
| `notify_phone` | Depends | Phone number that receives the KPLC token. Required for `KPLC`. |
| `category_name` | No | Name of a payout category you've set up, for reporting. |
| `idempotency_key` | No | Unique key per transaction. A repeat request with the same key is rejected, so you can retry safely. |

> **Use idempotency keys**
Set a unique `idempotency_key` on every transaction (for example your internal payout ID).
If a network error makes you unsure whether a request went through, resend it: duplicates
are rejected with `duplicate transaction detected`.

### Response

```json
{
  "file_id": "Y8Q2KLM",
  "tracking_id": "8b0a3d0c-6f4e-4a8e-9a53-2f3c1b1f6e0d",
  "batch_reference": "payroll-2026-09",
  "status": "Preview and approve",
  "status_code": "BP103",
  "nonce": "a1b2c3",
  "wallet": {"wallet_id": "XZ3LKQ", "currency": "KES", "current_balance": "12500.00", "available_balance": "12500.00"},
  "transactions": [
    {
      "transaction_id": "KQ9PLXR",
      "status": "Pending",
      "status_code": "TP101",
      "request_reference_id": "…",
      "name": "Jane Doe",
      "account": "254712345678",
      "amount": "150.00",
      "narrative": "Salary"
    }
  ],
  "charge_estimate": "10.00",
  "total_amount_estimate": "160.00",
  "total_amount": "150.00",
  "transactions_count": 1
}
```

Keep the `tracking_id`: you need it to approve the batch and check its status.

## 2. Approve

Send back the `tracking_id`, the `nonce` and the `transactions` array from the initiate response:

```bash
curl -X POST https://api.intasend.com/api/v1/send-money/approve/ \
  -H "Authorization: Bearer $INTASEND_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_id": "8b0a3d0c-6f4e-4a8e-9a53-2f3c1b1f6e0d",
    "nonce": "a1b2c3",
    "transactions": [ /* the transactions array from initiate */ ]
  }'
```

The batch moves to `CHECKING-ACCOUNT-BALANCE` (`BP104`). IntaSend then debits your wallet and sends each transaction.
You can only approve a batch while it is in `PREVIEW-AND-APPROVE` (`BP103`).

## 3. Check status

```bash
curl -X POST https://api.intasend.com/api/v1/send-money/status/ \
  -H "Authorization: Bearer $INTASEND_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tracking_id": "8b0a3d0c-6f4e-4a8e-9a53-2f3c1b1f6e0d"}'
```

The response adds `actual_charges`, `paid_amount` and `failed_amount`. Each transaction also gets
`provider`, `provider_reference` (for example the M-Pesa receipt number), `provider_account_name` and `charge`.
See [Status codes](https://developers.intasend.com/guides/send-money/status-codes/) for every batch and transaction state.

## Cancel

You can cancel a batch before it starts sending, while it is in `BP103`, `BP104` or `BF105` and
every transaction is still pending:

```bash
curl -X POST https://api.intasend.com/api/v1/send-money/cancel/ \
  -H "Authorization: Bearer $INTASEND_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"file_id": "Y8Q2KLM"}'
```

## Callbacks

If you set `callback_url`, IntaSend POSTs the full batch result (the same body as the status
response) once the batch **completes**, **fails on low balance** or is **cancelled**.

The callback is sent once and is not retried, so don't rely on it alone. Also subscribe to the
`send_money_event` [webhook](https://developers.intasend.com/guides/webhooks/), which fires on every batch state change, includes
your challenge for verification and can be replayed from the dashboard. Keep polling
[status](https://developers.intasend.com/guides/send-money/overview/#3-check-status) as a fallback.

## Refunds of failed transactions

When a batch completes, the amount of any failed transaction, including its charge, is credited back to the wallet you paid from.
