Airtime¶
Top up any Nigerian mobile number on MTN, Glo, Airtel or 9mobile.
Buy airtime¶
Purchases airtime for a phone number and debits the wallet.
Auth: API key (live or test) or session.
curl -X POST https://api.instapaybill.com/api/v1/airtime/buy \
-H "Authorization: Bearer $IPB_LIVE_KEY" \
-H "Content-Type: application/json" \
-H "User-Agent: my-integration/1.0" \
-d '{
"phone": "08012345678",
"amount": 500,
"network": "MTN",
"apiRefId": "order-1001"
}'
Request body¶
| Field | Type | Required | Description |
|---|---|---|---|
phone |
string | ✅ | Nigerian mobile number. Validated as an en-NG mobile number. |
amount |
integer | ✅ | Face value in naira, minimum 1. |
network |
string | ✅ | MTN, AIRTEL, GLO, T2MOBILE. Case-insensitive. |
apiRefId |
string | — | Your order reference. Stored for API-channel requests only. |
type |
string | — | VTU variant: vtu (default), share&sell, awuf4u. |
pin |
string | — | Wallet PIN. Ignored for API-key requests. |
promoCode |
string | — | Discount code. Not applied for API-channel requests. |
coupon |
string | — | Alias of promoCode. Not applied for API-channel requests. |
Success response — 200¶
{
"msg": "MTN Airtime Purchase of ₦500 for 08012345678 was successful",
"data": {
"code": 200,
"reference": "IP|TN|A|123|20260918120000_ASDFG",
"msg": "Airtime purchase successful",
"message": "Top-up of ₦500 to 08012345678 delivered successfully.",
"description": "MTN Airtime Purchase",
"type": "airtime"
},
"code": 200
}
Pending — 300¶
The provider has not confirmed delivery yet. The wallet stays debited and the platform polls the provider.
{
"code": 300,
"reference": "IP|TN|A|123|20260918120000_ASDFG",
"error": [
{
"path": "airtime-merrybills",
"msg": "Your purchase is being verified with the provider. We will confirm delivery shortly."
}
]
}
Do not retry. Wait for the webhook or poll
resolve.
Failure — 400¶
The provider declined. The wallet was refunded.
{
"code": 400,
"reference": "IP|TN|A|123|20260918120000_ASDFG",
"error": [
{
"path": "airtime-merrybills",
"msg": "Insufficient balance on your provider account"
}
]
}
Validation errors — 300¶
{
"error": [
{
"value": "",
"msg": "Phone number is required",
"path": "phone",
"location": "body"
}
]
}
Guards that apply to this endpoint¶
The order of the middleware chain is significant. A request is rejected at the first guard that fails.
| Guard | Rejects when | Response |
|---|---|---|
UserAuth |
Bad credential, non-agent key, missing User-Agent |
401 / 403 / 500 |
checkNetworkState |
The network's category is flagged networkBad |
300 with canSchedule: true |
UserActive |
Account suspended or inactive | 403 |
TransactionStatusAndKYCCheck |
No BVN and no NIN | 300, path: "kyc_required" |
UserAuthSouceNecessary |
Unknown channel | 300, "Invalid source header" |
UserAuthSouceNecessaryChecker |
usecases.airtime !== 'enable' or the channel is switched off |
300, "Service is currently unavailable" |
MaximumLimiterPurchase |
Too many purchases (session channels only) | 429 |
| express-validator | Missing/invalid field | 300 |
Then, inside the service, in order:
| Check | Rejects when | Response |
|---|---|---|
| Phone/network match | The number belongs to a different network | 300 |
| KYC purchase gate | privilege === 'user' with no BVN/NIN |
300, path: "kyc" |
| Minimum / maximum | Amount below usecases.airtimeMini or above usecases.airtimeMax |
300 |
| Amount sanity | amount <= 0 |
300, "Invalid amount, kindly double check" |
| Service enabled | usecases.airtime !== 'enable' |
300 |
| Product enabled | The airtime product is not enable |
300 |
| Wallet exists | The user has no wallet record | 300 |
| Wallet balance | Insufficient funds | 300 |
| Telephone blacklist | The number is on the blacklist | 300 |
| Biller health | The provider is flagged down |
300 |
| Duplicate guards | An identical request is in flight | 300 |
| Biller minimum | Below the provider's minimum (e.g. ₦100 for VTUplug) | 300 |
The phone/network match¶
If the number's actual carrier does not match the network you named, the
request is rejected:
{
"error": [
{
"msg": "The phone number 08012345678 belongs to GLO, not MTN",
"path": "network"
}
]
}
The carrier is resolved through the MCD/Sprintcheck operator lookup and cached for 24 hours. The check fails open if the lookup service is down, so a provider outage will not block your purchases.
Airtime-to-cash (converter)¶
The airtime-to-cash flow lets a customer convert a bonus airtime balance into
cash. It is an app/web-only feature — the API channel receives 403
"This service is not available for api channel".
| Method | Path | Access |
|---|---|---|
GET |
/api/v1/airtime/converter |
App/web — converter history |
POST |
/api/v1/airtime/converter |
App/web — submit a conversion |
POST |
/api/v1/airtime/converter/{id} |
Admin — decline |
PUT |
/api/v1/airtime/converter/{id} |
Admin — mark successful |
GET |
/api/v1/airtime/converter-stats |
Admin |
GET |
/api/v1/airtime/converter-number |
App/web |
POST |
/api/v1/airtime/converter-number |
Admin |
PUT |
/api/v1/airtime/converter-number/{id} |
Admin |
POST /api/v1/airtime/converter requires network, credit_mode,
reference and amount in the body, plus a verified email, an active
account, completed KYC, and the airtimetocash permission for the deciding
role.
Banks¶
| Method | Path | Access | Description |
|---|---|---|---|
GET |
/api/v1/airtime/banks |
API key | List of supported banks |
POST |
/api/v1/airtime/banks |
App/web | Resolve an account holder's name |
POST requires bank_code and account_number (both numeric).
Related¶
- Data — bundles, same response envelope
- Transactions — finding and resolving a purchase
- Errors — the
300vs400rule