Skip to content

Airtime

Top up any Nigerian mobile number on MTN, Glo, Airtel or 9mobile.


Buy airtime

POST/api/v1/airtime/buy

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).


  • Data — bundles, same response envelope
  • Transactions — finding and resolving a purchase
  • Errors — the 300 vs 400 rule