curl --request POST \
--url https://api.pay.aptahq.com/v1/reference/resolve-bank-account \
--header 'Content-Type: application/json' \
--header 'X-AptaPay-Signature: <api-key>' \
--data '
{
"country": "NG",
"bank_code": "044",
"account_number": "0011223344"
}
'const options = {
method: 'POST',
headers: {'X-AptaPay-Signature': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({country: 'NG', bank_code: '044', account_number: '0011223344'})
};
fetch('https://api.pay.aptahq.com/v1/reference/resolve-bank-account', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/reference/resolve-bank-account"
payload = {
"country": "NG",
"bank_code": "044",
"account_number": "0011223344"
}
headers = {
"X-AptaPay-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}Account number to account name
Turn a bank account number into the account holder’s name, so a user can confirm they are paying the right person. This is the “we found: JANE DOE — is that correct?” step on a payout form, and the single cheapest way to stop money going to a mistyped account number.
Call it after the user picks a bank and types an account number, before POST /v1/payouts. Use the returned name for confirmation only — the gateway re-resolves it at payout time regardless, so a name you cached is never what authorizes the transfer.
Rate-limited PER API KEY, not just per IP: it takes an arbitrary account number and returns a real person’s name, which is an enumeration oracle if one authenticated caller can hammer it. Call it once per user-entered account number, not in a loop. A 429 means you have hit that per-key limit.
bank_account_unresolvable (422 — the number is wrong, and the user can fix it) is kept DISTINCT from provider_error (503 — transient, try again later). Surface them differently: collapsing the two shows “check your account number” to a user whose number was fine, during an outage.
curl --request POST \
--url https://api.pay.aptahq.com/v1/reference/resolve-bank-account \
--header 'Content-Type: application/json' \
--header 'X-AptaPay-Signature: <api-key>' \
--data '
{
"country": "NG",
"bank_code": "044",
"account_number": "0011223344"
}
'const options = {
method: 'POST',
headers: {'X-AptaPay-Signature': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({country: 'NG', bank_code: '044', account_number: '0011223344'})
};
fetch('https://api.pay.aptahq.com/v1/reference/resolve-bank-account', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/reference/resolve-bank-account"
payload = {
"country": "NG",
"bank_code": "044",
"account_number": "0011223344"
}
headers = {
"X-AptaPay-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}Authorizations
v1={hex HMAC-SHA256} over the ten-field canonical string. Sent alongside X-AptaPay-Key, X-AptaPay-Timestamp and X-AptaPay-Nonce — all four are required. OpenAPI can only model one header per scheme, so the other three are described in the Authentication section above.
Body
Response
Resolved.
The single response envelope, used by EVERY endpoint so a consuming app parses one shape everywhere.
200
"OK"
Present on success. Shape varies per endpoint.
Machine-readable error classification. EXACTLY ONE of terminal/retriable/ ambiguous is true. laces_api inferred this from the HTTP status and got it wrong, stranding real money twice (2026-08-15, 2026-08-16). Branch on these booleans, never on the status code:
terminal -> the provider refused. Reverse the debit and tell the user. retriable -> safe to retry with the SAME Idempotency-Key. ambiguous -> the outcome is UNKNOWN. Do NOT reverse. Poll instead.
Show child attributes
Show child attributes