Send Top-up
Send airtime top-up to a phone number using the Version 1 API.
Version 1 API (v1.4) -- This API is in maintenance mode and will not receive new features. New integrations should use the Version 2 API which provides richer transaction management, product catalogs, bundle support, and multiple payment methods.
POST /adp
Send airtime to a mobile phone number. The amount is deducted from your account balance for the destination network.
Request
POST https://api.clickairtime.com/adp
Content-Type: application/json
X-Click-Airtime-Email: your@email.com
X-Click-Airtime-Token: your-api-token
Headers
| Header | Required | Description |
|---|---|---|
Content-Type | Yes | Must be application/json |
X-Click-Airtime-Email | Yes | Your registered account email address |
X-Click-Airtime-Token | Yes | Your API authentication token |
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
msisdn | string | Yes | Recipient phone number in international format without + prefix (e.g., 263719545499) |
amount | number | Conditional | Amount to send in the destination currency (e.g., 100). Required unless using a FIXED_PRODUCT via productId. |
extRefId | string | No | Your external reference ID for tracking and deduplication (e.g., 1W89-GZ881-WYYQ) |
productId | number | No | Product identifier from the Product Catalog. When provided, routes the top-up through the product's gateway configuration. |
processingMode | string | No | "SYNC" (default) or "ASYNC". In SYNC mode the response carries a terminal state (SUCCESS / FAILED) within ~10s. In ASYNC mode the request is queued and the response returns immediately with code: 232; poll GET /adp/transactions for the final state. |
sendSMS | boolean | No | Whether to send an SMS notification on success. Default false. |
Product-based top-ups: You can optionally specify a productId to use a pre-configured product. For FIXED_PRODUCT products, the amount is pre-determined and the amount field is ignored. For OPEN_PRODUCT products, you must still provide the amount. Browse available products via the Product Catalog endpoints.
Code Examples
curl -X POST https://api.clickairtime.com/adp \
-H "Content-Type: application/json" \
-H "X-Click-Airtime-Email: your@email.com" \
-H "X-Click-Airtime-Token: your-api-token" \
-d '{
"msisdn": "263719545499",
"amount": 100,
"extRefId": "1W89-GZ881-WYYQ"
}'const response = await fetch('https://api.clickairtime.com/adp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Click-Airtime-Email': 'your@email.com',
'X-Click-Airtime-Token': 'your-api-token',
},
body: JSON.stringify({
msisdn: '263719545499',
amount: 100,
extRefId: '1W89-GZ881-WYYQ',
}),
});
const result = await response.json();
if (result.code === 1) {
console.log('Transaction ID:', result.data.transactionId);
console.log('State:', result.data.state);
console.log('Amount:', result.data.amount);
} else {
console.error('Top-up failed:', result.message);
}import requests
response = requests.post(
'https://api.clickairtime.com/adp',
headers={
'Content-Type': 'application/json',
'X-Click-Airtime-Email': 'your@email.com',
'X-Click-Airtime-Token': 'your-api-token',
},
json={
'msisdn': '263719545499',
'amount': 100,
'extRefId': '1W89-GZ881-WYYQ',
}
)
result = response.json()
if result['code'] == 1:
print(f"Transaction ID: {result['data']['transactionId']}")
print(f"State: {result['data']['state']}")
print(f"Amount: {result['data']['amount']}")
else:
print(f"Top-up failed: {result['message']}")$response = Http::withHeaders([
'Content-Type' => 'application/json',
'X-Click-Airtime-Email' => 'your@email.com',
'X-Click-Airtime-Token' => 'your-api-token',
])->post('https://api.clickairtime.com/adp', [
'msisdn' => '263719545499',
'amount' => 100,
'extRefId' => '1W89-GZ881-WYYQ',
]);
$result = $response->json();
if ($result['code'] === 1) {
echo "Transaction ID: " . $result['data']['transactionId'];
echo "State: " . $result['data']['state'];
echo "Amount: " . $result['data']['amount'];
} else {
echo "Top-up failed: " . $result['message'];
}String body = """
{
"msisdn": "263719545499",
"amount": 100,
"extRefId": "1W89-GZ881-WYYQ"
}
""";
HttpResponse<String> response = Unirest
.post("https://api.clickairtime.com/adp")
.header("Content-Type", "application/json")
.header("X-Click-Airtime-Email", "your@email.com")
.header("X-Click-Airtime-Token", "your-api-token")
.body(body)
.asString();
JSONObject result = new JSONObject(response.getBody());
if (result.getInt("code") == 1) {
JSONObject data = result.getJSONObject("data");
System.out.println("Transaction ID: " + data.getString("transactionId"));
System.out.println("State: " + data.getString("state"));
System.out.println("Amount: " + data.getDouble("amount"));
} else {
System.out.println("Top-up failed: " + result.getString("message"));
}Top-up with Product ID (Fixed Product)
curl -X POST https://api.clickairtime.com/adp \
-H "Content-Type: application/json" \
-H "X-Click-Airtime-Email: your@email.com" \
-H "X-Click-Airtime-Token: your-api-token" \
-d '{
"msisdn": "233541112259",
"productId": 42,
"extRefId": "FIX-001-GHS5"
}'const response = await fetch('https://api.clickairtime.com/adp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Click-Airtime-Email': 'your@email.com',
'X-Click-Airtime-Token': 'your-api-token',
},
body: JSON.stringify({
msisdn: '233541112259',
productId: 42,
extRefId: 'FIX-001-GHS5',
}),
});
const result = await response.json();
if (result.code === 1) {
console.log('Transaction ID:', result.data.transactionId);
console.log('Amount:', result.data.amount);
} else {
console.error('Top-up failed:', result.message);
}import requests
response = requests.post(
'https://api.clickairtime.com/adp',
headers={
'Content-Type': 'application/json',
'X-Click-Airtime-Email': 'your@email.com',
'X-Click-Airtime-Token': 'your-api-token',
},
json={
'msisdn': '233541112259',
'productId': 42,
'extRefId': 'FIX-001-GHS5',
}
)
result = response.json()
if result['code'] == 1:
print(f"Transaction ID: {result['data']['transactionId']}")
print(f"Amount: {result['data']['amount']}")
else:
print(f"Top-up failed: {result['message']}")Response
Success (200)
{
"message": "Top-up successful! Airtime has been successfully topped up",
"statusCode": 200,
"code": 1,
"data": {
"transactionId": "f661f75a-c249-4aef-8cf3-abcdef123456",
"state": "SUCCESS",
"amount": 100,
"effectiveAmount": "0.06",
"currency": "MWK",
"wallet": {
"currency": "USD",
"amount": "0.06",
"fxRate": "1729.0500"
},
"provider": {
"transaction_id": "EXT-12345",
"reference_code": "REF-67890"
}
}
}
Failure -- Insufficient Balance
{
"message": "Request declined due to low balance",
"statusCode": 500,
"code": 20
}
Failure -- Invalid Recipient
{
"message": "Recipient's number is invalid",
"statusCode": 500,
"code": 30
}
Failure -- Duplicate External Reference
{
"message": "External reference ID already exists",
"statusCode": 500,
"code": 90
}
Error (401) -- Authentication Failed
{
"message": "Invalid or missing authentication credentials",
"statusCode": 401
}
Response Fields
| Field | Type | Description |
|---|---|---|
message | string | Human-readable result description |
statusCode | number | HTTP status code |
code | number | Top-level response code. 1 for success, 232 for async/pending, other values indicate specific errors — see Response Codes. |
data.transactionId | string | Unique Click Airtime transaction identifier (UUID) |
data.state | string | Transaction state. Terminal values: SUCCESS, FAILED, TIMEOUT, UNDERLIVERED. Non-terminal: PENDING, PROCESSING. |
data.amount | number | Amount sent in destination currency |
data.currency | string | Destination (recipient) currency, e.g. MWK, GHS |
data.effectiveAmount | string | number | Amount deducted from your wallet (in your wallet's currency, after FX/rate conversion) |
data.wallet.currency | string | Currency your wallet was debited in (e.g. USD) |
data.wallet.amount | string | number | Amount actually debited from your wallet in that currency. Use this for FX reconciliation. |
data.wallet.fxRate | string | number | FX rate applied for the conversion (destination units per wallet unit). null for same-currency top-ups. |
data.provider.transaction_id | string | Provider's transaction ID |
data.provider.reference_code | string | Provider's reference code |
Error Responses
| HTTP Status | Description |
|---|---|
| 200 | Success (check code field — value 1 means success) |
| 401 | Invalid or missing X-Click-Airtime-Email or X-Click-Airtime-Token headers |
| 403 | Business logic failure (insufficient balance, invalid number, etc.) — check code and message |
| 500 | Internal server error -- contact support if persistent |
Important Notes
- Phone numbers must be in international format without the
+prefix (e.g.,263719545499for Zimbabwe,233541112259for Ghana) - The
amountis specified in the destination currency (the currency of the recipient's country) - Use a unique
extRefIdfor each transaction to enable deduplication and easy lookup via the Transactions endpoint - For FX reconciliation, use
data.wallet—wallet.amountis the exact amount debited from your wallet inwallet.currency, andwallet.fxRateis the rate that was applied. No need to call a separate rates endpoint. - Most transactions resolve synchronously. If you receive
data.state: "PENDING"(top-levelcode: 232), poll the Transactions endpoint byextRefId;data.statewill move to a terminal value (SUCCESS,FAILED,TIMEOUT,UNDERLIVERED) once the provider responds — typically within seconds, and guaranteed to be terminal within about 12 minutes (our reconciliation service converges every transaction by then). - If the top-up fails at the network provider level, the balance is not deducted from your account
For the complete list of error codes and their meanings, see the Response Codes reference.
Migrating to V2? The Version 2 API uses a simplified flow with API key authentication and richer product selection. See the Migration Guide for a step-by-step walkthrough.
