Sell WAEC, NECO, NABTEB and JAMB PINs on your own website or app.
An API lets your website talk to ExamInfo by itself. Your live key is your secret password, so keep it on your server only. Your wallet pays for each PIN you sell. Your reference is a unique number you make for every sale, so one sale can never be charged twice. A webhook is covered in section 5 in plain words. If you have no developer, send this page to one.
1) Register at /become-a-reseller and pay the one-time registration fee (N3,000). 2) You receive a test key, a live key and a webhook secret. 3) Fund your wallet by transfer; the owner credits it. 4) Build with the test key first, then switch to the live key.
Base URL:
https://YOUR-SERVER/api/v1
All requests and replies are JSON. Amounts are in Naira (whole numbers).
Send your key in the Authorization header on every call (or X-API-Key).
Authorization: Bearer ei_live_xxxxxxxxxxxxxxxx
Test key (ei_test_...): returns fake PINs, uses no wallet money and never calls the supplier. Live key (ei_live_...): real PINs, charged from your wallet. Keep keys on your server, never in a browser or app.
/balanceYour wallet balance.
{ "ok": true, "data": { "balance": 25000, "currency": "NGN", "mode": "live" } }
/productsProducts and your prices. code is what you send when buying.
{ "ok": true, "data": [
{ "code": "WAEC", "name": "WAEC Result Checking PIN", "group": "WAEC", "price": 3400, "currency": "NGN", "requires": [], "max_quantity": 5 },
{ "code": "JAMB_UTME", "name": "JAMB UTME Registration PIN", "group": "JAMB", "price": 7900, "requires": ["profile_code","email","phone"], "max_quantity": 1 }
] }
JAMB Questions and Answers practice access: code JAMB_PRACTICE, requires name, email and reg (JAMB registration number). The reply has an access_url to give the student, valid for the hours shown in access_hours.
/ordersBuy PINs or tokens. reference is yours: 8 to 64 letters, numbers, - or _, new for every purchase.
{ "product": "WAEC", "quantity": 2, "reference": "ORD-2026-000123" }
JAMB products also need profile_code (10 digits), email and phone, quantity 1.
{ "ok": true, "data": { "reference": "ORD-2026-000123", "order_id": "ROXXXX", "product": "WAEC", "quantity": 2,
"unit_price": 3400, "amount": 6800, "status": "delivered", "mode": "live",
"pins": [ { "type": "Serial", "value": "1234567890" }, { "type": "PIN", "value": "123456789012" } ] } }
status is delivered, processing (wait for the webhook or check the order) or failed (wallet refunded). HTTP 201 = delivered, 202 = processing.
/orders/{reference}Check one order. GET/orders?limit=20 lists recent orders.
/pingHealth check (no key needed).
Buy 1 WAEC PIN:
In plain words: a webhook is a doorbell for your website. When an order finishes, ExamInfo presses it by sending a message to the web address you gave us, and your site can show the PIN to your customer straight away. It is optional. Leave the address empty if you have no developer.
When an order finishes (or fails) we POST to your webhook URL: { "event": "order.delivered", "data": { ...order } }. Events: order.delivered, order.failed. Reply with any 2xx. We retry a few times if you do not.
Check the X-ExamInfo-Signature header: sha256= + HMAC-SHA256 of the raw body using your webhook secret.
// Node.js
const crypto = require('crypto');
const sig = 'sha256=' + crypto.createHmac('sha256', WEBHOOK_SECRET).update(rawBody).digest('hex');
if (sig !== req.headers['x-examinfo-signature']) return res.sendStatus(401);Errors look like { "ok": false, "error": { "code": "insufficient_balance", "message": "..." } }
| HTTP | code | Meaning |
|---|---|---|
| 401 | invalid_key | Key missing or wrong |
| 403 | account_disabled | Your account is switched off |
| 400 | bad_reference / bad_quantity / bad_json | Fix the request |
| 400 | unknown_product | Use a code from /products |
| 400 | bad_profile_code / bad_email / bad_phone | JAMB details wrong |
| 402 | insufficient_balance | Wallet too low (balance and needed amount are returned) |
| 409 | product_unavailable | Out of stock right now |
| 409 | reference_reused | Same reference used for a different order |
| 422 | profile_not_found | JAMB profile code not found |
| 429 | rate_limited | Too many requests |
| 502 | supplier_failed | Could not complete; wallet refunded |
| 503 | api_off | API switched off for now |
No double buying. Send the same reference again and you get the first order back ("duplicate": true), never a second purchase. If a call times out, repeat it with the same reference.
Limits. 120 requests a minute and 30 orders a minute per key.
Processing orders. Your money is held until the supplier answers. Do not buy again; wait for the webhook or call GET /orders/{reference}.
Test mode. Use the test key to try everything for free.
Machine-readable spec: /api/openapi.json