ABS Vendor API
Sell airtime, data, electricity and airtime vouchers programmatically. One API key, three request styles, and a full sandbox so you can build and test without spending a naira.
https://absvendor.com — all endpoints are HTTPS only.There are three ways to talk to ABS Vendor, all sharing the same API key:
Direct API
Richest surface — airtime, data, electricity, vouchers, meter validation. Form-encoded.
MSorg style
JSON, drop-in for platforms built around the MaskawaSub/MSorg shape.
Adex style
JSON, drop-in for platforms built around the Adex shape.
Authentication
Every request is authenticated with your secret API key, sent as a Bearer token in the Authorization header. Generate or rotate your key from Dashboard → API Keys.
Authorization: Bearer abs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxThe Partner (MSorg/Adex) endpoints additionally accept Token <key> and Basic base64(username:key) for compatibility. Your key is the only credential — there is no separate partner ID.
Sandbox / test mode
Add sandbox=true to any spend request (airtime, data, electricity, voucher) — or send the header X-ABS-Sandbox: 1. The request runs the full pipeline (auth, validation, ID lookup, PIN/IP/cap checks) but does not charge your wallet and does not vend. The response is shape-identical to a live one, with "sandbox": true added.
curl -X POST https://absvendor.com/api/user/transaction/buy_airtime.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -F "airtimetid=MTNAIR6TFD" \ -F "phoneno=08012345678" \ -F "amount=100" \ -F "sandbox=1"
Rate limits & controls
- Rate limit: 300 requests per 60 minutes per key.
- IP whitelist (Partner endpoints): if set, requests from other IPs are rejected (403).
- Daily spend cap (Partner endpoints): caps NGN spent per UTC day (429 when exceeded).
- Transaction PIN (Partner endpoints): if enabled, send
pinon every spend call.
Responses & errors
Each style uses its own envelope. A successful spend always means the wallet was debited and the order was created.
Direct API envelope
// success { "status": true, "text": "Successful", "data": { ... } } // failure { "status": false, "text": "Please check your input...", "error": { "code": 141, "text": "The body request is not valid..." } }
Partner MSorg envelope
{ "Status": "successful", ... }
{ "Status": "failed", "message": "<reason>" }Partner Adex envelope
{ "status": "success", ... }
{ "status": "failed", "message": "<reason>" }| HTTP | Meaning |
|---|---|
| 200 | Success |
| 400 / code 141 | Invalid or missing parameter (often a wrong product ID — see below) |
| 401 | Missing/invalid API key, or PIN required and wrong/absent |
| 403 | API disabled for key, or IP not whitelisted |
| 404 | Unknown network / plan / provider |
| 429 | Rate limit or daily spend cap exceeded |
Finding product IDs
This is the #1 cause of code 141. Airtime and data use different product IDs, and the network name (e.g. "MTN") is never a valid ID. Always pull live values from Get System Data (Direct) or List Networks / List Data Plans (Partner).
| You're buying | Send as | Where to get it | Example |
|---|---|---|---|
| Airtime | airtimetid | System Data → airtimeproviders[].product_trackid (or .trackid) | MTNAIR6TFD |
| Data | airtimetid = network, cardunittid = plan | List Data Plans → network_id + id | MTN56TFD + 6BO1H |
| Electricity | provider_tid | System Data → electricity services | disco service trackid |
| Voucher | airtimetid = network, cardunittid = unit | System Data → voucherproviders[] | MTNAIR6TFD + HBSI2 |
MTN56TFD is the MTN data network. For airtime use the MTN airtime ID (MTNAIR6TFD or its provider trackid). Mixing them returns code 141.Choosing an integration
| Direct (native) | MSorg | Adex | |
|---|---|---|---|
| Body format | form-encoded | JSON | JSON |
| Airtime / Data | ✔ | ✔ | ✔ |
| Electricity / Vouchers / Meter | ✔ | — | — |
| IP whitelist · daily cap · PIN | — | ✔ | ✔ |
| Best for | New integrations | MSorg-compatible apps | Adex-compatible apps |
Direct · Buy Airtime
| Field | Req | Description |
|---|---|---|
airtimetid | required | Airtime network ID — product trackid (e.g. MTNAIR6TFD) or provider trackid (e.g. MNH5Y) |
phoneno | required | Recipient NG number, e.g. 08012345678 |
amount | required | NGN, whole number, minimum 50 |
coupon_code | optional | Discount coupon |
sandbox | optional | 1 to simulate |
curl -X POST https://absvendor.com/api/user/transaction/buy_airtime.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -F "airtimetid=MTNAIR6TFD" \ -F "phoneno=08012345678" \ -F "amount=100"
{ "status": true,
"data": { "coupon_applied": false, "final_amount": 100 },
"text": "Successful" }Direct · Buy Data
| Field | Req | Description |
|---|---|---|
airtimetid | required | Data network product trackid (List Data Plans → network_id), e.g. MTN56TFD |
cardunittid | required | Data plan trackid (List Data Plans → id), e.g. 6BO1H |
phoneno | required | Recipient NG number |
coupon_code | optional | Discount coupon |
sandbox | optional | 1 to simulate |
curl -X POST https://absvendor.com/api/user/transaction/buy_data.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -F "airtimetid=MTN56TFD" \ -F "cardunittid=6BO1H" \ -F "phoneno=08012345678"
Direct · Buy Electricity
| Field | Req | Description |
|---|---|---|
provider_tid | required | Disco service trackid (from System Data electricity services) |
meter_number | required | Meter number (digits, ≥ 6) |
amount | required | NGN, whole number, minimum 1000 |
sandbox | optional | 1 to simulate |
curl -X POST https://absvendor.com/api/user/transaction/buy_electricity.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -F "provider_tid=SERVICE_TRACKID" \ -F "meter_number=12345678901" \ -F "amount=1000"
Direct · Buy Airtime Voucher
| Field | Req | Description |
|---|---|---|
airtimetid | required | Network product trackid (System Data → voucherproviders[].product_trackid), e.g. MTNAIR6TFD |
cardunittid | required | Voucher unit trackid (e.g. HBSI2 = MTN ₦500) |
totalunit | required | Quantity, 1–30 |
sandbox | optional | 1 to simulate |
curl -X POST https://absvendor.com/api/user/transaction/buy_airtime_voucher.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -F "airtimetid=MTNAIR6TFD" \ -F "cardunittid=HBSI2" \ -F "totalunit=1"
Direct · Validate Meter
Read-only — returns the meter owner's name/address. No charge.
| Field | Req | Description |
|---|---|---|
meter_number | required | Meter number |
provider_tid | required | Disco service trackid |
curl -X POST https://absvendor.com/api/user/transaction/validate_meter.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -F "meter_number=12345678901" \ -F "provider_tid=SERVICE_TRACKID"
Direct · Get System Data
Returns the full live catalog: airtimeproviders, data plans/providers, electricity services, voucherproviders, and network_allowed_numbers. Use it to discover every ID above.
curl https://absvendor.com/api/user/system/get_system_data.php \
-H "Authorization: Bearer abs_live_YOUR_KEY"{ "status": true,
"data": {
"airtimeproviders": [
{ "name": "MTN Airtime Nigeria",
"product_trackid": "MTNAIR6TFD", "trackid": "MNH5Y" }
] } }MSorg · Balance / Account
curl https://absvendor.com/api/partner/v1/user.php \
-H "Authorization: Bearer abs_live_YOUR_KEY"{ "Status": "successful", "username": "reseller",
"wallet_balance": "5000.00", "currency": "NGN",
"api_daily_cap": "10000.00", "api_daily_spent": "500.00" }MSorg · List Networks
{ "Status": "successful", "count": 4,
"networks": [ { "id": "MTNAIR6TFD", "name": "MTN Airtime Nigeria" } ] }MSorg · List Data Plans
{ "Status": "successful", "count": 123,
"plans": [
{ "id": "6BO1H", "network_id": "MTN56TFD",
"plan_network": "MTN", "plan_name": "1GB Daily", "plan_amount": "290.00",
"data_size": "1GB", "validity_type": "Daily",
"validity": "1 day", "validity_days": 1 }
] }| Field | Description |
|---|---|
data_size | Bundle size as listed, e.g. 1GB. May be empty for a few plans. |
validity_type | Daily · Weekly · Monthly · Yearly — also Social Media and FibreX / ODU for those categories. |
validity | Human-readable duration, e.g. 30 days, 1 year. |
validity_days | Duration as an integer. null when the plan has no fixed duration (some social bundles) — prefer this field over parsing plan_name. |
MSorg · Buy Airtime
| Field | Req | Description |
|---|---|---|
network | required | Network id from List Networks, e.g. MTNAIR6TFD |
mobile_number | required | Recipient NG number |
amount | required | NGN, minimum 50 |
pin | if enabled | Transaction PIN (when key requires it) |
sandbox | optional | true to simulate |
curl -X POST https://absvendor.com/api/partner/v1/topup.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"network":"MTNAIR6TFD","mobile_number":"08012345678","amount":100}'
{ "Status": "successful", "ident": "BA-...",
"network": "MTNAIR6TFD", "mobile_number": "08012345678",
"amount": "100", "balance_after": "4900.00" }MSorg · Buy Data
| Field | Req | Description |
|---|---|---|
network | required | Network id (List Data Plans → network_id), e.g. MTN56TFD |
plan | required | Plan id (List Data Plans → id), e.g. 6BO1H |
mobile_number | required | Recipient NG number |
pin | if enabled | Transaction PIN |
sandbox | optional | true to simulate |
curl -X POST https://absvendor.com/api/partner/v1/data.php \ -H "Authorization: Bearer abs_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"network":"MTN56TFD","plan":"6BO1H","mobile_number":"08012345678"}'
MSorg · Requery Transaction
{ "Status": "successful", "ident": "BA-...",
"amount": "100.00", "mobile_number": "08012345678" }Status maps to successful, Processing, failed, or Refund.
Adex · Login / Balance
Accepts Basic base64(username:key), Token <key>, or Bearer <key>.
{ "status": "success", "AccessToken": "abs_live_...",
"balance": "5000.00", "username": "reseller" }Adex · Buy Airtime
| Field | Req | Description |
|---|---|---|
network | required | 1=MTN, 2=Airtel, 3=GLO, 4=9mobile (or a network trackid) |
phone | required | Recipient NG number |
amount | required | NGN, minimum 50 |
request-id | optional | Your idempotency reference |
sandbox | optional | true to simulate |
curl -X POST https://absvendor.com/api/partner/v1/adex/topup.php \ -H "Authorization: Token abs_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"network":1,"phone":"08012345678","amount":100,"request-id":"ref-123"}'
{ "network": "MTN", "status": "success",
"amount": 100, "phone_number": "08012345678",
"newbal": 4900, "request-id": "ref-123" }Adex · Buy Data
| Field | Req | Description |
|---|---|---|
network | required | 1=MTN, 2=Airtel, 3=GLO, 4=9mobile |
phone | required | Recipient NG number |
data_plan | required | Plan trackid (List Data Plans → id), e.g. 6BO1H |
request-id | optional | Your idempotency reference |
sandbox | optional | true to simulate |
curl -X POST https://absvendor.com/api/partner/v1/adex/data.php \ -H "Authorization: Token abs_live_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"network":1,"phone":"08012345678","data_plan":"6BO1H"}'
Webhooks
Rather than polling requery, register a URL and we POST to it when something happens on your account. Add and manage endpoints from your dashboard or via /api/partner/v1/webhooks.php. Each endpoint gets its own signing secret, shown once when you create it.
Subscribe to * for everything, or list the events you want.
| Event | Fires when |
|---|---|
transaction.settled | An order reached a successful final state |
transaction.failed | An order failed or was rejected upstream |
refund.issued | An automatic or manual refund landed |
wallet.credited | Your balance moved up |
wallet.debited | Your balance moved down |
Webhook payload
Content-Type: application/json| Header | Description |
|---|---|
X-ABS-Event | Event name, as above |
X-ABS-Delivery | Delivery id. Stable across retries — dedupe on this |
X-ABS-Timestamp | Unix seconds, and part of the signed string |
X-ABS-Signature | sha256=<hmac>, see below |
{
"event": "transaction.settled",
"created_at": "2026-08-20T13:05:00+00:00",
"data": {
"order_id": "BD202608201305VXP2466KANG159249W",
"status": "1",
"amount": "500.00",
"service": 3,
"trans_kind": 1,
"recipient": "08012345678",
"message": "Data purchase successful",
"balance_before": "12500.00",
"balance_after": "12000.00",
"confirmed_at": "2026-08-20 13:05:00"
}
}| Field | Description |
|---|---|
data.order_id | Your transaction reference — the same one requery takes |
data.status | 1 settled, 3 failed, 6 refunded |
data.service | Service type id |
data.trans_kind | Transaction kind id |
data.change | On wallet.* only: absolute amount moved |
A single purchase normally produces two events — a transaction.* and a wallet.* — carrying the same order_id. Treat them independently and key on X-ABS-Delivery.
Verifying signatures
Compute HMAC-SHA256 over timestamp + "." + raw request body using your endpoint's secret, and compare with the hex digest in X-ABS-Signature. Sign the raw body, before any JSON parsing or re-encoding. The timestamp is inside the signed string so a captured body cannot be replayed later against the same signature.
$body = file_get_contents('php://input'); $timestamp = $_SERVER['HTTP_X_ABS_TIMESTAMP'] ?? ''; $sent = $_SERVER['HTTP_X_ABS_SIGNATURE'] ?? ''; $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret); if (!hash_equals($expected, $sent)) { http_response_code(401); exit; } if (abs(time() - (int) $timestamp) > 300) { http_response_code(401); exit; } http_response_code(200);
app.post('/webhook', express.raw({type: '*/*'}), (req, res) => { const ts = req.get('X-ABS-Timestamp'); const sent = req.get('X-ABS-Signature'); const mac = crypto.createHmac('sha256', secret) .update(ts + '.' + req.body.toString()) .digest('hex'); if ('sha256=' + mac !== sent) return res.sendStatus(401); res.sendStatus(200); });
Retries & ordering
Reply 2xx as soon as you have stored the delivery. Anything else, or a response slower than 10 seconds, counts as a failure and is retried.
| Backoff | 30s, 2m, 10m, 30m, 2h, 6h, 12h |
| Attempts | 7, spanning roughly 24 hours |
| Endpoint disabled | After 20 consecutive failures |
Do your own work after replying, not before — a slow reply is a failed delivery. Events are not guaranteed to arrive in order, and a retry can arrive after a later event, so treat X-ABS-Delivery as the idempotency key and let the newest confirmed_at win.