> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useyala.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Initiate Swap

> Move funds between two wallets of your business. The source wallet is debited and the destination wallet is credited in one request.

## Overview

Swaps funds between two wallets that belong to the authenticated business. There is no beneficiary and no payout fee.

* Debit the source wallet and credit the destination wallet in one request
* The currency pair must already be enabled for your business (`GET /pairs`)
* `amountToBeSent` must match the current rate for `swapAmount`
* A new swap returns partner status `PROCESSING`

Wallet ids come from [Get Wallet Balances](/api-reference/endpoint/get-wallets) (`walletId`). The business is taken from your API key. Do not send `businessId` in the body.

## Required Headers

| Header            | Required | Description                                                                                     |
| ----------------- | -------- | ----------------------------------------------------------------------------------------------- |
| `Idempotency-Key` | **Yes**  | Client-generated unique key. Same rules as [Initiate Payout](/api-reference/endpoint/initiate). |
| `x-api-key`       | Yes      | Your API key                                                                                    |

<Warning>
  **Idempotency-Key is required.** A missing header returns `400 Bad Request`.
</Warning>

## Request

```http theme={null}
POST /v1/payout-api/payouts/swap HTTP/1.1
Host: gateway.staging.useyala.com
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
x-api-key: <YOUR_API_KEY>
```

### Request Body

```json theme={null}
{
  "sourceWalletId": "660e8400-e29b-41d4-a716-446655440001",
  "destinationWalletId": "550e8400-e29b-41d4-a716-446655440000",
  "swapAmount": 1000,
  "swapCurrency": "USD",
  "amountToBeSent": 1500000,
  "currencyToBeSent": "NGN",
  "reference": "SWAP-REF-001"
}
```

`amountToBeSent` in the example is illustrative. Send the destination amount that matches the live rate, or the API returns `400` with message `Invalid payout amount`.

### Request Fields

| Field                 | Type          | Required | Description                                                                                                                    |
| --------------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `sourceWalletId`      | string (UUID) | Yes      | Wallet to debit. Must belong to your business and its currency must equal `swapCurrency`.                                      |
| `destinationWalletId` | string (UUID) | Yes      | Wallet to credit. Must belong to your business, differ from the source wallet, and its currency must equal `currencyToBeSent`. |
| `swapAmount`          | number        | Yes      | Positive amount debited from the source wallet.                                                                                |
| `swapCurrency`        | string        | Yes      | ISO 4217 currency of `swapAmount` (for example `USD`).                                                                         |
| `amountToBeSent`      | number        | Yes      | Positive amount credited to the destination wallet. Must match the current rate for `swapAmount`.                              |
| `currencyToBeSent`    | string        | Yes      | ISO 4217 currency of `amountToBeSent` (for example `NGN`).                                                                     |
| `reference`           | string        | No       | Your reference. When omitted, Yala generates one.                                                                              |

## Response

`201 Created`

```json theme={null}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "payoutRef": "PAY-2024-001",
  "businessId": "20000000-0000-0000-0000-000000000001",
  "sourceWalletId": "660e8400-e29b-41d4-a716-446655440001",
  "destinationWalletId": "550e8400-e29b-41d4-a716-446655440000",
  "sourceAmount": 1000,
  "sourceCurrency": "USD",
  "destinationAmount": 1500000,
  "destinationCurrency": "NGN",
  "exchangeRate": 1500,
  "transactionFees": 0,
  "status": "PROCESSING",
  "method": "SWAP",
  "createdAt": "2024-01-21T12:00:00.000Z"
}
```

### Response Fields

| Field                 | Type              | Description                                                                                      |
| --------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
| `id`                  | string (UUID)     | Swap payout id. Use it with `GET /v1/payout-api/payouts/{id}` and webhook `payoutId`.            |
| `payoutRef`           | string            | Payout reference (`reference` when you sent one).                                                |
| `businessId`          | string            | Your business id.                                                                                |
| `sourceWalletId`      | string            | Wallet that was debited.                                                                         |
| `destinationWalletId` | string            | Wallet that was credited.                                                                        |
| `sourceAmount`        | number            | Amount debited.                                                                                  |
| `sourceCurrency`      | string            | Source currency.                                                                                 |
| `destinationAmount`   | number            | Amount credited.                                                                                 |
| `destinationCurrency` | string            | Destination currency.                                                                            |
| `exchangeRate`        | number            | Rate applied to this swap.                                                                       |
| `transactionFees`     | number            | Always `0`. Swaps do not charge a payout fee.                                                    |
| `status`              | string            | `PROCESSING` on create. Later `SUCCESSFUL` or `REJECTED` when ops confirms or reverses the swap. |
| `method`              | string            | Always `SWAP`.                                                                                   |
| `createdAt`           | string (ISO 8601) | Creation time.                                                                                   |

<Tip>
  Save `id`. Load it later with [Get Payout or Swap by ID](/api-reference/endpoint/get-payout). That is the same route used for payouts. List only swaps with [List Payouts and Swaps](/api-reference/endpoint/list-payouts) and `view=swaps`.

  A swap is settled between your wallets immediately, and `status` stays `PROCESSING` until Yala ops marks it `SUCCESSFUL` or reverses it (`REJECTED`).
</Tip>

## Idempotency

Same behavior as initiate:

* **Same key + same body**: returns the original swap
* **Same key + different body**: `409 Conflict`
* **Missing key**: `400 Bad Request`

Retry a network failure with the same key and the same body.

## Error Responses

### 400 Bad Request - Missing Idempotency Key

```json theme={null}
{
  "statusCode": 400,
  "message": "Idempotency-Key header is required",
  "error": "MISSING_IDEMPOTENCY_KEY"
}
```

### 400 Bad Request - Pair not enabled

```json theme={null}
{
  "statusCode": 400,
  "message": "Currency pair is not enabled for this business. Please enable it in your dashboard.",
  "error": "CORRIDOR_NOT_ENABLED"
}
```

Enable the pair in the dashboard, or with `PATCH /v1/payout-api/payouts/currency-pairs/{currencyPairId}/toggle`, then retry.

### 400 Bad Request - Amount does not match the rate

```json theme={null}
{
  "statusCode": 400,
  "message": "Invalid payout amount"
}
```

Read the current rate from `GET /pairs`, recompute `amountToBeSent`, and send a new idempotency key.

### 400 Bad Request - Same wallet or currency mismatch

| `error`                        | When                                                     |
| ------------------------------ | -------------------------------------------------------- |
| `SWAP_WALLETS_MUST_DIFFER`     | `sourceWalletId` and `destinationWalletId` are the same. |
| `INVALID_SOURCE_CURRENCY`      | Source wallet currency is not `swapCurrency`.            |
| `INVALID_DESTINATION_CURRENCY` | Destination wallet currency is not `currencyToBeSent`.   |
| `INSUFFICIENT_FUNDS`           | Source wallet balance is below `swapAmount`.             |
| `UNSUPPORTED_CURRENCY_PAIR`    | The pair is not an active Yala pair.                     |

### 404 Not Found - Wallet

| `error`                        | When                                                               |
| ------------------------------ | ------------------------------------------------------------------ |
| `SOURCE_WALLET_NOT_FOUND`      | Source wallet is missing or does not belong to this business.      |
| `DESTINATION_WALLET_NOT_FOUND` | Destination wallet is missing or does not belong to this business. |

### 409 Conflict - Idempotency key reused

```json theme={null}
{
  "statusCode": 409,
  "message": "This idempotency key has already been used for another payout request. Use the same key to retrieve the original response, or use a new key for a new payout.",
  "error": "IDEMPOTENCY_KEY_CONFLICT"
}
```

## Usage Example

```javascript theme={null}
const idempotencyKey = crypto.randomUUID();

const walletsResponse = await fetch(
  'https://gateway.staging.useyala.com/v1/payout-api/payouts/wallets',
  { headers: { 'x-api-key': apiKey } },
);
const { data: wallets } = await walletsResponse.json();
const usdWallet = wallets.find((wallet) => wallet.currency === 'USD');
const ngnWallet = wallets.find((wallet) => wallet.currency === 'NGN');

const response = await fetch(
  'https://gateway.staging.useyala.com/v1/payout-api/payouts/swap',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
      'x-api-key': apiKey,
    },
    body: JSON.stringify({
      sourceWalletId: usdWallet.walletId,
      destinationWalletId: ngnWallet.walletId,
      swapAmount: 1000,
      swapCurrency: 'USD',
      amountToBeSent: 1500000,
      currencyToBeSent: 'NGN',
    }),
  },
);

const swap = await response.json();
console.log(swap.id, swap.status, swap.exchangeRate);
```

## Webhooks

Creating a swap emits `payout.status.changed`. `newStatus` is `PROCESSING`. Later confirmation or reversal uses the same webhook with `SUCCESSFUL` or `REJECTED`. The same subscription URL also receives `wallet.debited` and `wallet.credited` for the two wallet movements.

See the [Webhooks guide](/guides/webhooks).

## Notes

* Funds move at creation. There is no separate approval step before the wallets are updated.
* No supporting document is required.
* Track the swap with `GET /v1/payout-api/payouts/swaps/{id}`. `method` is `SWAP`.
