curl --request GET \
--url https://api.pay.aptahq.com/v1/balances \
--header 'X-AptaPay-Signature: <api-key>'const options = {method: 'GET', headers: {'X-AptaPay-Signature': '<api-key>'}};
fetch('https://api.pay.aptahq.com/v1/balances', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/balances"
headers = {"X-AptaPay-Signature": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text){
"success": true,
"data": [
{
"currency": "UGX",
"available_minor": 2000000,
"enabled": true
},
{
"currency": "KES",
"available_minor": 0,
"enabled": true
},
{
"currency": "ZAR",
"available_minor": 0,
"enabled": false
}
]
}{
"code": 200,
"message": "OK",
"data": "<unknown>",
"error": {
"code": "payout_rejected_by_provider",
"message": "<string>",
"terminal": true,
"retriable": true,
"ambiguous": true,
"details": {}
}
}Provider wallet balances, per currency (the SHARED pool)
The provider’s wallet balances — a pool every tenant shares. This is an operational view of the underlying account, not a statement of what you personally hold.
This is almost certainly not the number you want. Use GET /v1/float. Most of what this endpoint reports belongs to other tenants. A payout is checked against YOUR float, not against this pool, so a healthy figure here tells you nothing about whether your next payout will succeed — you can read 4,000,000 UGX here and still be refused with insufficient_tenant_float.
What this IS good for: knowing whether the underlying account needs topping up at all. If this wallet is empty, nobody’s payouts are going out, whatever their own float says. A payout consults both — your float first, then the real wallet — so a shortfall in either declines it.
Balances are PER CURRENCY, and that matters operationally: a payout can fail with insufficient_wallet_float for UGX while the KES and NGN wallets are perfectly well funded. The failure names the currency rather than declining generically.
A read of provider wallet state, not the gateway’s ledger — it reflects money actually available now, including funds not yet reflected in any tenant’s books.
enabled is separate from the balance, and both must be true to transact. A wallet the provider has disabled refuses payouts however well funded it is, so a balance alone is only half the answer. A provider that does not report the flag is treated as enabled — “unknown” must never read as “disabled” and strand a working wallet.
An empty or unfunded wallet is reported at zero, not omitted. “You have no KES wallet” and “your KES wallet is empty” are different answers, and only the first would justify a missing row. If a currency the account holds is absent from this response, that is a bug worth reporting — the gateway logs every wallet it drops, with the reason.
Currencies the gateway cannot represent in exact minor units are omitted. In practice this means the crypto wallets every Eversend account carries (USDC, BTC, ETH, …): they have no ISO minor-unit exponent, and inventing one would risk a 100x error in the one place that must not have one. Fiat wallets are never omitted for this reason.
curl --request GET \
--url https://api.pay.aptahq.com/v1/balances \
--header 'X-AptaPay-Signature: <api-key>'const options = {method: 'GET', headers: {'X-AptaPay-Signature': '<api-key>'}};
fetch('https://api.pay.aptahq.com/v1/balances', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pay.aptahq.com/v1/balances"
headers = {"X-AptaPay-Signature": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text){
"success": true,
"data": [
{
"currency": "UGX",
"available_minor": 2000000,
"enabled": true
},
{
"currency": "KES",
"available_minor": 0,
"enabled": true
},
{
"currency": "ZAR",
"available_minor": 0,
"enabled": false
}
]
}{
"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.
Response
Balances, one row per representable currency the account holds — including wallets with a zero balance.
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