Skip to main content

Quickstart

Make your first data purchase in three calls. Replace pk_live_xxx with your key.

1. Check your wallet balance

curl https://api.plustiveimpact.com/api/v1/balance \
-H "Authorization: Bearer pk_live_xxx"
{ "balance": 5000000, "currency": "NGN" }

Balance is in kobo - 5000000 is ₦50,000.00. Low? Top up with /v1/funding/initialize.

2. List the data plans you can buy

curl https://api.plustiveimpact.com/api/v1/plans \
-H "Authorization: Bearer pk_live_xxx"
[
{ "planId": "169ae891-…", "network": "MTN", "name": "1.0GB", "validity": "30 days", "price": 27000 }
]

price is your price in kobo (it reflects any custom pricing on your account). Grab a planId.

Each plan can also carry structured product fields, so you can group and label plans without parsing name: productId (a stable id for the same product across networks), sizeMb, validityDays, isUnlimited, and class. They appear once your account is on the product catalog.

If you resell plans to your own customers, use retailPrice and pricingBasis to price them correctly: pricingBasis: "retail" means sell at retailPrice (kobo) and keep the difference from price as your margin; pricingBasis: "special" means there is no retail figure (retailPrice is null), so add your own markup on top of price instead. Never guess one from the other, and never infer either from name.

Buying something other than data?

/v1/plans lists data plans only. Every other service has its own catalogue endpoint, and each returns [] until that service is enabled on your account - so an empty list here usually means you want one of these instead:

You sellList it withBuy it with
DataGET /v1/plansPOST /v1/data
Broadband (Smile, Spectranet, MTN)GET /v1/broadband/productsPOST /v1/broadband
Cable TVGET /v1/cabletv/productsPOST /v1/cabletv
ElectricityGET /v1/electricity/productsPOST /v1/electricity
Education (WAEC, NECO)GET /v1/education/productsPOST /v1/education
AirtimeGET /v1/networksPOST /v1/airtime

Wallet balance never affects any listing endpoint - it is checked only when you buy, so you can build your whole catalogue flow at a zero balance.

Buying airtime instead? List the networks you can top up with GET /v1/networks, then send a network's id as the networkId.

3. Buy data

curl -X POST https://api.plustiveimpact.com/api/v1/data \
-H "Authorization: Bearer pk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"planId": "169ae891-…",
"phone": "08030000000",
"clientReference": "order-1001"
}'
{
"reference": "PLS-9KQ2M7P…",
"status": "Success",
"providerReference": "118682181",
"priceCharged": 27000,
"balance": 4973000
}

That's it - the plan is delivered and your wallet is debited.

Idempotency

clientReference is your idempotency key, scoped per account. It makes every purchase safe to retry: re-sending the same clientReference returns the original transaction unchanged - no second vend, no extra charge. If a request times out, just retry with the same clientReference.

A replayed purchase is flagged so you can tell it apart from a fresh one:

  • the response body includes "idempotentReplay": true, and
  • the response carries an Idempotent-Replay: true header.

A fresh purchase has idempotentReplay as false and no such header.

{
"reference": "PLS-9KQ2M7P…",
"status": "Success",
"providerReference": "118682181",
"priceCharged": 27000,
"balance": 4973000,
"idempotentReplay": true
}

Use a unique clientReference for each distinct order - your own order id or a UUID works well. Reuse one only when you're deliberately retrying the same order.

If you reuse a clientReference for a different order - a different plan, recipient, or amount - we reject it with 409 Conflict (error code transaction.idempotency_conflict) and make no purchase, rather than silently returning the unrelated original:

{
"title": "Conflict",
"status": 409,
"detail": "This client reference was already used for a different order. Use a unique clientReference per order.",
"errors": [
{ "name": "transaction.idempotency_conflict", "reason": "This client reference was already used for a different order. Use a unique clientReference per order." }
]
}

A genuine retry - the same parameters - still returns the original transaction (flagged with idempotentReplay / the Idempotent-Replay header). Only a parameter mismatch conflicts; re-sending the exact same order is always safe.

The same rules apply to airtime and every bill payment.

Checking a transaction later

curl https://api.plustiveimpact.com/api/v1/transactions/PLS-9KQ2M7P… \
-H "Authorization: Bearer pk_live_xxx"

A Processing status means we're still confirming with the network - it'll settle, and we'll webhook you the terminal result.


Prefer to click than to copy-paste? Every endpoint in the API Reference has a live console - paste your key and send a real request.