Education¶
Generate result-checker PINs (WAEC, JAMB, NECO and others) that candidates use to check their results.
Education plans¶
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¶
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:
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" |