Skip to main content
Corridors and limits may change. Use the quote endpoint to validate amounts before confirming payouts.

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

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 before paying out — corridor and rail availability can change.
CountryCurrencyLOCAL_BANKLOCAL_MOMO
Nigeria (NG)NGNYes
Kenya (KE)KESYesYes
South Africa (ZA)ZARYes
Tanzania (TZ)TZSYesYes
Uganda (UG)UGXYesYes
Rwanda (RW)RWFYesYes
Malawi (MW)MWKYesYes
Zambia (ZM)ZMWYesYes
Botswana (BW)BWPYesYes
Cameroon (CM)XAFYesYes
Benin (BJ)XOFYes
Burkina Faso (BF)XOFYes
Côte d’Ivoire (CI)XOFYes
Mali (ML)XOFYes
Senegal (SN)XOFYes
Togo (TG)XOFYes
Gabon (GA)XAFYes
Republic of the Congo (CG)XAFYes
DR Congo (CD)CDFYes
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.

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)

{
  "label": "Payroll — Jane Doe",
  "rail": "LOCAL_BANK",
  "details": {
    "country": "NG",
    "accountNumber": "0123456789",
    "accountName": "Jane Doe",
    "bankCode": "058",
    "bankName": "GTBank"
  }
}
bankCode is the id returned by GET /v1/business/payouts/networks?country=NG&rail=LOCAL_BANK.

Local mobile money (LOCAL_MOMO)

{
  "label": "Field agent — Payee",
  "rail": "LOCAL_MOMO",
  "details": {
    "country": "CM",
    "phoneNumber": "+237612345678",
    "accountName": "Payee Name",
    "networkId": "network_opaque_id",
    "networkName": "Network Name"
  }
}
phoneNumber must be E.164 (e.g. +254712345678). networkId is the id returned by GET /v1/business/payouts/networks?country=CM&rail=LOCAL_MOMO.

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)

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);

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.
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',
});

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:
{
  "country": "NG",
  "accountNumber": "0123456789",
  "networkId": "058"
}
Response:
{
  "accountNumber": "0123456789",
  "accountName": "JANE DOE",
  "accountBank": "GTBank"
}
This endpoint validates country="NG" and rejects other countries. Mobile money has no equivalent “name resolve” API.

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

Settlement webhooks

Local-rail payouts emit the same business payout events as other rails. Subscribe via 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:
{
  "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):
{
  "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"
}
Full integration guides and webhook event reference will be expanded in a later docs release. For OpenAPI field-level detail, see the API Reference tab.