API Documentation
Embed Topteller wallet-powered recharge and bill-payment services into your website, app, ERP, or merchant portal.
Authentication
Authenticate requests using your API key pair or a JWT token for first-party flows.
API keys
Recommended for server-to-server integrations.
Platform payment keys
Used by approved merchant websites to create hosted Topteller Pay checkout requests.
JWT
Useful for first-party flows like API key management inside the app.
Developer Eligibility
API access is gated to ensure wallet integrity and responsible vending.
Quickstart
Follow these steps in order. Skipping service discovery is the most common cause of vending failures.
Upgrade the account to Diamond inside the Topteller app.
Open API Integration in-app, generate your credentials, and store the secret key securely.
Call the discovery endpoints first to fetch live service IDs and plan IDs.
Fund your Topteller wallet, then send purchase requests with an idempotency key.
Handle synchronous responses and optional webhook notifications for status updates.
Important Notes
Read these before going to production â they encode the most common integration mistakes.
Credentials and Wallet
Manage API credentials and inspect the wallet funding source for your integration.
Create a new API key pair for the signed-in Diamond user.
Fetch the active credential and metadata.
Update webhook URL, name, or rate limit.
Rotate your public and secret keys.
Deactivate the current key pair.
Check available wallet balance in NGN.
Get balance, user level, and reserved account details.
Service Discovery
Use these before vending so you always work with live IDs from your current provider catalogue.
Return the active services that can be integrated.
Return categories for grouping service UI.
Return a service and current user-aware pricing where available.
Return live plan IDs and provider plan codes for data/TV services.
Topteller Pay Checkout
Create hosted checkout requests so a Topteller user can securely approve payment on Topteller-owned screens.
Create a payment request and return checkoutUrl, checkoutToken, sdkUrl, fee, and totalAmount.
Fetch a payment request by reference for the authenticated merchant platform.
Browser SDK for opening the hosted Topteller checkout URL.
Deprecated endpoint. Always returns 410 because PIN collection must happen only on Topteller.
Purchases
Debit the integration wallet to vend services for your customers.
Top up airtime to a Nigerian phone number.
Buy airtime PINs from a live PIN service.
Vend a data plan using a live plan ID.
Subscribe a decoder using a live plan ID.
Vend prepaid or postpaid electricity units.
Buy WAEC/NECO/JAMB-style exam PINs.
Send bulk SMS after wallet debit.
Verification and Monitoring
Verify customer accounts before debit and monitor delivery after purchase.
Validate a decoder account before subscribing.
Validate meter details before purchase.
Fetch integration transaction history.
Inspect a single transaction.
Trigger a fresh provider verification for a pending transaction.
Get aggregate transaction statistics.
Webhooks
Set a webhook URL on your API key to receive purchase status notifications from Topteller.
application/json. Your endpoint must respond within 5s or the delivery will be retried.
Supported Events
{
"event": "data_purchase",
"reference": "DATA-123456789",
"status": "completed",
"phoneNumber": "08012345678",
"plan": "MTN 2GB SME",
"amount": 1200,
"finalPrice": 1188,
"timestamp": "2026-08-01T17:38:13.937Z"
}
Response Format
All endpoints return JSON. Check the success boolean first, then branch on message for error details.
â Success
{
"success": true,
"message": "Request processed successfully",
"data": {}
}
â Error
{
"success": false,
"message": "Readable error description"
}
Code Samples
Ready-to-use snippets for the most common integration paths. Replace placeholder keys with your real credentials.
Check wallet balance
Confirm that your integration wallet is funded before making debits.
curl --request GET "https://vtu.paradigmlimited.org/topteller/api/v1/api-keys/balance" \
--header "x-api-key: topt_live_xxxxxxxxxxxxxxxx" \
--header "x-secret-key: tsk_live_xxxxxxxxxxxxxxxx"
import axios from "axios";
const response = await axios.get("https://vtu.paradigmlimited.org/topteller/api/v1/api-keys/balance", {
headers: {
"x-api-key": "topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key": "tsk_live_xxxxxxxxxxxxxxxx"
}
});
console.log(response.data);
import requests
response = requests.get(
"https://vtu.paradigmlimited.org/topteller/api/v1/api-keys/balance",
headers={
"x-api-key": "topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key": "tsk_live_xxxxxxxxxxxxxxxx"
},
timeout=30,
)
print(response.json())
<?php
$curl = curl_init("https://vtu.paradigmlimited.org/topteller/api/v1/api-keys/balance");
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"x-api-key: topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key: tsk_live_xxxxxxxxxxxxxxxx"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
val request = Request.Builder()
.url("https://vtu.paradigmlimited.org/topteller/api/v1/api-keys/balance")
.get()
.addHeader("x-api-key", "topt_live_xxxxxxxxxxxxxxxx")
.addHeader("x-secret-key", "tsk_live_xxxxxxxxxxxxxxxx")
.build()
OkHttpClient().newCall(request).execute().use { response ->
println(response.body?.string())
}
Discover services and plans
Fetch live service IDs and plan IDs before vending.
curl --request GET "https://vtu.paradigmlimited.org/topteller/api/v1/api/services"
const services = await axios.get("https://vtu.paradigmlimited.org/topteller/api/v1/api/services");
const plans = await axios.get("https://vtu.paradigmlimited.org/topteller/api/v1/api/services/10/plans");
console.log({ services: services.data, plans: plans.data });
services = requests.get("https://vtu.paradigmlimited.org/topteller/api/v1/api/services", timeout=30).json()
plans = requests.get("https://vtu.paradigmlimited.org/topteller/api/v1/api/services/10/plans", timeout=30).json()
print(services)
print(plans)
<?php
$services = file_get_contents("https://vtu.paradigmlimited.org/topteller/api/v1/api/services");
$plans = file_get_contents("https://vtu.paradigmlimited.org/topteller/api/v1/api/services/10/plans");
echo $services;
echo $plans;
suspend fun fetchServices(client: OkHttpClient) {
val servicesRequest = Request.Builder()
.url("https://vtu.paradigmlimited.org/topteller/api/v1/api/services")
.build()
val plansRequest = Request.Builder()
.url("https://vtu.paradigmlimited.org/topteller/api/v1/api/services/10/plans")
.build()
client.newCall(servicesRequest).execute().use { println(it.body?.string()) }
client.newCall(plansRequest).execute().use { println(it.body?.string()) }
}
Purchase airtime
Send a debit request from your funded Topteller wallet. Always use an idempotency key.
curl --request POST "https://vtu.paradigmlimited.org/topteller/api/v1/purchase/airtime" \
--header "Content-Type: application/json" \
--header "x-api-key: topt_live_xxxxxxxxxxxxxxxx" \
--header "x-secret-key: tsk_live_xxxxxxxxxxxxxxxx" \
--header "x-idempotency-key: airtime-order-10001" \
--data '{
"serviceId": 1,
"phoneNumber": "08012345678",
"amount": 1000,
"pin": "1234"
}'
const response = await axios.post(
"https://vtu.paradigmlimited.org/topteller/api/v1/purchase/airtime",
{
serviceId: 1,
phoneNumber: "08012345678",
amount: 1000,
pin: "1234"
},
{
headers: {
"Content-Type": "application/json",
"x-api-key": "topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key": "tsk_live_xxxxxxxxxxxxxxxx",
"x-idempotency-key": "airtime-order-10001"
}
}
);
console.log(response.data);
payload = {
"serviceId": 1,
"phoneNumber": "08012345678",
"amount": 1000,
"pin": "1234",
}
response = requests.post(
"https://vtu.paradigmlimited.org/topteller/api/v1/purchase/airtime",
json=payload,
headers={
"x-api-key": "topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key": "tsk_live_xxxxxxxxxxxxxxxx",
"x-idempotency-key": "airtime-order-10001",
},
timeout=30,
)
print(response.json())
<?php
$payload = json_encode([
"serviceId" => 1,
"phoneNumber" => "08012345678",
"amount" => 1000,
"pin" => "1234",
]);
$curl = curl_init("https://vtu.paradigmlimited.org/topteller/api/v1/purchase/airtime");
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key: tsk_live_xxxxxxxxxxxxxxxx",
"x-idempotency-key: airtime-order-10001",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
val payload = """
{
"serviceId": 1,
"phoneNumber": "08012345678",
"amount": 1000,
"pin": "1234"
}
""".trimIndent()
val request = Request.Builder()
.url("https://vtu.paradigmlimited.org/topteller/api/v1/purchase/airtime")
.post(payload.toRequestBody("application/json".toMediaType()))
.addHeader("x-api-key", "topt_live_xxxxxxxxxxxxxxxx")
.addHeader("x-secret-key", "tsk_live_xxxxxxxxxxxxxxxx")
.addHeader("x-idempotency-key", "airtime-order-10001")
.build()
OkHttpClient().newCall(request).execute().use { response ->
println(response.body?.string())
}
Requery a transaction
Use this when a purchase is still pending and you need a fresh provider check.
curl --request POST "https://vtu.paradigmlimited.org/topteller/api/v1/api/transactions/248/requery" \
--header "x-api-key: topt_live_xxxxxxxxxxxxxxxx" \
--header "x-secret-key: tsk_live_xxxxxxxxxxxxxxxx"
const response = await axios.post(
"https://vtu.paradigmlimited.org/topteller/api/v1/api/transactions/248/requery",
{},
{
headers: {
"x-api-key": "topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key": "tsk_live_xxxxxxxxxxxxxxxx"
}
}
);
console.log(response.data);
response = requests.post(
"https://vtu.paradigmlimited.org/topteller/api/v1/api/transactions/248/requery",
headers={
"x-api-key": "topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key": "tsk_live_xxxxxxxxxxxxxxxx",
},
timeout=30,
)
print(response.json())
<?php
$curl = curl_init("https://vtu.paradigmlimited.org/topteller/api/v1/api/transactions/248/requery");
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"x-api-key: topt_live_xxxxxxxxxxxxxxxx",
"x-secret-key: tsk_live_xxxxxxxxxxxxxxxx"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
val request = Request.Builder()
.url("https://vtu.paradigmlimited.org/topteller/api/v1/api/transactions/248/requery")
.post("{}".toRequestBody("application/json".toMediaType()))
.addHeader("x-api-key", "topt_live_xxxxxxxxxxxxxxxx")
.addHeader("x-secret-key", "tsk_live_xxxxxxxxxxxxxxxx")
.build()
OkHttpClient().newCall(request).execute().use { response ->
println(response.body?.string())
}
Initiate hosted checkout
Create a Topteller Pay request and send your customer to checkoutUrl for secure approval.
curl --request POST "https://vtu.paradigmlimited.org/topteller/api/v1/external-payment/initiate" \
--header "Content-Type: application/json" \
--header "x-platform-api-key: tk_live_xxxxxxxxxxxxxxxx" \
--header "idempotency-key: checkout-order-10001" \
--data '{
"payerPhone": "08012345678",
"amount": 2500,
"description": "Order #10001",
"listingReference": "LIST-88"
}'
const response = await axios.post(
"https://vtu.paradigmlimited.org/topteller/api/v1/external-payment/initiate",
{
payerPhone: "08012345678",
amount: 2500,
description: "Order #10001",
listingReference: "LIST-88"
},
{
headers: {
"Content-Type": "application/json",
"x-platform-api-key": "tk_live_xxxxxxxxxxxxxxxx",
"idempotency-key": "checkout-order-10001"
}
}
);
console.log(response.data.data.checkoutUrl);
response = requests.post(
"https://vtu.paradigmlimited.org/topteller/api/v1/external-payment/initiate",
json={
"payerPhone": "08012345678",
"amount": 2500,
"description": "Order #10001",
"listingReference": "LIST-88",
},
headers={
"x-platform-api-key": "tk_live_xxxxxxxxxxxxxxxx",
"idempotency-key": "checkout-order-10001",
},
timeout=30,
)
print(response.json())
<?php
$payload = json_encode([
"payerPhone" => "08012345678",
"amount" => 2500,
"description" => "Order #10001",
"listingReference" => "LIST-88",
]);
$curl = curl_init("https://vtu.paradigmlimited.org/topteller/api/v1/external-payment/initiate");
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-platform-api-key: tk_live_xxxxxxxxxxxxxxxx",
"idempotency-key: checkout-order-10001",
],
]);
echo curl_exec($curl);
curl_close($curl);
val payload = """
{
"payerPhone": "08012345678",
"amount": 2500,
"description": "Order #10001",
"listingReference": "LIST-88"
}
""".trimIndent()
val request = Request.Builder()
.url("https://vtu.paradigmlimited.org/topteller/api/v1/external-payment/initiate")
.post(payload.toRequestBody("application/json".toMediaType()))
.addHeader("x-platform-api-key", "tk_live_xxxxxxxxxxxxxxxx")
.addHeader("idempotency-key", "checkout-order-10001")
.build()
OkHttpClient().newCall(request).execute().use { response ->
println(response.body?.string())
}