This page documents the key data models returned and accepted by the API, including all enumerated values and field-level details.
Customer
| Field | Type | Description |
|---|
customerId | string | Unique identifier for the customer |
name | string | Full name of the customer |
email | string (email) | Email address |
phone | string | Phone number in E.164 format (e.g. +2348192837465) |
countryCode | string | ISO 3166-1 alpha-2 country code (e.g. NG) |
meta | object | Optional metadata you attach to the customer |
createdAt | string (ISO 8601) | Timestamp when the customer was created |
updatedAt | string (ISO 8601) | Timestamp of the last update |
On create and update requests, supply the customer’s full name as fullName; responses return it as name. KYC details are submitted separately through the Update KYC endpoint.
Transaction
| Field | Type | Description |
|---|
transactionId | string | Unique identifier for the transaction |
customerId | string | ID of the customer who owns this transaction |
sourceId | string | ID of the source payment method |
destinationId | string | ID of the destination payment method |
sourceAmount | string | Amount debited from the source wallet |
sourceCurrency | string | ISO 4217 currency code of the source wallet (e.g. USD) |
destinationAmount | string | Amount credited to the destination |
destinationCurrency | string | ISO 4217 currency code of the destination (e.g. NGN) |
type | string | Transaction type. See Transaction Types |
channel | string | Payment channel of the transaction. See Transaction Channels. |
status | string | Current status. See Transaction Statuses |
merchantReference | string | Merchant-supplied reference (mirrors meta.reference from the create request) |
meta | object | Metadata attached to the transaction. See TransactionMeta |
createdAt | string (ISO 8601) | Timestamp when the transaction was created |
updatedAt | string (ISO 8601) | Timestamp of the last update |
Transaction Types
| Value | Description |
|---|
WITHDRAW | Send funds from the Afriex wallet to a recipient’s payment method |
DEPOSIT | Receive funds into the Afriex wallet |
SWAP | Convert funds between currencies within the Afriex wallet |
Transaction Channels
| Channel | Description |
|---|
BANK_ACCOUNT | Local bank account transfer |
SWIFT | International SWIFT wire transfer |
MOBILE_MONEY | Mobile money wallet (e.g. M-Pesa, MTN MoMo) |
UPI | Unified Payments Interface (India) |
INTERAC | Interac e-Transfer (Canada) |
WE_CHAT | WeChat Pay |
CARD | Card payment |
CRYPTO | Crypto rail |
VIRTUAL_BANK_ACCOUNT | Hosted virtual bank account |
POOL_ACCOUNT | Shared pool account for incoming deposits |
ACH_BANK_ACCOUNT | US ACH bank transfer |
PAYBILL_TILL | Paybill / Till (M-Pesa business numbers) |
RFP | Request for Payment |
VIRTUAL_CARD | Virtual card |
ALIPAY | Alipay |
WALLET | Afriex wallet (internal leg) |
Transaction Statuses
| Status | Description |
|---|
PENDING | Transaction has been received and is awaiting processing |
PROCESSING | Transaction is actively being processed |
SUCCESS | Transaction settled successfully |
FAILED | Transaction failed. Check meta.failureReason for the reason code. See Failure reasons. |
CANCELLED | Transaction was cancelled before processing |
REFUNDED | Funds were returned to the sender |
RETRY | Transaction is being automatically retried |
UNKNOWN | Status could not be determined. Contact support. |
SCHEDULED | Transaction is scheduled to be processed later |
CUSTOMER_ACTION_REQUIRED | Transaction is paused pending an action from the customer |
REJECTED | Transaction was rejected after review |
IN_REVIEW | Transaction is under manual review |
DISPUTED | A dispute has been raised against the transaction |
DISPUTE_RESOLVED | The dispute has been resolved |
DISPUTE_WON | The dispute was resolved in your favour |
DISPUTE_LOST | The dispute was resolved against you |
DISPUTE_EVIDENCE_SUBMITTED | Evidence has been submitted for the dispute |
Subscribe to Transaction Events to receive real-time status updates instead of polling the API.
The meta object passed when creating a transaction. idempotencyKey and reference are required.
| Field | Type | Required | Description |
|---|
idempotencyKey | string | Yes | Unique key to prevent duplicate transactions. Use a UUID or your own unique identifier. |
reference | string | Yes | Your internal reference for the transaction (e.g. order ID). Returned on the transaction as merchantReference. |
narration | string | No | Human-readable reason or description for the transaction |
invoice | string | No | Base64-encoded invoice document to attach to the transaction |
{
"meta": {
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
"reference": "ORDER-12345",
"narration": "Payment for invoice #12345",
"invoice": "base64EncodedStringHere"
}
}
Always provide a unique idempotencyKey per transaction attempt. If you retry a failed request with the same key, Afriex will return the original transaction instead of creating a duplicate.
Afriex also sets fields on meta after the transaction is created. These echo alongside any fields you passed on create.
| Field | Type | Description |
|---|
otpRequired | boolean | true when the customer must complete an OTP challenge before the transaction can settle. See Simulating OTP-required deposits. |
failureReason | object | Present only when status is FAILED or REJECTED. Carries a stable code, a customer-safe message, and a retryable flag. See Failure reasons. |
Failure reasons
When a transaction reaches FAILED or REJECTED, meta.failureReason explains why using a stable AFX_* code and a customer-safe message. Branch on code rather than the underlying rail so your integration stays stable across routing changes.
| Field | Type | Description |
|---|
code | string | Stable AFX_* failure code. Safe to switch on; the set grows over time but existing values do not change meaning. |
message | string | Customer-safe short description of the failure. Suitable for display; do not parse. Branch on code instead. |
retryable | boolean | true when re-submitting the same request (or, for AFX_PROPOSAL_EXPIRED, starting a fresh proposal) may succeed. false when the caller must change the request before retrying. |
{
"meta": {
"failureReason": {
"code": "AFX_VELOCITY_LIMIT_EXCEEDED",
"message": "This transaction exceeds the allowed velocity limit. Try again later.",
"retryable": true
}
}
}
| Code | Meaning |
|---|
AFX_REQUEST_FAILED | Generic failure while processing the request. Usually retryable. |
AFX_SYSTEM_ERROR | Internal Afriex error. Retry with backoff. |
AFX_SERVICE_UNAVAILABLE | A downstream service was unavailable. Retry with backoff. |
AFX_INVALID_CURRENCY | The requested currency is not supported for this route. |
AFX_INVALID_AMOUNT | The requested amount is invalid or outside allowed limits. |
AFX_INVALID_RECIPIENT | The recipient payment method is invalid or incomplete. |
AFX_RECIPIENT_NOT_FOUND | The recipient account could not be located at the destination institution. |
AFX_BENEFICIARY_RESTRICTED | The beneficiary is restricted and cannot receive funds. |
AFX_INVALID_SENDER | The sender’s payment method is invalid or ineligible. |
AFX_INVALID_REQUEST | The request payload is malformed or inconsistent. |
AFX_VELOCITY_LIMIT_EXCEEDED | The customer has exceeded a velocity limit. Retry after the window resets. |
AFX_AMOUNT_LIMIT_EXCEEDED | The request exceeds a per-transaction, daily, or account limit. |
AFX_PAYMENT_FAILED | The payment leg failed at the payment rail. |
AFX_COMPLIANCE_REJECTED | The transaction was rejected during compliance review. |
AFX_PROPOSAL_EXPIRED | The rate proposal expired before the transaction was committed. Start a fresh proposal. |
The AFX_* set may grow over time as new failure modes are surfaced. Existing values will not change meaning. Handle unknown codes by falling back to message.
PaymentMethod
Empty fields are omitted from responses. Account-shaped channels (BANK_ACCOUNT, MOBILE_MONEY, SWIFT, etc.) populate accountName, accountNumber, and institution; the CARD channel instead populates last4, brand, expiration, and cardName.
| Field | Type | Description |
|---|
paymentMethodId | string | Unique identifier for the payment method |
customerId | string | ID of the customer who owns this payment method |
channel | string | Payment channel. See Payment Channels |
countryCode | string | ISO 3166-1 alpha-2 country code |
currency | string | ISO 4217 currency code. Use this instead of inferring from countryCode, which is ambiguous (e.g. a USD account in a non-US country) |
capabilities | string[] | Operations this method is enabled for (e.g. DEPOSIT, WITHDRAW). Filterable via the capabilities query parameter on List Payment Methods |
reference | string | Identifier used to reconcile incoming deposits against |
accountName | string | Account holder name. Present for account-shaped channels |
accountNumber | string | Account number. Present for account-shaped channels |
institution | object | Bank or provider details. See PaymentMethodInstitution |
recipient | object | Recipient contact details. See PaymentMethodRecipient |
transaction | object | Per-transaction overrides. See PaymentMethodTransaction |
status | string | Lifecycle status. See Payment Method Statuses |
last4 | string | Last 4 digits of the card. CARD channel only |
brand | string | Card brand (e.g. Visa, MasterCard). CARD channel only |
expiration | object | Card expiry with month and year. CARD channel only |
cardName | string | Name on the card. CARD channel only |
expiresInMinutes | integer | Minutes until a dynamic virtual account expires, when applicable |
amount | number | Requested amount for a dynamic virtual account, when applicable |
extra | object | Additional channel-specific properties |
meta | object | Optional metadata attached to the payment method |
Payment Channels
| Channel | Description |
|---|
BANK_ACCOUNT | Local bank account transfer |
MOBILE_MONEY | Mobile money wallet (e.g. M-Pesa, MTN MoMo) |
SWIFT | International SWIFT wire transfer |
INTERAC | Interac e-Transfer (Canada) |
UPI | Unified Payments Interface (India) |
WE_CHAT | WeChat Pay |
ALIPAY | Alipay |
CARD | Card payment |
CRYPTO | Crypto rail |
VIRTUAL_BANK_ACCOUNT | Hosted virtual bank account |
POOL_ACCOUNT | Shared pool account for incoming deposits |
ACH_BANK_ACCOUNT | US ACH bank transfer |
PAYBILL_TILL | Paybill / Till (M-Pesa business numbers) |
RFP | Request for Payment |
VIRTUAL_CARD | Virtual card |
Not every channel is valid when creating a payment method. The Create Payment Method endpoint accepts a narrower set of channels; the list above covers all channels that can appear on a returned payment method.
Payment Method Statuses
| Status | Description |
|---|
active | Payment method is active and usable |
pending | Payment method is awaiting activation or verification |
deleted | Payment method has been deleted |
expired | Payment method has expired (e.g. an expired card) |
blocked | Payment method has been blocked |
PaymentMethodRecipient
Recipient contact details attached to a payment method, when supplied.
| Field | Type | Description |
|---|
recipientName | string | Name of the recipient |
recipientEmail | string (email) | Email address of the recipient |
recipientPhone | string | Phone number of the recipient in E.164 format |
recipientAddress | string | Address of the recipient |
PaymentMethodTransaction
Optional per-transaction details attached to a payment method.
| Field | Type | Description |
|---|
transactionNarration | string | Optional reason or description for the transaction |
transactionInvoice | string | Base64-encoded invoice for the transaction |
PaymentMethodInstitution
| Field | Type | Description |
|---|
institutionId | string | Unique ID of the bank or mobile money provider |
institutionName | string | Display name of the institution |
institutionCode | string | Institution code (e.g. bank code) |
institutionAddress | string | Physical address of the institution, if required |
Use the List Institutions endpoint to retrieve valid institutions for a given country and channel.
Error Response
All API errors return a consistent JSON body:
| Field | Type | Description |
|---|
code | string | Machine-readable error code (e.g. AUTHENTICATION_ERROR) |
error | string | Short error category (e.g. Bad Request) |
details.errorMessage | string | Specific error reason (e.g. PHONE_COUNTRY_MISMATCH) |
details.friendlyMessage | string | User-friendly message you can display to end users |
details.data | object | Optional caller-safe context. On a customer-create uniqueness conflict (EMAIL_ALREADY_EXISTS, PHONE_NUMBER_ALREADY_EXISTS) contains { customerId } for the existing customer so you can adopt it without a follow-up lookup |
{
"code": "INVALID_BUSINESS_CUSTOMER_REQUEST",
"error": "Bad Request",
"details": {
"errorMessage": "PHONE_COUNTRY_MISMATCH",
"friendlyMessage": "The phone number does not match the customer's country."
}
}
Common error codes
| Code | HTTP Status | Description |
|---|
AUTHENTICATION_ERROR | 401 | API key is missing or invalid |
INVALID_BUSINESS_CUSTOMER_REQUEST | 400 | Customer request validation failed |
PHONE_COUNTRY_MISMATCH | 400 | Phone number country does not match countryCode |
EMAIL_ALREADY_EXISTS | 409 | A customer with this email already exists. details.data.customerId returns the existing customer’s id. |
PHONE_NUMBER_ALREADY_EXISTS | 409 | A customer with this phone number already exists. details.data.customerId returns the existing customer’s id. |
NOT_FOUND | 404 | The requested resource does not exist |
RATE_LIMIT_EXCEEDED | 429 | Too many requests. Slow down and retry with backoff. |