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

# Local payout corridors

> Supported destination countries and recipient field requirements for LOCAL_BANK and LOCAL_MOMO payouts.

<Warning>
  Corridors and limits may change. Use the quote endpoint to validate amounts before confirming payouts.
</Warning>

## Overview

`LOCAL_BANK` and `LOCAL_MOMO` let you settle to a **local bank account** or **mobile money wallet** in a supported corridor. Your payout is funded from your business **USDC balance** using a locked FX quote (quote → confirm).

Jump to:

* Supported corridors table (below)
* [Payout quote](/payments/payouts/quote)

## Supported corridors

19 destination countries are supported. `LOCAL_BANK` is available where we have local bank-transfer coverage; `LOCAL_MOMO` is available where the corridor has active mobile-money payout channels. Always confirm an amount with the [quote endpoint](/payments/payouts/quote) before paying out — corridor and rail availability can change.

| Country                    | Currency | `LOCAL_BANK` | `LOCAL_MOMO` |
| -------------------------- | -------- | :----------: | :----------: |
| Nigeria (NG)               | NGN      |      Yes     |       —      |
| Kenya (KE)                 | KES      |      Yes     |      Yes     |
| South Africa (ZA)          | ZAR      |      Yes     |       —      |
| Tanzania (TZ)              | TZS      |      Yes     |      Yes     |
| Uganda (UG)                | UGX      |      Yes     |      Yes     |
| Rwanda (RW)                | RWF      |      Yes     |      Yes     |
| Malawi (MW)                | MWK      |      Yes     |      Yes     |
| Zambia (ZM)                | ZMW      |      Yes     |      Yes     |
| Botswana (BW)              | BWP      |      Yes     |      Yes     |
| Cameroon (CM)              | XAF      |      Yes     |      Yes     |
| Benin (BJ)                 | XOF      |       —      |      Yes     |
| Burkina Faso (BF)          | XOF      |       —      |      Yes     |
| Côte d'Ivoire (CI)         | XOF      |       —      |      Yes     |
| Mali (ML)                  | XOF      |       —      |      Yes     |
| Senegal (SN)               | XOF      |       —      |      Yes     |
| Togo (TG)                  | XOF      |       —      |      Yes     |
| Gabon (GA)                 | XAF      |       —      |      Yes     |
| Republic of the Congo (CG) | XAF      |       —      |      Yes     |
| DR Congo (CD)              | CDF      |       —      |      Yes     |

<Note>
  The West Africa (XOF), additional Central Africa (XAF), and DR Congo (CDF) corridors are **mobile-money only** — pay out with `LOCAL_MOMO`. Use `GET /v1/business/payouts/networks?country=&rail=LOCAL_MOMO` to list the available carriers/networks for the recipient before confirming.
</Note>

## Recipient shapes per rail

You can pay out in one of two ways:

* **Saved recipient (3 calls):** create a recipient with `POST /v1/business/payouts/recipients`, then reference it by `recipientId` on every payout. Best when you pay the same payee repeatedly.
* **Inline recipient (2 calls):** pass a `recipient` object directly on `POST /v1/business/payouts` — no pre-created recipient needed. The inline recipient is sanctions-screened and saved to your recipient book automatically, so you can reuse it by `recipientId` later.

Provide **exactly one** of `recipientId` or `recipient` on confirm. The inline `recipient` uses the same fields as the saved-recipient `details` object (shown below).

### Get the network / bank list first

For `LOCAL_BANK` and `LOCAL_MOMO`, you must select an **opaque upstream network identifier** using:

`GET /v1/business/payouts/networks?country=NG&rail=LOCAL_BANK`

That endpoint returns a list of supported banks or mobile-money networks for the corridor. Use the returned `id` as:

* `bankCode` for `LOCAL_BANK`
* `networkId` for `LOCAL_MOMO`

Recipients are created with `POST /v1/business/payouts/recipients`. Use `label` to identify the payee in your dashboard.

### Local bank (`LOCAL_BANK`)

```json theme={null}
{
  "label": "Payroll — Jane Doe",
  "rail": "LOCAL_BANK",
  "details": {
    "country": "NG",
    "accountNumber": "0123456789",
    "accountName": "Jane Doe",
    "bankCode": "058",
    "bankName": "GTBank"
  }
}
```

<Note>
  `bankCode` is the `id` returned by `GET /v1/business/payouts/networks?country=NG&rail=LOCAL_BANK`.
</Note>

### Local mobile money (`LOCAL_MOMO`)

```json theme={null}
{
  "label": "Field agent — Payee",
  "rail": "LOCAL_MOMO",
  "details": {
    "country": "CM",
    "phoneNumber": "+237612345678",
    "accountName": "Payee Name",
    "networkId": "network_opaque_id",
    "networkName": "Network Name"
  }
}
```

<Note>
  `phoneNumber` must be E.164 (e.g. `+254712345678`). `networkId` is the `id` returned by
  `GET /v1/business/payouts/networks?country=CM&rail=LOCAL_MOMO`.
</Note>

## Cross-currency quote → confirm flow

Local-rail payouts use a two-step flow to lock FX and fees:

1. `POST /v1/business/payouts/quote` returns a signed `quoteToken` (valid for 60 seconds).
2. `POST /v1/business/payouts` confirms the payout by passing `quoteToken` back, along with **either** a saved `recipientId` **or** an inline `recipient` object.

If the quote is expired, invalid, or issued for a different business workspace, the create call fails.

### Quote and confirm (Node + Python)

<CodeGroup>
  ```typescript Node.js theme={null}
  import { Axra } from '@useaxra/node';

  const axra = new Axra(process.env.AXRA_SECRET_KEY!);

  // 1) Quote (locks FX + fee for 60s)
  const quote = await axra.payouts.quote({
    rail: 'LOCAL_BANK',
    country: 'NG',
    currency: 'NGN',
    srcAmount: '100.00',
  });

  // 2) Confirm (references locked quote by token)
  const payout = await axra.payouts.create({
    rail: 'LOCAL_BANK',
    quoteToken: quote.quoteToken,
    recipientId: 'rec_01H...',
    idempotencyKey: 'payout-ng-2026-06-02-0001',
  });

  console.log(payout.id, payout.status);
  ```

  ```python Python theme={null}
  import os
  from axra import Axra

  client = Axra(os.environ["AXRA_SECRET_KEY"])

  # 1) Quote (locks FX + fee for 60s)
  quote = client.payouts.quote(
      rail="LOCAL_BANK",
      country="NG",
      currency="NGN",
      src_amount="100.00",
  )

  # 2) Confirm (references locked quote by token)
  payout = client.payouts.create(
      rail="LOCAL_BANK",
      quote_token=quote.quote_token,
      recipient_id="rec_01H...",
      idempotency_key="payout-ng-2026-06-02-0001",
  )

  print(payout.id, payout.status)
  ```
</CodeGroup>

### Single-call payout with an inline recipient

Skip the separate recipient-creation call by passing the recipient inline on confirm. Exactly one of `recipientId` or `recipient` is required. The inline recipient is sanctions-screened on submit and saved to your recipient book for reuse.

<CodeGroup>
  ```typescript Node.js theme={null}
  const quote = await axra.payouts.quote({
    rail: 'LOCAL_BANK',
    country: 'NG',
    currency: 'NGN',
    srcAmount: '100.00',
  });

  const payout = await axra.payouts.create({
    rail: 'LOCAL_BANK',
    quoteToken: quote.quoteToken,
    recipient: {
      country: 'NG',
      accountNumber: '0123456789',
      accountName: 'Jane Doe',
      bankCode: '058',
      bankName: 'Example Bank',
    },
    idempotencyKey: 'payout-ng-2026-06-02-0001',
  });
  ```

  ```python Python theme={null}
  quote = client.payouts.quote(
      rail="LOCAL_BANK",
      country="NG",
      currency="NGN",
      src_amount="100.00",
  )

  payout = client.payouts.create(
      rail="LOCAL_BANK",
      quote_token=quote.quote_token,
      recipient={
          "country": "NG",
          "account_number": "0123456789",
          "account_name": "Jane Doe",
          "bank_code": "058",
          "bank_name": "Example Bank",
      },
      idempotency_key="payout-ng-2026-06-02-0001",
  )
  ```
</CodeGroup>

### Quote-token error codes

These codes can be returned when confirming a payout (`POST /v1/business/payouts`) with a `quoteToken`:

* `INVALID_QUOTE_TOKEN`: Token is malformed or signature validation failed.
* `QUOTE_EXPIRED`: Token is older than the 60-second TTL.
* `QUOTE_BUSINESS_MISMATCH`: Token was issued for a different business workspace.
* `INSUFFICIENT_PAYOUT_LIQUIDITY`: Liquidity is temporarily unavailable to settle the corridor.
* `TREASURY_FROZEN`: Payouts are temporarily disabled for treasury safety checks.
* `PAYOUT_CORRIDOR_NOT_ENABLED`: Your business is not enabled for this corridor.

## NG bank-name resolve (Nigeria only)

To validate an NG NUBAN and resolve the account-holder name:

`POST /v1/business/payouts/resolve-account`

Request:

```json theme={null}
{
  "country": "NG",
  "accountNumber": "0123456789",
  "networkId": "058"
}
```

Response:

```json theme={null}
{
  "accountNumber": "0123456789",
  "accountName": "JANE DOE",
  "accountBank": "GTBank"
}
```

<Note>
  This endpoint validates `country="NG"` and rejects other countries. Mobile money has no equivalent “name resolve” API.
</Note>

## Idempotency

`idempotencyKey` is **required** on `POST /v1/business/payouts`.

* Reusing the same key returns the original payout record for that key.
* Use a new key for every user-initiated payout attempt to avoid accidental deduping.

## 2FA threshold

The **\$10,000 USD-equivalent** TOTP requirement applies **only to interactive dashboard payouts**. When a human initiates a payout at or above this amount from the Axra dashboard, a fresh TOTP code (`twoFactorCode`) is required, and a missing code returns `401` with `code: "TWO_FACTOR_REQUIRED"`.

<Note>
  **Server-to-server payouts (API key) are exempt from the 2FA gate.** For programmatic payouts there is no interactive TOTP step at any amount — the controls are your secret API key, per-corridor velocity caps, and sanctions screening on every recipient. You never need to send `twoFactorCode` from a server integration.
</Note>

## Settlement webhooks

Local-rail payouts emit the same business payout events as other rails. Subscribe via [Webhooks](/manage/webhooks).

Event types:

* `payout.initiated` — payout accepted and ledger-debited (status `CREATED`).
* `payout.submitted` — dispatched to the payout provider (status `SUBMITTED`).
* `payout.settled` — funds delivered to the recipient (status `SETTLED`).
* `payout.failed` — dispatch or settlement failed; the debit is reversed (status `FAILED`).
* `payout.cancelled` — a `CREATED` payout was cancelled before dispatch (status `CANCELLED`).
* `payout.review` — payout held for compliance/admin review (status `PENDING_REVIEW`).

The `data.object` of each event is the same shape as `GET /v1/business/payouts/{id}`.

## Fee model

Merchants see **only the aggregated total** via `GET /v1/business/psp-pricing`. Rates are set per-merchant (your SLA), and the quote response includes the total Axra fee for the corridor.

## Confirm payout (raw API)

After quoting, confirm with **either** a saved `recipientId` **or** an inline `recipient` object (exactly one).

Saved recipient:

```json theme={null}
{
  "rail": "LOCAL_BANK",
  "quoteToken": "<from quote>",
  "recipientId": "<saved recipient id>",
  "idempotencyKey": "merchant-supplied-unique-key"
}
```

Inline recipient (created, screened, and saved in the same call):

```json theme={null}
{
  "rail": "LOCAL_BANK",
  "quoteToken": "<from quote>",
  "recipient": {
    "label": "Jane Doe",
    "rail": "LOCAL_BANK",
    "details": {
      "country": "NG",
      "accountNumber": "0123456789",
      "accountName": "Jane Doe",
      "bankCode": "058",
      "bankName": "Example Bank"
    }
  },
  "idempotencyKey": "merchant-supplied-unique-key"
}
```

<Note>
  Full integration guides and webhook event reference will be expanded in a later docs release. For OpenAPI field-level detail, see the [API Reference](/api-reference) tab.
</Note>
