ABS Vendor ABS Vendor · API Home Support Get API key →
FOR DEVELOPERS

One API for data, airtime and bills

Vend to any Nigerian network from your own app. It is wallet-based rather than card-based, there is a full sandbox, and if you are already built against MaskawaSub or Adex the request shapes are drop-in compatible, so switching does not mean rewriting.

156 live data plans 4 networks Sandbox that charges nothing NCC-licensed

Live rates, straight from the vending tables

These are the agent and API prices your key is actually billed at, not a marketing figure. They update on this page the moment they change in our system.

MTN
from ₦340
73 active plans · retail from ₦343
Airtel
from ₦50
44 active plans · retail from ₦80
Glo
from ₦45
39 active plans · retail from ₦48

Every plan, ID and price is queryable at GET /api/partner/v1/data_plans.php — pull the catalogue programmatically instead of hardcoding it. Electricity, cable and exam pins are on the same key and wallet.

Your first call, in about two minutes

1

Get a key

Generate it from your dashboard. One bearer key is your only credential — there is no separate partner ID to manage.

2

Test free

Add sandbox=true. Requests still validate properly — wrong plan IDs still fail — but your wallet is never charged.

3

Go live

Drop the sandbox flag. Same endpoint, same shape. Fund your wallet and you are vending.

Wallet-basedFund once and vend. No card on file, no per-transaction gateway cut.
Auto-refund on declineA rejected vend settles straight back to your wallet — no ticket, no chasing.
Every response storedRequest and response retained per transaction, so disputes are a lookup rather than an argument.
Three request stylesNative, MSorg-shaped or Adex-shaped — migrate without rewriting your integration.

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.

Base URL  https://absvendor.com  — all endpoints are HTTPS only.

There are three ways to talk to ABS Vendor, all sharing the same API key:

NATIVE

Direct API

Richest surface — airtime, data, electricity, vouchers, meter validation. Form-encoded.

COMPATIBLE

MSorg style

JSON, drop-in for platforms built around the MaskawaSub/MSorg shape.

COMPATIBLE

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.

HTTP header
Authorization: Bearer abs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

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

Keep your key secret. It can spend your wallet balance. Set an IP whitelist, a daily spend cap, and (optionally) a transaction-PIN requirement on the API Keys page.

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.

Sandbox — Direct API
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"
Sandbox still validates. It only skips the charge + vend, so your IDs, phone format and amounts must be valid — exactly mirroring live. Read endpoints (balance, networks, plans, system data, validate meter) behave the same in either mode.

Rate limits & controls

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 / failure
// 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

Success / failure
{ "Status": "successful", ... }
{ "Status": "failed", "message": "<reason>" }

Partner Adex envelope

Success / failure
{ "status": "success", ... }
{ "status": "failed", "message": "<reason>" }
HTTPMeaning
200Success
400 / code 141Invalid or missing parameter (often a wrong product ID — see below)
401Missing/invalid API key, or PIN required and wrong/absent
403API disabled for key, or IP not whitelisted
404Unknown network / plan / provider
429Rate 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 buyingSend asWhere to get itExample
AirtimeairtimetidSystem Data → airtimeproviders[].product_trackid (or .trackid)MTNAIR6TFD
Dataairtimetid = network, cardunittid = planList Data Plans → network_id + idMTN56TFD + 6BO1H
Electricityprovider_tidSystem Data → electricity servicesdisco service trackid
Voucherairtimetid = network, cardunittid = unitSystem Data → voucherproviders[]MTNAIR6TFD + HBSI2
Common gotcha: 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)MSorgAdex
Body formatform-encodedJSONJSON
Airtime / Data
Electricity / Vouchers / Meter
IP whitelist · daily cap · PIN
Best forNew integrationsMSorg-compatible appsAdex-compatible apps
Tip: The Direct API does not enforce the per-key IP/cap/PIN controls — those apply to the Partner endpoints. If you rely on those guardrails, integrate against the Partner API.

Direct · Buy Airtime

POST /api/user/transaction/buy_airtime.php sandbox
FieldReqDescription
airtimetidrequiredAirtime network ID — product trackid (e.g. MTNAIR6TFD) or provider trackid (e.g. MNH5Y)
phonenorequiredRecipient NG number, e.g. 08012345678
amountrequiredNGN, whole number, minimum 50
coupon_codeoptionalDiscount coupon
sandboxoptional1 to simulate
Request
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"
Response
{ "status": true,
  "data": { "coupon_applied": false, "final_amount": 100 },
  "text": "Successful" }

Direct · Buy Data

POST /api/user/transaction/buy_data.php sandbox
FieldReqDescription
airtimetidrequiredData network product trackid (List Data Plans → network_id), e.g. MTN56TFD
cardunittidrequiredData plan trackid (List Data Plans → id), e.g. 6BO1H
phonenorequiredRecipient NG number
coupon_codeoptionalDiscount coupon
sandboxoptional1 to simulate
Request
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

POST /api/user/transaction/buy_electricity.php sandbox
FieldReqDescription
provider_tidrequiredDisco service trackid (from System Data electricity services)
meter_numberrequiredMeter number (digits, ≥ 6)
amountrequiredNGN, whole number, minimum 1000
sandboxoptional1 to simulate
Call Validate Meter first to confirm the customer name/address before charging.
Request
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

POST /api/user/transaction/buy_airtime_voucher.php sandbox
FieldReqDescription
airtimetidrequiredNetwork product trackid (System Data → voucherproviders[].product_trackid), e.g. MTNAIR6TFD
cardunittidrequiredVoucher unit trackid (e.g. HBSI2 = MTN ₦500)
totalunitrequiredQuantity, 1–30
sandboxoptional1 to simulate
Request
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

POST /api/user/transaction/validate_meter.php

Read-only — returns the meter owner's name/address. No charge.

FieldReqDescription
meter_numberrequiredMeter number
provider_tidrequiredDisco service trackid
Request
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

GET /api/user/system/get_system_data.php

Returns the full live catalog: airtimeproviders, data plans/providers, electricity services, voucherproviders, and network_allowed_numbers. Use it to discover every ID above.

Request
curl https://absvendor.com/api/user/system/get_system_data.php \
  -H "Authorization: Bearer abs_live_YOUR_KEY"
Response (excerpt)
{ "status": true,
  "data": {
    "airtimeproviders": [
      { "name": "MTN Airtime Nigeria",
        "product_trackid": "MTNAIR6TFD", "trackid": "MNH5Y" }
    ] } }

MSorg · Balance / Account

GET /api/partner/v1/user.php
Request
curl https://absvendor.com/api/partner/v1/user.php \
  -H "Authorization: Bearer abs_live_YOUR_KEY"
Response
{ "Status": "successful", "username": "reseller",
  "wallet_balance": "5000.00", "currency": "NGN",
  "api_daily_cap": "10000.00", "api_daily_spent": "500.00" }

MSorg · List Networks

GET /api/partner/v1/networks.php
Response
{ "Status": "successful", "count": 4,
  "networks": [ { "id": "MTNAIR6TFD", "name": "MTN Airtime Nigeria" } ] }

MSorg · List Data Plans

GET /api/partner/v1/data_plans.php
Response (excerpt)
{ "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 }
  ] }
FieldDescription
data_sizeBundle size as listed, e.g. 1GB. May be empty for a few plans.
validity_typeDaily · Weekly · Monthly · Yearly — also Social Media and FibreX / ODU for those categories.
validityHuman-readable duration, e.g. 30 days, 1 year.
validity_daysDuration as an integer. null when the plan has no fixed duration (some social bundles) — prefer this field over parsing plan_name.

MSorg · Buy Airtime

POST /api/partner/v1/topup.php sandbox
FieldReqDescription
networkrequiredNetwork id from List Networks, e.g. MTNAIR6TFD
mobile_numberrequiredRecipient NG number
amountrequiredNGN, minimum 50
pinif enabledTransaction PIN (when key requires it)
sandboxoptionaltrue to simulate
Request (JSON)
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}'
Response
{ "Status": "successful", "ident": "BA-...",
  "network": "MTNAIR6TFD", "mobile_number": "08012345678",
  "amount": "100", "balance_after": "4900.00" }

MSorg · Buy Data

POST /api/partner/v1/data.php sandbox
FieldReqDescription
networkrequiredNetwork id (List Data Plans → network_id), e.g. MTN56TFD
planrequiredPlan id (List Data Plans → id), e.g. 6BO1H
mobile_numberrequiredRecipient NG number
pinif enabledTransaction PIN
sandboxoptionaltrue to simulate
Request (JSON)
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

GET /api/partner/v1/transaction.php?ident=<order_id>
Response
{ "Status": "successful", "ident": "BA-...",
  "amount": "100.00", "mobile_number": "08012345678" }

Status maps to successful, Processing, failed, or Refund.

Adex · Login / Balance

POST /api/partner/v1/adex/user.php

Accepts Basic base64(username:key), Token <key>, or Bearer <key>.

Response
{ "status": "success", "AccessToken": "abs_live_...",
  "balance": "5000.00", "username": "reseller" }

Adex · Buy Airtime

POST /api/partner/v1/adex/topup.php sandbox
FieldReqDescription
networkrequired1=MTN, 2=Airtel, 3=GLO, 4=9mobile (or a network trackid)
phonerequiredRecipient NG number
amountrequiredNGN, minimum 50
request-idoptionalYour idempotency reference
sandboxoptionaltrue to simulate
Request (JSON)
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"}'
Response
{ "network": "MTN", "status": "success",
  "amount": 100, "phone_number": "08012345678",
  "newbal": 4900, "request-id": "ref-123" }

Adex · Buy Data

POST /api/partner/v1/adex/data.php sandbox
FieldReqDescription
networkrequired1=MTN, 2=Airtel, 3=GLO, 4=9mobile
phonerequiredRecipient NG number
data_planrequiredPlan trackid (List Data Plans → id), e.g. 6BO1H
request-idoptionalYour idempotency reference
sandboxoptionaltrue to simulate
Request (JSON)
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.

EventFires when
transaction.settledAn order reached a successful final state
transaction.failedAn order failed or was rejected upstream
refund.issuedAn automatic or manual refund landed
wallet.creditedYour balance moved up
wallet.debitedYour balance moved down

Webhook payload

POST your registered URL · Content-Type: application/json
HeaderDescription
X-ABS-EventEvent name, as above
X-ABS-DeliveryDelivery id. Stable across retries — dedupe on this
X-ABS-TimestampUnix seconds, and part of the signed string
X-ABS-Signaturesha256=<hmac>, see below
Body
{
  "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"
  }
}
FieldDescription
data.order_idYour transaction reference — the same one requery takes
data.status1 settled, 3 failed, 6 refunded
data.serviceService type id
data.trans_kindTransaction kind id
data.changeOn 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.

PHP
$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);
Node
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.

Backoff30s, 2m, 10m, 30m, 2h, 6h, 12h
Attempts7, spanning roughly 24 hours
Endpoint disabledAfter 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.