Iron Pay API
One authenticated endpoint powers everything. Send a JSON body with a
cmd field to verify received payments, read live wallet balances, and send
money from a connected KBZPay or Wave account.
Alias: POST https://ironpay.ironx.dev/api/v1. The cmd field selects the operation;
provider is kbzpay (default), wave, or aya.
{
"success": true,
"message": "<tag>",
"data": {}
}
{
"success": false,
"message": "<human readable>",
"code": "<machine_code>"
}account_id to choose which connected account a command uses. If omitted, the default/first
account is used.Account ID format: by default it is the wallet phone number with the leading
0 and country code 95 stripped (e.g. 09xxxxxxxxx →
9xxxxxxxxx). Use the exact value returned by cmd: account.Authentication
Create an API key from your dashboard at /web → API Keys → Generate new key.
Send it as a Bearer token on every request. API keys are scoped to the user; use account_id
on each request to select which of the user's connected wallets to use.
Authorization: Bearer iron_… Content-Type: application/json
The exec endpoint
All operations share one route. Switch behaviour with cmd:
| cmd | Does | Key fields |
|---|---|---|
verify | Confirm a received payment (charges fee) | provider, digits |
balance | Live wallet balance + service deposit | provider |
check | Resolve a recipient's name before transferring | provider, phone |
transfer | Send money from the connected wallet | provider, phone, amount, note, pin |
transfer_otp | Complete a KBZPay OTP-challenged transfer | tx_id, otp |
transfer_resend | Resend the transfer OTP | tx_id |
account | Connection state for a provider | provider |
account_id optional | all commands | Selects which connected account to use. Defaults to the normalized phone of the first account. Use the exact value from cmd: account. |
cmd verify
Match a received (credit) transaction by the last digits of its id and confirm it. A fee of 1% of the amount is charged from your deposit on success.
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"verify","provider":"kbzpay","digits":"78622","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "verify" |
digits required | string | Last digits of the transaction id (e.g. last 5) |
provider optional | string | kbzpay (default), wave, or aya |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
Response 200
{
"success": true,
"message": "verified",
"data": {
"provider": "kbzpay",
"transaction_id": "01004158021237478622",
"amount": 1500,
"amount_text": "1,500.00",
"currency": "MMK",
"sender": "Aung Aung",
"time": "2026-05-01T08:13:35.000Z",
"time_unix": 1746086015000,
"fee_charged": "15.00",
"fee_minor": 1500,
"fee_rate_bps": 100,
"fee_rate": "1%",
"deposit_remaining": "985.00",
"deposit_remaining_minor": 98500
}
}Errors
402insufficient_deposit
404not_found
409ambiguous_match · account_not_connected
cmd balance
Live balance of the connected wallet, plus your service-side deposit (used to pay verify fees).
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"balance","provider":"wave","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "balance" |
provider optional | string | kbzpay (default) / wave / aya |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
Response 200
{
"success": true,
"message": "",
"data": {
"provider": "wave",
"msisdn": "09790000000",
"wallet_balance": 254300,
"wallet_total": 254300,
"wallet_frozen": null, // Wave reports one balance; null. KBZPay fills this.
"currency": "MMK",
"deposit": "985.00",
"deposit_minor": 98500,
"fee_rate_bps": 100,
"fee_rate": "1%"
}
}cmd check
Resolve a recipient's registered name before sending — the "who am I paying?" confirmation the wallet apps show. No fee, no PIN. Works on all three providers.
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"check","provider":"kbzpay","phone":"09790000000","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "check" (aliases: check_account, beneficiary) |
phone required | string | Recipient, 09xxxxxxxxx |
provider optional | string | kbzpay (default) / wave / aya |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
Response 200
{
"success": true,
"message": "account_found",
"data": {
"provider": "kbzpay",
"registered": true,
"recipient": "09790000000",
"recipient_name": "Paing Phyo Khant",
"name": "Paing Phyo Khant",
"status": "active",
"currency": "MMK"
}
}Errors
400bad_phone
404not_registered
409account_not_connected
cmd transfer
Send money from the connected wallet. Wave completes in one call. KBZPay may return an
otp_required challenge for new/untrusted devices — finish it with transfer_otp.
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"transfer","provider":"kbzpay","phone":"09790000000","amount":1000,"note":"Invoice 42","pin":"123456","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "transfer" |
phone required | string | Recipient, 09xxxxxxxxx |
amount required | number | MMK, > 0 |
pin required | string | Wallet PIN |
note required | string | Transfer note/reference, max 100 chars |
provider optional | string | kbzpay (default) / wave / aya |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
Response — completed 200
{
"success": true,
"message": "transfer_success",
"data": {
"provider": "wave",
"transaction_id": "WV2605110012345",
"amount": 1000,
"recipient": "09790000000",
"recipient_name": "Mya Mya",
"note": "Invoice 42",
"currency": "MMK",
"status": "success",
"time": "2026-06-11T09:42:10.000Z",
"message": "Transfer successful"
}
}Response — OTP required 200
KBZPay only, when the device is challenged. Hold the tx_id and submit the SMS code via transfer_otp within 5 minutes.
{
"success": true,
"message": "otp_required",
"data": {
"otp_required": true,
"tx_id": "9f1c4ad2e7b84a01bc33de55aa12f088",
"sent_to": "09790000000",
"next_cmd": "transfer_otp",
"hint": "An SMS code was sent. Call cmd 'transfer_otp' with { tx_id, otp }."
}
}Errors
400bad_phone · bad_amount · no_pin · pin_failed
409account_not_connected · verify_required
cmd transfer_otp
Finish a KBZPay transfer that returned otp_required. On success the money moves and you get the same transfer_success shape.
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"transfer_otp","tx_id":"9f1c4ad2e7b84a01bc33de55aa12f088","otp":"456789","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "transfer_otp" |
tx_id required | string | OTP-challenged transfer id returned by transfer |
otp required | string | SMS OTP code |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
Response 200
{
"success": true,
"message": "transfer_success",
"data": {
"provider": "kbzpay",
"transaction_id": "0100415802123747xxxx",
"amount": 1000,
"recipient": "09790000000",
"note": "Invoice 42",
"currency": "MMK",
"status": "Success",
"title": "Transfer Successful",
"time": "2026-06-11T09:43:55.000Z"
}
}Errors
400otp_wrong · tx_expired · no_otp
cmd transfer_resend
Resend the SMS code for a parked KBZPay transfer (extends the 5-minute window).
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"transfer_resend","tx_id":"9f1c4ad2e7b84a01bc33de55aa12f088","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "transfer_resend" |
tx_id required | string | OTP-challenged transfer id returned by transfer |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
{
"success": true,
"message": "",
"data": {
"sent_to": "09790000000"
}
}cmd account
Check whether a provider is connected for your account.
Request
# POST https://ironpay.ironx.dev/api/v1/exec curl -X POST https://ironpay.ironx.dev/api/v1/exec \n -H "Authorization: Bearer iron_…" \n -H "Content-Type: application/json" \n -d '{"cmd":"account","provider":"kbzpay","account_id":"9978114808"}'
| Field | Type | Notes |
|---|---|---|
cmd required | string | "account" |
provider optional | string | kbzpay (default) / wave / aya |
account_id optional | string | Selects which connected account to use. Must be the exact normalized phone returned by cmd: account (e.g. 9xxxxxxxxx, not 09xxxxxxxxx). |
Response 200
{
"success": true,
"message": "",
"data": {
"provider": "kbzpay",
"connected": true,
"state": "connected",
"msisdn": "09790000000"
}
}Errors
Errors return { success:false, message, code } with a matching HTTP status.
| Status | code | Meaning |
|---|---|---|
| 400 | no_cmd / bad_cmd | Missing or unknown cmd |
| 400 | bad_phone / bad_amount / no_pin | Transfer validation failed |
| 401 | no_key / invalid_key | Missing or revoked API key |
| 402 | insufficient_deposit | Top up to verify |
| 404 | not_found | No matching transaction |
| 404 | not_registered | No account for this number (check) |
| 409 | account_not_connected / account_disconnected | Reconnect in the app |
| 409 | ambiguous_match | Provide more digits |
| 429 | rate_limited | 60 requests / minute / key |
| 502 | upstream_error / transfer_failed | Wallet provider error |
Rate limits & billing
rate_limited.verify. balance, check,
transfer and account are not billed.