Recipients
Save the people and businesses you pay, with the account details for one payout method.
/recipientsCreate a recipientGET/recipientsList recipientsGET/payouts/requirementsPayout methods and required fieldsGET/payouts/banksBanks and mobile money networksPOST/payouts/validate-accountLook up an account holderA recipient pairs a currency with a payout method (a rail, such as bank, mobile_money or interac) and the destination details that rail needs. Create the recipient once and pay it as often as you like with payouts.
The recommended flow:
Call GET /payouts/requirements to see which rails a currency supports and which destination fields each needs.
For bank and mobile money rails, get the institution code from GET /payouts/banks.
Where supported, confirm the account holder's name with POST /payouts/validate-account.
Payout requirements
GET /payouts/requirements describes every payout corridor: the rails available for a currency, the fields each rail's destination needs, validation patterns, examples and limits. Requires payment.view. Build your recipient forms from it rather than hard-coding fields; it changes as new corridors open.
Prop
Type
curl "https://business-api.migrapay.com/v1/api/payouts/requirements?currency=NGN&available=true" \
-H "Authorization: Bearer $MIGRAPAY_API_KEY"{
"status": "success",
"message": "Recipient requirements retrieved successfully.",
"data": {
"corridors": [
{
"currency": "NGN",
"country": "Nigeria",
"countryCode": "NG",
"prefix": "+234",
"supportsAccountResolve": true,
"limits": { "min": 1000, "max": 10000000 },
"available": true,
"rails": [
{
"key": "bank",
"label": "Bank transfer",
"fields": [
{
"name": "bankCode",
"label": "Bank",
"type": "select",
"required": true,
"pattern": "^[A-Za-z0-9][A-Za-z0-9 _.-]{1,63}$",
"minLength": 2,
"maxLength": 64,
"example": "058",
"helpText": "The recipient's bank, as a code from the bank list for the payout currency. Use the code exactly as the list returns it rather than typing your own.",
"optionsSource": "banks"
},
{
"name": "accountNumber",
"label": "Account number",
"type": "string",
"required": true,
"pattern": "^[0-9]{10}$",
"minLength": 10,
"maxLength": 10,
"example": "0123456789",
"helpText": "The Nigerian account number (NUBAN), exactly 10 digits. Verify the account first to confirm the account holder's name."
},
{
"name": "accountName",
"label": "Account name",
"type": "string",
"required": true,
"pattern": "^[A-Za-zÀ-ÿ0-9][A-Za-zÀ-ÿ0-9 .,'&-]{1,99}$",
"minLength": 2,
"maxLength": 100,
"example": "Ada Obi",
"helpText": "The account holder's name exactly as the bank holds it. Where account verification is available, we look the name up for you; otherwise type it carefully."
}
],
"notes": "Pay out to a local bank account. You need the recipient's bank, account number and account name exactly as the bank holds it. For Nigerian naira accounts you can look up the account name before sending.",
"available": true
}
]
}
]
}
}The example is trimmed to the three required bank fields; the real response lists the optional ones too. Each field has a name (the key to use in destination), label, type (string, number, select, phone or email), required and example, and may add pattern, minLength, maxLength, helpText, options, optionsSource (banks or momo_networks: fetch them from /payouts/banks), checksum (iban, clabe or country) and aliases. Unavailable corridors and rails carry available: false and a reason (CURRENCY_DISABLED or RAIL_DISABLED). limits are per-payout amounts in the destination currency.
Rails
| Rail | Typical use | Required destination fields |
|---|---|---|
bank | Local bank transfer | bankCode, accountNumber, accountName |
mobile_money | Mobile money wallet | mno, accountNumber, accountName |
interac | Interac e-Transfer (CAD) | email |
eft | Canadian EFT | institutionNumber, transitNumber, accountNumber, institutionName |
ach, ach_same_day | US ACH | accountNumber, routingNumber, accountName |
wire | Domestic wire | accountNumber, routingNumber |
swift | International wire | bic, accountNumber |
sepa | SEPA (EUR) | iban, accountName |
faster_payments | UK Faster Payments | sortCode, accountNumber, accountName |
spei | SPEI (MXN) | clabe |
pix | PIX (BRL) | pixKey, accountName |
co_bank_transfer | Colombian bank transfer | documentType, documentNumber, accountNumber, bankCode, accountTypeDetail, phoneNumber |
bre_b | Bre-B (COP) | breBKey, accountName |
Which rails you can use depends on the currency and on what is enabled for your account; the requirements endpoint is the source of truth.
Banks and networks
GET /payouts/banks lists the banks and mobile money networks you can pay into for a currency. Requires payment.view. Use the returned code as destination.bankCode (banks) or destination.mno (mobile money).
Prop
Type
curl "https://business-api.migrapay.com/v1/api/payouts/banks?currency=NGN&type=bank" \
-H "Authorization: Bearer $MIGRAPAY_API_KEY"{
"status": "success",
"message": "Banks retrieved successfully.",
"data": {
"banks": [
{ "code": "044", "name": "Access Bank", "kind": "bank", "country": "NG" },
{ "code": "058", "name": "Guaranty Trust Bank", "kind": "bank", "country": "NG" }
]
}
}Missing currency returns 400 CURRENCY_REQUIRED. The list changes rarely; cache it for a few hours.
Validate an account
POST /payouts/validate-account looks up the name on a bank account so you can show it to your user before saving the recipient. Requires payment.view. Only corridors with supportsAccountResolve: true in the requirements support it.
Prop
Type
curl -X POST https://business-api.migrapay.com/v1/api/payouts/validate-account \
-H "Authorization: Bearer $MIGRAPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "currency": "NGN", "bankCode": "058", "accountNumber": "0123456789" }'{
"status": "success",
"message": "Account validated successfully.",
"data": { "account": { "accountName": "ADA OKAFOR" } }
}Errors: 400 VALIDATION_ERROR (a required field is missing), 400 ACCOUNT_RESOLVE_NOT_SUPPORTED, 422 UNSUPPORTED_CORRIDOR, 422 ACCOUNT_NOT_VERIFIED (the bank could not confirm the account; data.message is a sentence you can show your user).
{
"status": "error",
"message": "ACCOUNT_NOT_VERIFIED",
"data": { "message": "We couldn't confirm this account. Check the bank and account number." }
}Bank-confirmed names (NGN)
For NGN bank recipients the account name is confirmed with the recipient's bank, and the bank's name is the one we use. When you create the recipient we look the account up and store the name the bank holds in destination.accountName, replacing anything you sent (you may leave accountName out). The recipient comes back with verified: true, verifiedName (the bank's name) and verifiedAt.
If the bank cannot confirm the account, the recipient is not created and you get 422 ACCOUNT_NOT_VERIFIED. Check the bank code and account number and try again. Call validate-account first to show your user the name before they save. A payout to an NGN bank recipient that was saved before this check runs the same lookup first and is refused with the same error, before anything is debited.
Create a recipient
POST /recipients saves a recipient. Requires recipient.create.
Prop
Type
curl -X POST https://business-api.migrapay.com/v1/api/recipients \
-H "Authorization: Bearer $MIGRAPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Ada Okafor",
"ownerType": "individual",
"rail": "bank",
"currency": "NGN",
"destination": {
"bankCode": "058",
"accountNumber": "0123456789",
"accountName": "Ada Okafor"
},
"label": "Lagos contractor",
"email": "[email protected]"
}'{
"status": "success",
"message": "Recipient created successfully.",
"data": {
"recipient": {
"id": "8d1e5b2a-6c4f-4a9e-b3d7-000000000001",
"businessId": "b7e4d2c1-5a3f-4c8e-8d2b-000000000001",
"ownerType": "individual",
"displayName": "Ada Okafor",
"rail": "bank",
"currency": "NGN",
"destination": {
"bankCode": "058",
"accountNumber": "0123456789",
"accountName": "ADA OKAFOR"
},
"institutionId": "c2a9f4e7-1b3d-4f6a-9e8c-000000000001",
"screeningStatus": "clear",
"coolingUntil": null,
"status": "active",
"label": "Lagos contractor",
"email": "[email protected]",
"lastUsedAt": null,
"verifiedName": "ADA OKAFOR",
"verifiedAt": "2026-08-12T16:05:42.000Z",
"verified": true,
"createdBy": "e5b8c3d1-7f2a-4b6e-a9d4-000000000001",
"createdAt": "2026-08-12T16:05:42.000Z"
}
}
}Phone numbers in destination are stored in international (E.164) format. createdBy is the id of the API key that created the recipient. verified is true when the account name was confirmed with the bank; verifiedName and verifiedAt are null otherwise. For NGN bank recipients destination.accountName is the bank's name, as above.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR | A field is missing or invalid. data keys look like destination.accountNumber. |
| 422 | RAIL_NOT_AVAILABLE | This rail is not available for the currency. |
| 422 | INSTITUTION_UNKNOWN | bankCode or mno is not recognised. data.field names the field. |
| 422 | ACCOUNT_NOT_VERIFIED | NGN bank: the bank could not confirm the account. data.message explains. |
List recipients
GET /recipients returns all your recipients, newest first. Requires recipient.view.
curl https://business-api.migrapay.com/v1/api/recipients \
-H "Authorization: Bearer $MIGRAPAY_API_KEY"{
"status": "success",
"message": "Recipients retrieved successfully.",
"data": { "recipients": [{ "id": "8d1e5b2a-6c4f-4a9e-b3d7-000000000001", "displayName": "Ada Okafor", "…": "…" }] }
}Each item is a full recipient object, as above.