Get started

The Data API lets you sell mobile-data top-ups across every Nigerian network — MTN, Glo, Airtel, and 9mobile — straight from your backend. Token auth, IP whitelisting, idempotent retries, and a shared wallet that any of your apps can debit.

Token authBearer token via X-API-Key.
IP whitelistLock tokens to trusted servers in production.
Idempotent retriesSafe retries with Idempotency-Key.
Shared walletOne balance debited per call — check it via /api/wallet/.

Your first call — buy 1GB of MTN data:

curl --location 'https://amigo.ng/api/data/' \
--header 'X-API-Key: YOUR_API_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
  "network": 1,
  "mobile_number": "09012345678",
  "plan": 1001,
  "Ported_number": true
}'

Overview

Amigo Developers gives you a single API to sell mobile-data top-ups:

  • Data API — buy mobile data on MTN, Glo, Airtel, and 9mobile. Per-plan pricing, idempotent retries, real-time efficiency scores.
  • Wallet balance New — a lightweight GET /api/wallet/ that returns your current live or test balance, so your backend can check funds before queuing a buy.

Base URL: https://amigo.ng/api. All responses are JSON. Money is Naira (the wallet endpoint returns up to 2 decimals; the data endpoints use whole-naira amounts) and most responses include a pre-formatted display string like "₦4,000".

Authentication

Pass your token in any of these headers — whichever your HTTP client makes easiest:

X-API-Key: amg_live_xxxxxxxxxxxxxxxx
# --or--
Authorization: Token amg_live_xxxxxxxxxxxxxxxx
# --or--
Authorization: Bearer amg_live_xxxxxxxxxxxxxxxx

Each token belongs to a single Amigo user + API client. The user_id identifies whose wallet gets debited; the client_id identifies which merchant owns the resulting work.

Keep tokens out of source control. Generate one per environment (staging vs. production), and rotate immediately if leaked. Whitelisting your IPs is supported — ask support to enable it on your client.

Test mode New

Build and integrate against a sandbox before touching real money. Test-mode tokens are intentionally familiar (Stripe-style) — the token prefix decides the environment:

Token prefixModeWalletOrder referencesInventory
amg_test_… test Sandbox wallet — fake money, starts at ₦1,000,000. AMG-COM-TEST-… Never touched — test orders don't affect live availability.
amg_live_… (or legacy) live Your Amigo wallet (real funds). AMG-COM-… Decremented at order time, restored on cancel.

Total isolation. A test token only sees test orders and test webhook endpoints; a live token only sees live ones. The two namespaces never cross. Every webhook payload includes a livemode boolean so subscribers can route or filter.

Refill the sandbox wallet:

POST /api/commerce/sandbox/reset-balance/ Test mode only

Refills the sandbox wallet back to ₦1,000,000. Requires an amg_test_ token — live tokens get 403 live_token_not_allowed.

curl -X POST "https://amigo.ng/api/commerce/sandbox/reset-balance/" \
  -H "X-API-Key: $TEST_TOKEN"

# → { "success": true, "data": { "test_balance": 1000000, "display": "₦1,000,000", "livemode": false } }
Recommended integration flow. Wire up your storefront with a test token, place a few orders, watch the webhook events fire to your test endpoint, exercise cancel/refund. When everything looks right, swap to a live token — same API surface, same response shapes, real money this time.

Wallet balance New

Check the balance your token can spend, in the current mode. Useful for showing a low-balance warning in your dashboard, gating a data buy before you call /api/data/, or reconciling against your own ledger.

GET /api/wallet/ Live + test

Mode follows the token: amg_live_… reads your live wallet, amg_test_… reads the sandbox. There's no cross-mode peek with one token. Response is never cached — it always reflects the latest debit, credit, or refund.

curl "https://amigo.ng/api/wallet/" \
  -H "X-API-Key: $TOKEN"

# →
{
  "success": true,
  "data": {
    "balance":  168.50,
    "display":  "₦168.50",
    "currency": "NGN",
    "livemode": true,
    "mode":     "live"
  }
}
FieldTypeNotes
balancenumberNaira, up to 2 decimal places.
displaystringPre-formatted for UI, e.g. "₦168.50".
currencystringAlways "NGN" today.
livemodebooleantrue for live tokens, false for test.
modestring"live" or "test" — convenience over livemode.

Errors

Every non-2xx response uses the same envelope:

{
  "success": false,
  "error":   "insufficient_balance",
  "message": "Wallet balance is below the order total."
}
HTTPerrorWhen
401invalid_tokenMissing, expired, or revoked token.
403ip_not_whitelistedYour client has a whitelist and your IP isn't on it.
404not_foundProduct, order, or endpoint doesn't exist (or isn't yours).
422invalid_*Body validation failed; message explains.
422insufficient_balanceWallet is below the order total.
422out_of_stockItem ran out between catalog read and order create.
429rate_limitedToo many requests in this minute window.
500server_errorUnexpected. Logged on our end; retry with backoff.

Idempotency

The Data API accepts an Idempotency-Key header on every state-changing POST. Retry with the same key and you get back the original response — no double-debit, no duplicate work.

curl -X POST https://amigo.ng/api/data/ \
  -H "X-API-Key: $TOKEN" \
  -H "Idempotency-Key: data-2026-05-17-7f3a" \
  -H "Content-Type: application/json" \
  --data @buy.json

Keys are opaque strings up to 80 chars. Scope is per-token; reuse across tokens has no effect. Cache TTL is 24h.

Data API Mobile data top-ups

One endpoint: POST /api/data/. Pick a plan from the live catalog below, send it with the customer's mobile number, and we route the top-up through the carrier.

POST /api/data/

POST /api/data/ Idempotent

Body:

{
  "network":       1,              // 1=MTN, 2=Glo, 4=Airtel, 3=9mobile
  "mobile_number": "09012345678",
  "plan":          1001,           // from /plans_catalog.php
  "Ported_number": true,           // if MNP'd
  "reference":     "MYAPP-TXN-123" // OPTIONAL — your own reference. If you retry
                                   // the same reference after a timeout you get
                                   // the cached response instead of a second vend,
                                   // and you can look it up later via
                                   // /api/data/status/?client_ref=MYAPP-TXN-123
}

Body parameters

FieldTypeRequiredNotes
networkintyes1=MTN, 2=Glo, 4=Airtel, 3=9mobile.
mobile_numberstringyesRecipient number, 10 or 11 digits.
planintyesPlan id from the live catalog below.
Ported_numberboolnoSet to true if the recipient has ported networks.
referencestringnoYour own reference (max 60 chars, A-Z a-z 0-9 . _ -). Doubles as an idempotency key: retries with the same reference return the cached response instead of vending twice. Also lets you requery via ?client_ref= if the original POST times out on your side.
<?php
$ch = curl_init('https://amigo.ng/api/data/');
curl_setopt_array($ch, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => [
    'X-API-Key: YOUR_API_TOKEN',
    'Content-Type: application/json',
    'Idempotency-Key: '.bin2hex(random_bytes(16))
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'network'       => 1,
    'mobile_number' => '09012345678',
    'plan'          => 1001,
    'Ported_number' => true
  ])
]);
echo curl_exec($ch);
fetch('https://amigo.ng/api/data/', {
  method: 'POST',
  headers: {
    'X-API-Key': 'YOUR_API_TOKEN',
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID()
  },
  body: JSON.stringify({
    network: 1,
    mobile_number: '09012345678',
    plan: 1001,
    Ported_number: true
  })
}).then(r => r.json()).then(console.log);
curl -X POST https://amigo.ng/api/data/ \
  -H "X-API-Key: $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  --data '{"network":1,"mobile_number":"09012345678","plan":1001,"Ported_number":true,"reference":"MYAPP-TXN-123"}'

Success response (200)

{
  "success":          true,
  "reference":        "AMG-20260713215802-ad5475",   // Amigo's reference — save this
  "client_reference": "MYAPP-TXN-123",               // echoed only if you sent one
  "message":          "Y'ello! You have gifted 2GB to 2348012345678. Share link https://go.mtn.ng/app/Dashboard with 2348012345678 to download the new MyMTN app for exciting offers.",
  "network":          1,
  "plan":             1001,
  "amount_charged":   350.00,
  "status":           "delivered"
}

On delivered transactions the message field is the carrier's own notification text (MTN's "Y'ello!…", Glo's / Airtel's equivalent). If the carrier didn't return one we fall back to a short Amigo-authored line like "2GB delivered to 09012345678 successfully.". Failed transactions always use the Amigo-authored copy so nothing carrier-branded ever pairs with a failed status.

Live plan catalog

These tables refresh on every page load from the same plan APIs your integration can use. Use the Plan ID column when calling POST /data/.

MTN (network 1)
MTN Plans
Plan IDCapacityValidityPrice
Glo (network 2)
Glo Plans
Plan IDCapacityValidityPrice
Airtel (network 4)
Airtel Plans
Plan IDCapacityValidityPrice

Status (requery) New

Look up the current state of a single data purchase you already made. Use this when the original POST /api/data/ response was delayed or you want to reconcile against your own ledger before refunding a customer.

GET /api/data/status/?ref=AMG-… or ?client_ref=… Live + test

Lookup: send exactly one of ?ref= (the Amigo reference we returned) or ?client_ref= (the reference you sent in the original POST body). Response shape is identical either way.

Scope: a token only sees purchases owned by its own client. Requests for a reference that belongs to another client return a clean 404 — same shape as "doesn't exist anywhere" — so reference enumeration is blocked.

# Look up by Amigo's reference
curl "https://amigo.ng/api/data/status/?ref=AMG-20260618215802-ad5475" \
  -H "X-API-Key: $TOKEN"

# Or look up by YOUR reference (from the "reference" field in POST /api/data/)
curl "https://amigo.ng/api/data/status/?client_ref=MYAPP-TXN-123" \
  -H "X-API-Key: $TOKEN"

# →
{
  "success": true,
  "data": {
    "reference":             "AMG-20260618215802-ad5475",
    "client_reference":      "MYAPP-TXN-123",           // only if you sent one
    "status":                "delivered",
    "network":               1,
    "mobile_number":         "09064703618",
    "plan":                  3333,
    "amount_charged":        900,
    "message":               "Y'ello! You have gifted 3GB to 2349064703618. Share link https://go.mtn.ng/app/Dashboard with 2349064703618 to download the new MyMTN app for exciting offers.",
    "purchased_at":          "2026-06-18T21:58:02+00:00",
    "queried_at":            "2026-06-21T11:34:59+00:00",
    "duration_ms":           2672,
    "original_http_status":  200,
    "vendor_response": {                                 // success only
      "server_message": "Records Successfully Fetched",
      "status":         true,
      "data": {
        "message":         "Request processed",
        "reference":       260618215802,
        "recipient_phone": "2349064703618",
        "bundle":          "3000",
        "data": {
          "data": {
            "notification": "Y'ello! You have gifted 3GB to 2349064703618…",
            "unit":         "MB",
            "productId":    "1622"
          }
        }
      }
    }
  }
}
FieldTypeNotes
referencestringThe Amigo reference you received from the original POST /data/.
client_referencestringPresent only when you sent a reference in the original POST body. Echoed regardless of which lookup param you used.
statusstringdelivered or failed. failed means we did not debit your wallet.
networkint1=MTN, 2=Glo, 4=Airtel, 3=9mobile.
mobile_numberstringThe recipient you originally targeted.
planintThe plan id from the live catalog.
amount_chargednumberWhat Amigo debited from your wallet, in Naira. 0 for failed transactions.
messagestringOn delivered transactions this is the carrier's own notification text (MTN "Y'ello!…", etc.). If the carrier didn't return one we fall back to a short Amigo-authored line. Failed transactions always use the Amigo-authored copy.
purchased_atstringISO 8601 UTC timestamp of the original purchase.
queried_atstringISO 8601 UTC timestamp of this requery call.
duration_msintHow long the original vend took (provider round-trip).
original_http_statusintThe HTTP status the original POST /data/ returned.
vendor_responseobjectDelivered vends only. Raw payload from the underlying carrier (SimServers / Switchboard → MTN, Glo, etc.). Passed through as-is — treat any field not in this table as best-effort and never inject it into copy you show your customers.

Error responses

HTTPerrorWhen
404not_foundNo purchase with that reference is owned by your client.
422missing_refNeither ?ref= nor ?client_ref= was provided.
422ambiguous_refBoth ?ref= and ?client_ref= were sent — pick one.
422invalid_refReference contains illegal characters or is over 80 chars.

Plan efficiency

Reliability scores so you can pick plans that are safer for production traffic. A score ≥ 98% is excellent; < 75% is poor and we recommend avoiding.

MTN
MTN Efficiency
Plan IDCapacityEfficiency
Glo
Glo Efficiency
Plan IDCapacityEfficiency
Airtel
Airtel Efficiency
Plan IDCapacityEfficiency