Skip to content

Education

Generate result-checker PINs (WAEC, JAMB, NECO and others) that candidates use to check their results.


Education plans

GET/api/v1/education/

Auth: API key or session.

curl https://api.instapaybill.com/api/v1/education/ \
  -H "Authorization: Bearer $IPB_LIVE_KEY" \
  -H "User-Agent: my-integration/1.0"

Returns the available exam types with their prices:

{
  "msg": "Education plans retrieved successfully",
  "data": [
    {
      "type": "WAEC",
      "price": 2000,
      "category": "<categoryId>",
      "image": { "id": "<id>", "url": "<url>" }
    }
  ]
}

Use type in the buy request.


Generate result-checker PINs

POST/api/v1/education/buy
curl -X POST https://api.instapaybill.com/api/v1/education/buy \
  -H "Authorization: Bearer $IPB_LIVE_KEY" \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-integration/1.0" \
  -d '{
    "type": "WAEC",
    "numberOfPin": 2,
    "apiRefId": "order-4001"
  }'

Request body

Field Type Required Description
type string ✅ Exam type. Uppercased for the plan lookup — waec and WAEC both work.
numberOfPin number ✅ How many PINs to generate. Must be greater than 0.
apiRefId string — Your order reference. API-channel only.
pin string — Wallet PIN. Ignored for API-key requests.
promoCode / coupon string — Not applied for API-channel requests.

Success response — 200

{
  "msg": "WAEC Education Purchase of ₦2,000 for numberOfPin: 2 was successful",
  "data": {
    "code": 200,
    "reference": "IP|TN|ED|127|20260918120000_ASDFG",
    "msg": "PINs generated successfully",
    "data_pin": {
      "serial": "WAE1234567890",
      "pin": "1234-5678-9012"
    },
    "description": "WAEC Pin Purchase of ₦2,000 for numberOfPin: 2",
    "type": "education",
    "message": "1. Serial: WAE1234567890 / PIN: 1234-5678-9012"
  },
  "code": 200
}

data.data_pin shape varies by provider

The raw PIN payload is passed through from the provider, so its shape is not normalised. It may be a single object, an array of objects, or a map keyed by serial — depending on which provider served the request.

In practice you will see one of:

"data_pin": { "serial": "WAE1234567890", "pin": "1234-5678-9012" }
"data_pin": [ { "serial": "WAE1234567890", "pin": "1234-5678-9012" } ]
"data_pin": { "WAE1234567890": "1234-5678-9012" }

Write your client to handle all three. The sandbox returns a consistent pins array, which is the shape you should code against.

Pending — 300

{
  "code": 300,
  "reference": "IP|TN|ED|127|20260918120000_ASDFG",
  "error": [
    {
      "path": "education-merrybills",
      "msg": "Your purchase is being verified with the provider. We will confirm delivery shortly."
    }
  ]
}

Failure — 400

{
  "code": 400,
  "reference": "IP|TN|ED|127|20260918120000_ASDFG",
  "error": [
    {
      "path": "education-merrybills",
      "msg": "Education PIN provider unavailable"
    }
  ]
}

data.msg may be absent

The education provider layer differs per biller: some return message, some return msg. Because the response spreads the provider result directly, data.msg is not guaranteed. data.message always is.


Guards

Check Rejects when Response
checkNetworkState The education category is flagged networkBad 300 with canSchedule: true
UserActive Account suspended 403
TransactionStatusAndKYCCheck No BVN and no NIN 300, path: "kyc_required"
usecases.education The service is switched off 300
Plan exists No plan matches the uppercased type 300
Pin count numberOfPin <= 0 300
Wallet balance Insufficient funds 300
Biller health The provider is flagged down 300
Duplicate guards An identical request is in flight 300
KYC purchase gate privilege === 'user' with no BVN/NIN 300, path: "kyc"

  • Catalogue — plan and category metadata
  • Sandbox — practice the PIN flow with a consistent response shape