Partner API · v1

Sell data, airtime and bills from your own platform

Connect your website, app or bot to Global Link Data. Every purchase is one HTTPS request with a JSON body, paid from your Global Link Data wallet at your reseller price, and you get the result instantly or by webhook.

Base URLhttps://globallinkdata.com.ng/api/v1
FormatJSON request bodies (Content-Type: application/json) and JSON responses
Who can use itVendor (API) accounts. Contact us to upgrade your account.

Quick start

  1. Log in and open Developer API. Generate a live key (and a sandbox key for testing).
  2. Call GET /catalog to get plan IDs and your prices.
  3. Buy with e.g. POST /data, sending your key, the plan ID, the phone number and your transaction PIN.
  4. Set a webhook URL so you're told when pending orders finish.

Connect without coding

Already run a VTU website? You don't need to write any code to sell our services. Add Global Link Data as a supplier in your own admin panel, exactly like any other supplier — our API speaks the two most common VTU script formats.

API URLhttps://globallinkdata.com.ng/api/
API / auth typeVicom or Token (Larabills / VTUPlug-type scripts), or MSORG — choose whichever your script offers.
API key / tokenYour live API key. If you haven't locked it to your server's IP addresses, enter it as YOUR_API_KEY:YOUR_PIN (key, colon, transaction PIN).
Plan / package IDsOur IDs from the tables below. Many scripts can import them automatically from the list URLs.
ServiceVicom-type URLMSORG-type URL
BalanceGET /api/userGET /api/user/
AirtimePOST /api/airtimePOST /api/topup/
DataPOST /api/dataPOST /api/data/
Cable TVPOST /api/cable
verify: /api/cable/cable-validation
POST /api/cablesub/
verify: /api/validateiuc
ElectricityPOST /api/bill
verify: /api/bill/bill-validation
POST /api/billpayment/
verify: /api/validatemeter
Exam PINsPOST /api/examPOST /api/epin/
Recharge / data cardsPOST /api/recharge_card
POST /api/data_card
same
Bulk SMSPOST /api/bulk-smssame
Order statusGET /api/transaction/<request-id>same
Plan listsGET /api/data_plans · /api/get-cable · /api/get-cable-plan?cable=ID · /api/get-bill · /api/get-exam · /api/get-networks
  • Network numbers follow your script's format. Vicom-type: 1 MTN, 2 AIRTEL, 3 GLO, 4 9MOBILE. MSORG-type: 1 MTN, 2 GLO, 3 AIRTEL, 4 9MOBILE. Network names ("MTN") work too.
  • Common field spellings are all accepted (phone or mobile_number, plan or data_plan, iuc or smart_card_number, …), and replies include both formats' fields (status, Status, api_response, msg, request-id, new_balance…).
  • Send your request-id (most scripts do) and retries are safe: the same ID returns the original order. Your script's automatic status check (/api/transaction/<request-id>) works too.
  • Building something new? Use the v1 API below — it is cleaner and fully documented here.

Authentication

Send your API key as a Bearer token on every request:

Authorization: Bearer YOUR_API_KEY
  • Live keys look like gld_live_… and spend real money. Sandbox keys start with sandbox_ and are fully simulated (details). Same URL — the key decides.
  • Purchases need your transaction PIN in the body ("pin") — unless your key is locked to your server's IP addresses. Three wrong PINs lock purchases for 10 minutes.
  • IP allowlist (recommended): on the Developer API page, list your server's IP addresses. Requests from any other IP are refused, and purchases from your listed IPs no longer need the PIN — the simplest and safest setup for a server-to-server integration.
  • Keys don't expire. If one leaks, regenerate it on the Developer API page — the old key stops working at once.

Responses & errors

Every response has a status field. A purchase ends in one of four ways — handle all four:

Success (HTTP 200)
{
    "status": "success",
    "message": "Data purchase successful",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "balance_after": 44770
    }
}

Delivered. Keep the reference.

Pending (HTTP 200)
{
    "status": "pending",
    "success": true,
    "pending": true,
    "message": "Your order is processing.",
    "data": {
        "reference": "GLDE213826EA8181791581826"
    }
}

Accepted and charged; the provider hasn't confirmed yet. Wait for the webhook or poll /receipt. Do not retry.

Declined & refunded (HTTP 400)
{
    "status": "error",
    "success": false,
    "message": "Transaction failed. Your wallet has been refunded.",
    "refunded": true,
    "reference": "GLDE213826EA8181791581826"
}

The provider declined it and your wallet was refunded. Safe to retry.

Rejected (HTTP 4xx)
{
    "status": "error",
    "message": "Invalid transaction PIN"
}

Nothing was charged. Fix the request and try again.

HTTPMeaning
200Success or pending (check status).
400Missing/invalid field, wrong PIN, insufficient balance ("Insufficient balance. Please top up your wallet."), or declined and refunded.
401Missing, wrong or revoked API key, or suspended account.
403"API access requires a vendor account.", API access disabled for the account, request from an IP not on your allowlist, or the service is switched off.
404Unknown transaction reference (/receipt).
405Wrong method (e.g. GET on a POST endpoint).
429Too many requests — slow down (limits).
500Our error. Check /receipt before retrying a purchase.

Limits & safety

  • Purchases: 30 per minute per account. Smartcard/meter lookups: 20 per 5 minutes. Bulk SMS: 20 requests per hour.
  • Duplicate protection: an identical purchase (same service, number and amount/plan) within 5 seconds is refused with "Duplicate request detected" and not charged.
  • Retrying safely: send your own request_id with each purchase. If a request times out, send it again with the same ID — you get the original order back ("duplicate": true) instead of buying twice. Without a request_id, check /transactions before retrying.
  • Your account's daily spending limits and per-purchase limits also apply.

Sandbox testing

  • Use your sandbox_… key against the same base URL. Nothing real is bought and your live wallet is untouched.
  • Your sandbox wallet starts with ₦1,000,000 (reset it any time on the Developer API page).
  • Purchases succeed instantly — unless the phone, meter or smartcard number ends in 0000, which simulates a decline and refund so you can test that path.
  • Sandbox purchases also send webhooks, marked "sandbox": true.

Wallet balance

Your wallet and cashback balance. With a sandbox key it returns the simulated sandbox balance.

GET /api/v1/balance
curl "https://globallinkdata.com.ng/api/v1/balance" \
  -H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/balance");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/balance", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY"
  }
});
const data = await res.json();
console.log(data);
import requests

res = requests.get(
    "https://globallinkdata.com.ng/api/v1/balance",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "data": {
        "wallet_balance": 45250,
        "cashback_balance": 120.5,
        "currency": "NGN",
        "sandbox": false
    }
}

Catalog & your prices

Every active data plan, cable plan, electricity company and exam type, priced for your account tier. Call it to build your own price list, and cache it — prices change rarely.

GET /api/v1/catalog

Use data_plans[].id as plan_id when buying data, cable_plans[].id as plan_id for cable TV, cable_plans[].cable_id when validating a smartcard, and discos[].disco_id for electricity. The plan_id field inside a catalog row is our internal supplier code — don't send it.

curl "https://globallinkdata.com.ng/api/v1/catalog" \
  -H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/catalog");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/catalog", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY"
  }
});
const data = await res.json();
console.log(data);
import requests

res = requests.get(
    "https://globallinkdata.com.ng/api/v1/catalog",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "data": {
        "data_plans": [
            {
                "id": 209,
                "network": "GLO",
                "plan_id": "0",
                "plan_name": "1 GB",
                "display_name": "1GB",
                "data_type": "SME",
                "data_size": "1GB",
                "validity": "14 Days",
                "selling_price": "480.00",
                "network_id": 2,
                "cashback_type": null,
                "cashback_value": null
            }
        ],
        "cable_plans": [
            {
                "id": 12,
                "provider": "GOTV",
                "cable_id": "1",
                "plan_id": "gotv-max",
                "plan_name": "GOtv Max",
                "display_name": "GOtv Max",
                "selling_price": "8500.00"
            }
        ],
        "discos": [
            {
                "disco_id": 1,
                "disco_name": "Ikeja Electric",
                "charge": "0.00"
            }
        ],
        "exam_prices": {
            "WAEC": 3500,
            "NECO": 1200
        },
        "recharge_card_prices": {
            "MTN": {
                "100": 98
            }
        },
        "bulk_sms_pricing": {
            "price_per_page": 3,
            "discount_percent": 0,
            "cost_price": 2.5
        },
        "cashback": {
            "airtime": null
        }
    }
}

Buy data

Sends a data bundle to a phone number.

POST /api/v1/data
Body fieldTypeRequiredDescription
plan_idintegerYesThe plan's id from GET /catalog or the data plan table.
phonestringYes11-digit Nigerian number, e.g. 08031234567.
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
networkstringNoIgnored — the plan decides the network. Accepted for older integrations.
portedbooleanNoSend true for a number ported to another network, to skip the number-prefix check.
curl -X POST "https://globallinkdata.com.ng/api/v1/data" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id":209,"phone":"08157038870","pin":"12345","request_id":"order-1002"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/data");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'plan_id' => 209,
      'phone' => '08157038870',
      'pin' => '12345',
      'request_id' => 'order-1002',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/data", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "plan_id": 209,
      "phone": "08157038870",
      "pin": "12345",
      "request_id": "order-1002"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/data",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"plan_id":209,"phone":"08157038870","pin":"12345","request_id":"order-1002"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "Data purchase successful",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "cashback_earned": 0,
        "balance_before": 45250,
        "balance_after": 44770
    }
}
Number on a different network
{
    "status": "error",
    "message": "This number looks like an MTN line, not GLO. If it was ported, set ported to true."
}

Buy airtime

Tops up a phone number. Your discount is applied automatically, so you are charged less than the face value.

POST /api/v1/airtime
Body fieldTypeRequiredDescription
networkstringYesMTN, GLO, AIRTEL or 9MOBILE.
phonestringYes11-digit Nigerian number.
amountnumberYesFace value in naira. Default limits ₦50 – ₦10,000 per purchase.
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
portedbooleanNoSend true for a number ported to another network, to skip the number-prefix check.
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
curl -X POST "https://globallinkdata.com.ng/api/v1/airtime" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"network":"MTN","phone":"08031234567","amount":100,"pin":"12345","request_id":"order-1001"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/airtime");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'network' => 'MTN',
      'phone' => '08031234567',
      'amount' => 100,
      'pin' => '12345',
      'request_id' => 'order-1001',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/airtime", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "network": "MTN",
      "phone": "08031234567",
      "amount": 100,
      "pin": "12345",
      "request_id": "order-1001"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/airtime",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"network":"MTN","phone":"08031234567","amount":100,"pin":"12345","request_id":"order-1001"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "Airtime purchase successful",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "cashback_earned": 0,
        "balance_before": 45250,
        "balance_after": 45151
    }
}

Verify a smartcard / IUC

Looks up the customer name on a DStv, GOtv or Startimes card before you charge them. Free, but limited to 20 lookups per 5 minutes.

POST /api/v1/validate-cable
Body fieldTypeRequiredDescription
cable_idintegerYesThe provider: cable_plans[].cable_id from the catalog (see the cable table).
smartcardstringYesSmartcard / IUC number.
curl -X POST "https://globallinkdata.com.ng/api/v1/validate-cable" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cable_id":1,"smartcard":"7023456789"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/validate-cable");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'cable_id' => 1,
      'smartcard' => '7023456789',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/validate-cable", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "cable_id": 1,
      "smartcard": "7023456789"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/validate-cable",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"cable_id":1,"smartcard":"7023456789"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "success": true,
    "message": "SmartCard verified successfully.",
    "data": {
        "name": "IBRAHIM MUSA",
        "customer_name": "IBRAHIM MUSA",
        "smartcard": "7023456789"
    }
}

Pay for cable TV

Renews or changes a DStv, GOtv or Startimes subscription.

POST /api/v1/cabletv
Body fieldTypeRequiredDescription
plan_idintegerYesThe package's id from cable_plans (catalog or cable table). The package decides the provider.
smartcardstringYesSmartcard / IUC number. Verify it first with /validate-cable.
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
phonestringNoCustomer's phone number, passed to the provider.
curl -X POST "https://globallinkdata.com.ng/api/v1/cabletv" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id":12,"smartcard":"7023456789","pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/cabletv");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'plan_id' => 12,
      'smartcard' => '7023456789',
      'pin' => '12345',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/cabletv", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "plan_id": 12,
      "smartcard": "7023456789",
      "pin": "12345"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/cabletv",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"plan_id":12,"smartcard":"7023456789","pin":"12345"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "Subscription successful",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "cashback_earned": 0,
        "balance_before": 45250,
        "balance_after": 36750
    }
}

Verify a meter

Looks up the customer name and address on a meter. Free, 20 lookups per 5 minutes.

POST /api/v1/validate-meter
Body fieldTypeRequiredDescription
disco_idintegerYesThe electricity company's disco_id (catalog or disco table).
meter_numberstringYesMeter number.
meter_typestringYesprepaid or postpaid.
curl -X POST "https://globallinkdata.com.ng/api/v1/validate-meter" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/validate-meter");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'disco_id' => 1,
      'meter_number' => '45031234567',
      'meter_type' => 'prepaid',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/validate-meter", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "disco_id": 1,
      "meter_number": "45031234567",
      "meter_type": "prepaid"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/validate-meter",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "data": {
        "name": "ADEBAYO OLU",
        "address": "12 Allen Avenue, Ikeja"
    }
}

Pay an electricity bill

Buys prepaid units (returns the token) or pays a postpaid bill.

POST /api/v1/electricity
Body fieldTypeRequiredDescription
disco_idintegerYesThe electricity company's disco_id.
meter_numberstringYesMeter number. Verify it first with /validate-meter.
meter_typestringYesprepaid or postpaid.
amountnumberYesAmount in naira. Default limits ₦1,000 – ₦50,000. A company may add a service charge (see discos[].charge).
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
phonestringNoCustomer's phone number.

If the provider is slow, the response has "status": "pending" and "token": null; the token arrives in your webhook and in /receipt.

curl -X POST "https://globallinkdata.com.ng/api/v1/electricity" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid","amount":2000,"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/electricity");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'disco_id' => 1,
      'meter_number' => '45031234567',
      'meter_type' => 'prepaid',
      'amount' => 2000,
      'pin' => '12345',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/electricity", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "disco_id": 1,
      "meter_number": "45031234567",
      "meter_type": "prepaid",
      "amount": 2000,
      "pin": "12345"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/electricity",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid","amount":2000,"pin":"12345"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "success": true,
    "message": "Electricity purchase successful",
    "pending": false,
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "token": "1234-5678-9012-3456-7890",
        "cashback_earned": 0,
        "pending": false,
        "balance_before": 45250,
        "balance_after": 43250
    }
}

Buy exam PINs

WAEC, NECO, NABTEB and other result-checker PINs.

POST /api/v1/exam_pin
Body fieldTypeRequiredDescription
exam_namestringYesExam code, e.g. WAEC, NECO, NABTEB (see the exam table).
quantityintegerYes1 – 5 PINs.
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
curl -X POST "https://globallinkdata.com.ng/api/v1/exam_pin" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"exam_name":"WAEC","quantity":1,"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/exam_pin");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'exam_name' => 'WAEC',
      'quantity' => 1,
      'pin' => '12345',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/exam_pin", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "exam_name": "WAEC",
      "quantity": 1,
      "pin": "12345"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/exam_pin",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"exam_name":"WAEC","quantity":1,"pin":"12345"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "Exam PIN purchase successful",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "pins": [
            {
                "pin": "123456789012",
                "serial": "WRN123456789"
            }
        ],
        "balance_before": 45250,
        "balance_after": 41750
    }
}

Print recharge cards

Generates airtime PINs to print or resell. GET /recharge-card returns the networks and the denominations currently available.

POST /api/v1/recharge-card
Body fieldTypeRequiredDescription
network_idintegerYes1 MTN, 2 GLO, 3 AIRTEL, 4 9MOBILE.
denominationintegerYesCard value, e.g. 100, 200, 500 — must be one returned by GET /recharge-card.
quantityintegerNo1 – 50 cards (default 1).
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
name_on_cardstringNoBusiness name printed on the cards (max 30 characters).
curl -X POST "https://globallinkdata.com.ng/api/v1/recharge-card" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"network_id":1,"denomination":100,"quantity":2,"pin":"12345","name_on_card":"My Shop"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/recharge-card");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'network_id' => 1,
      'denomination' => 100,
      'quantity' => 2,
      'pin' => '12345',
      'name_on_card' => 'My Shop',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/recharge-card", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "network_id": 1,
      "denomination": 100,
      "quantity": 2,
      "pin": "12345",
      "name_on_card": "My Shop"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/recharge-card",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"network_id":1,"denomination":100,"quantity":2,"pin":"12345","name_on_card":"My Shop"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "2 card(s) ready",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "pins": [
            {
                "pin": "1234567890123456",
                "serial": "SN0001"
            },
            {
                "pin": "6543210987654321",
                "serial": "SN0002"
            }
        ]
    }
}

Print data cards

Generates data PINs. GET /data-card?net=MTN lists the plans for a network.

POST /api/v1/data-card
Body fieldTypeRequiredDescription
plan_idintegerYesData plan id (same IDs as /data).
quantityintegerNo1 – 5 cards (default 1).
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
curl -X POST "https://globallinkdata.com.ng/api/v1/data-card" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"plan_id":209,"quantity":1,"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/data-card");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'plan_id' => 209,
      'quantity' => 1,
      'pin' => '12345',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/data-card", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "plan_id": 209,
      "quantity": 1,
      "pin": "12345"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/data-card",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"plan_id":209,"quantity":1,"pin":"12345"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "1 card(s) ready",
    "data": {
        "reference": "GLDE213826EA8181791581826",
        "pins": [
            {
                "pin": "1234567890",
                "serial": "DC0001"
            }
        ]
    }
}

Send bulk SMS

Sends one message to many numbers. Limited to 20 requests per hour.

POST /api/v1/bulk_sms
Body fieldTypeRequiredDescription
sender_idstringYesSender name shown on phones, max 11 characters.
messagestringYesThe message. Long messages are charged per page (see bulk_sms_pricing in the catalog).
recipientsarray or stringYesPhone numbers as a JSON array, or one string separated by commas or new lines.
pinstringYesThe account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details).
request_idstringNoYour own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase.
curl -X POST "https://globallinkdata.com.ng/api/v1/bulk_sms" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sender_id":"MyShop","message":"Your order is ready.","recipients":["08031234567","08051234567"],"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/bulk_sms");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
      'sender_id' => 'MyShop',
      'message' => 'Your order is ready.',
      'recipients' => [
        '08031234567',
        '08051234567',
      ],
      'pin' => '12345',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
        'Content-Type: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/bulk_sms", {
  method: "POST",
  headers: {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "sender_id": "MyShop",
      "message": "Your order is ready.",
      "recipients": [
          "08031234567",
          "08051234567"
      ],
      "pin": "12345"
  })
});
const data = await res.json();
console.log(data);
import requests

res = requests.post(
    "https://globallinkdata.com.ng/api/v1/bulk_sms",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"sender_id":"MyShop","message":"Your order is ready.","recipients":["08031234567","08051234567"],"pin":"12345"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "message": "SMS sent to 2 recipient(s)",
    "data": {
        "reference": "GLDE213826EA8181791581826"
    }
}

Transaction status

Gets one transaction by its reference — use it to check a pending order. If an order has been pending for over 2 minutes, this call also re-checks it with the provider before answering.

GET /api/v1/receipt
Query parameterTypeRequiredDescription
referencestringYesThe reference you got when buying (query string).

data.status is success, pending, processing or failed. A failed order is refunded to your wallet automatically.

curl "https://globallinkdata.com.ng/api/v1/receipt?reference=GLDE213826EA8181791581826" \
  -H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/receipt?reference=GLDE213826EA8181791581826");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/receipt?reference=GLDE213826EA8181791581826", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY"
  }
});
const data = await res.json();
console.log(data);
import requests

res = requests.get(
    "https://globallinkdata.com.ng/api/v1/receipt",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={"reference":"GLDE213826EA8181791581826"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "success": true,
    "data": {
        "id": 95,
        "reference": "GLDE213826EA8181791581826",
        "transaction_ref": "GLDE213826EA8181791581826",
        "category": "data",
        "type": "debit",
        "amount": 480,
        "status": "success",
        "description": "GLO 1 GB for 08157038870",
        "phone": "08157038870",
        "balance_before": 45250,
        "balance_after": 44770,
        "charge": 0,
        "provider_ref": null,
        "created_at": "2026-10-09 22:37:06",
        "pins": [],
        "token": null,
        "meta": {},
        "recharge_card": null
    }
}
Unknown reference (404)
{
    "status": "error",
    "success": false,
    "message": "Transaction not found."
}

Transaction history

Your transactions, newest first.

GET /api/v1/transactions
Query parameterTypeRequiredDescription
pageintegerNoPage number (default 1).
per_pageintegerNo10 – 50 (default 20).
filterstringNoOnly one service, e.g. data, airtime, cabletv, electricity.
curl "https://globallinkdata.com.ng/api/v1/transactions?page=1&per_page=20&filter=data" \
  -H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/transactions?page=1&per_page=20&filter=data");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer YOUR_API_KEY',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/transactions?page=1&per_page=20&filter=data", {
  headers: {
    "Authorization": "Bearer YOUR_API_KEY"
  }
});
const data = await res.json();
console.log(data);
import requests

res = requests.get(
    "https://globallinkdata.com.ng/api/v1/transactions",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    params={"page":1,"per_page":20,"filter":"data"},
    timeout=60,
)
print(res.json())
Response · 200
{
    "status": "success",
    "data": {
        "transactions": [
            {
                "id": 95,
                "transaction_ref": "GLDE213826EA8181791581826",
                "category": "data",
                "amount": "480.00",
                "status": "success",
                "description": "GLO 1 GB for 08157038870",
                "created_at": "2026-10-09 22:37:06"
            }
        ],
        "total": 1,
        "page": 1,
        "per_page": 20,
        "total_pages": 1
    }
}

Webhooks

Set an HTTPS URL on the Developer API page. When any of your purchases reaches a final result — success, or failed (refunded) — we POST it to you. This is how you learn the outcome of a pending order without polling.

HeaderValue
X-Webhook-Eventtransaction.success or transaction.failed
X-Webhook-SignatureHMAC-SHA256 of the raw request body, using your live API key as the secret (your sandbox key if you have no live key), in hex. Check it before trusting the body.
Content-Typeapplication/json
{
    "event": "transaction.success",
    "reference": "GLDE213826EA8181791581826",
    "status": "success",
    "service": "electricity",
    "amount": 2000,
    "description": "Ikeja Electric prepaid ₦2,000 for 45031234567",
    "phone": "08031234567",
    "network": null,
    "refunded": false,
    "balance_after": 43250,
    "sandbox": false,
    "created_at": "2026-10-09T22:37:06+01:00",
    "timestamp": "2026-10-09T22:37:40+01:00",
    "token": "1234-5678-9012-3456-7890"
}
  • Reply with any 2xx status within 10 seconds. Otherwise we retry after 1, 5, 15, 60 and 240 minutes, then stop.
  • You may receive the same event more than once — use reference to ignore repeats.
  • Failed orders include a message; electricity includes the token. If you regenerate your live key, signatures use the new key.

Verifying the signature

<?php
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, 'YOUR_LIVE_API_KEY');
if (!hash_equals($expected, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
// Look up your order by $event['reference'] and mark it success / failed.
http_response_code(200);
const crypto = require("crypto");
const express = require("express");
const app = express();

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const expected = crypto.createHmac("sha256", process.env.LIVE_API_KEY)
    .update(req.body).digest("hex");
  const got = req.get("X-Webhook-Signature") || "";
  if (got.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body);
  // Look up your order by event.reference and mark it success / failed.
  res.sendStatus(200);
});
import hashlib, hmac, json
from flask import Flask, request, abort

app = Flask(__name__)

@app.post("/webhook")
def webhook():
    body = request.get_data()
    expected = hmac.new(b"YOUR_LIVE_API_KEY", body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Webhook-Signature", "")):
        abort(401)
    event = json.loads(body)
    # Look up your order by event["reference"] and mark it success / failed.
    return "", 200

Data plan IDs

Live list of active plans. Send the ID as plan_id. Your prices are in GET /catalog.

GLO (1 plans)
IDPlanTypeValidity
2091 GBSME14 Days

Cable plan IDs

Send the package ID as plan_id to /cabletv, and the provider's cable_id to /validate-cable.

No cable plans are active right now.

Electricity company IDs

Send as disco_id to /validate-meter and /electricity.

No electricity companies are active right now.

Exam types

Send the code as exam_name to /exam_pin.

CodeExam
WAECWest African Examinations Council
NECONational Examinations Council
NABTEBNational Business and Technical Examinations Board