AIRTIME API
This section outlines the structure and requirements for purchasing airtime via the API. The API enables seamless top-ups to one or more mobile numbers in a single request. All transactions are asynchronous, with per-recipient status updates delivered via callbacks or status queries.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
POST | /v1/gw/airtime/send | Submit airtime request |
POST | /v1/auth | Get access token |
GET | /v1/gw/airtime/{reference}/status | Query status of all recipients |
Authentication
All requests require an access token obtained via the authentication endpoint.
Endpoint
POST /v1/auth
Request
{
"consumer_key": "<your_consumer_key>",
"consumer_secret": "<your_consumer_secret>"
}
Example
curl -X POST https://sandbox.api.gateway.lipad.io/v1/auth \
-H "Content-Type: application/json" \
-d '{"consumer_key": "your_key", "consumer_secret": "your_secret"}'
Success Response
{
"access_token": "xyz789abc123",
"expiresIn": 3600
}
Token expires in 1 hour. Re-authenticate when needed.
Airtime Request
Send airtime to one or more phone numbers in a single request.
Endpoint
POST /v1/gw/airtime/send
Request Payload
{
"reference": "467777774564563453",
"recipients": [
{
"phone_number": "254XXXXXXXXXXX",
"amount": "10"
}
]
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
reference | String | Yes | Unique request ID (e.g., UUID). Must be unique per request. |
recipients | Array | Yes | List of recipients (1–1000). |
phone_number | String | Yes | E.164 format (e.g., 2547...) |
amount | String | Yes | Amount in local currency (e.g., "10" for KES 10) |
Use string for
amountto avoid precision issues.
Example Request
curl -X POST https://sandbox.api.gateway.lipad.io/v1/gw/airtime/send \
-H "Content-Type: application/json" \
-H "x-access-token: xyz789abc123" \
-d '{
"reference": "da091c64-07d6-4a7c-9a9f-e0134869166c",
"recipients": [
{ "phone_number": "254711000000", "amount": "10" },
{ "phone_number": "254722000000", "amount": "50" }
]
}'
Airtime Response
Success (Accepted)
{
"reference": "da091c64-07d6-4a7c-9a9f-e0134869166c",
"response_code": 720,
"response_description": "Airtime request has been accepted for processing"
}
Common Errors
| Code | Description |
|---|---|
721 | Validation Failed |
Callback Handling
The API sends one POST callback per recipient to your configured callback_url (set in the merchant portal).
Callback Payload (per recipient)
{
"phone_number": "2547XXXXXXXXXXXX",
"amount": 10,
"service_status": 602,
"external_reference": "da091c64-07d6-4a7c-9a9f-e0134869166c",
"provider_reference": "2611175",
"correlation_id": "GHCRODJAPX6I",
"message": "Airtime request processed successfully"
}
Fields
| Field | Type | Description |
|---|---|---|
phone_number | String | Recipient number |
amount | Number | Amount sent |
service_status | Number | See codes below |
external_reference | String | Your original reference |
provider_reference | String | Network transaction ID |
correlation_id | String | Internal ID |
message | String | Result description |
Service Status Codes
| Code | Name | Description |
|---|---|---|
600 | UNPROCESSED | Not started |
601 | PROCESSING | In progress |
602 | SUCCESSFUL | Airtime delivered |
603 | FAILED | Failed (check message) |
604 | JAMMED | Stuck at provider |
605 | REVERSED | Reversed |
606 | REFUNDED | Funds returned |
607 | SUBMITTED_FOR_REPROCESSING | Will retry |
Always respond with
200 OKto acknowledge callback and prevent retries.
Idempotency Key:
external_reference+phone_number
Status Query
Check the status of all recipients in a request.
Endpoint
GET /v1/gw/airtime/{reference}/status
Example
curl -X GET https://sandbox.api.gateway.lipad.io/v1/gw/airtime/da091c64-07d6-4a7c-9a9f-e0134869166c/status \
-H "x-access-token: xyz789abc123"
Response (Array)
[
{
"phone_number": "254718192689",
"amount": 10,
"service_status": 602,
"external_reference": "da091c64-07d6-4a7c-9a9f-e0134869166c",
"provider_reference": "2611175",
"correlation_id": "GHCRODJAPX6I",
"message": "Airtime request processed successfully"
},
{
"phone_number": "254722000000",
"amount": 50,
"service_status": 603,
"external_reference": "da091c64-07d6-4a7c-9a9f-e0134869166c",
"provider_reference": null,
"correlation_id": "ABC123XYZ",
"message": "Invalid phone number"
}
]
Returns same structure as callbacks. Poll until all statuses are final.
Quick Integration Checklist
| Step | Action |
|---|---|
| 1 | Get consumer_key & consumer_secret from Merchant Portal > My Business > API Keys |
| 2 | Call /v1/auth → get access_token |
| 3 | Generate unique reference (UUID) |
| 4 | POST to /v1/gw/airtime/send |
| 5 | Save reference |
| 6 | Set up public callback_url in portal |
| 7 | Handle callbacks → return 200 OK |
| 8 | (Optional) Poll status endpoint |
Best Practices
- Phone Format: Always use E.164 (
254..., not07...) - Idempotency: Deduplicate using
reference - Bulk Limit: Max 1000 recipients per request
- Retry Logic: Re-query if status is
601or604 - Sandbox Testing: Use any valid phone number`
Example: Full Flow (Node.js)
const axios = require("axios");
const API = "https://sandbox.api.gateway.lipad.io";
let token = "";
async function auth() {
const res = await axios.post(`${API}/v1/auth`, {
consumer_key: "your_key",
consumer_secret: "your_secret",
});
token = res.data.access_token;
}
async function sendAirtime() {
await auth();
const res = await axios.post(
`${API}/v1/gw/airtime/send`,
{
reference: "da091c64-07d6-4a7c-9a9f-e0134869166c",
recipients: [{ phone_number: "2547XXXXXXXXX", amount: "10" }],
},
{
headers: { "x-access-token": token },
}
);
console.log("Submitted:", res.data);
}
async function checkStatus(ref) {
await auth();
const res = await axios.get(`${API}/v1/gw/airtime/${ref}/status`, {
headers: { "x-access-token": token },
});
console.log("Status:", res.data);
}
You're ready to disburse airtime at scale.