Documentation API Airtime
L'API REST d'Unitech Airtime permet d'envoyer du crédit téléphonique depuis votre application. Toutes les requêtes et réponses sont en JSON, en UTF-8. Les montants sont des entiers en FCFA (XOF).
https://airtime.unitech.sn/api/v1Authentification
Créez une clé dans API & Webhooks de votre espace revendeur, puis envoyez-la dans l'en-tête Authorization. Ne l'utilisez jamais dans une application mobile ou du JavaScript côté navigateur : appelez l'API depuis votre serveur.
Authorization: Bearer uat_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
POST/airtime
Débite votre wallet et envoie le crédit au numéro indiqué. En cas d'échec, le montant est recrédité automatiquement.
| Champ | Type | Requis | Description |
|---|---|---|---|
phone | string | oui | Numéro du bénéficiaire : 771234567 ou 221771234567 |
amount | integer | oui | Montant en FCFA |
operator | string | non | Code opérateur (voir Opérateurs). Détecté automatiquement si absent. |
reference | string | recommandé | Votre référence unique (1–64 caractères : lettres, chiffres, . _ : -). Rejouer la même référence renvoie la vente existante sans nouveau débit. |
curl -X POST https://airtime.unitech.sn/api/v1/airtime \
-H "Authorization: Bearer VOTRE_CLE_API" \
-H "Content-Type: application/json" \
-d '{"phone":"771234567","amount":1000,"operator":"ORANGE_SN","reference":"CMD-1042"}'
Réponse 200 (réussi ou échec traité) ou 202 (en cours) :
{
"success": true,
"duplicate": false,
"data": {
"reference": "AT-7KQ3M9XH2TPA",
"client_reference": "CMD-1042",
"type": "airtime", // airtime | bundle
"bundle_id": null,
"bundle_label": null,
"status": "success", // success | pending | failed
"operator": "ORANGE_SN",
"phone": "221771234567",
"amount": 1000,
"currency": "XOF",
"failure_reason": null,
"refunded": false,
"created_at": "2026-10-26T11:08:12+00:00",
"completed_at": "2026-10-26T11:08:14+00:00",
"balance": 49000
}
}
pending : l'opérateur n'a pas encore confirmé. Ne relancez pas une nouvelle vente : attendez le webhook ou interrogez GET /transaction. Si vous renvoyez la requête, gardez la même reference.
Exemple PHP
$ch = curl_init('https://airtime.unitech.sn/api/v1/airtime');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('AIRTIME_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'phone' => '771234567',
'amount' => 1000,
'reference' => 'CMD-' . $orderId,
]),
CURLOPT_TIMEOUT => 60,
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
if (($res['data']['status'] ?? null) === 'success') { /* livré */ }
Exemple Node.js
const res = await fetch('https://airtime.unitech.sn/api/v1/airtime', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.AIRTIME_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone: '771234567', amount: 1000, reference: `CMD-${orderId}` }),
});
const { data, error } = await res.json();
Forfaits internet
Deux appels : récupérer la liste des forfaits, puis acheter celui choisi. Le montant débité est le prix du forfait (price). La liste évolue selon les opérateurs : rechargez-la régulièrement plutôt que de stocker les identifiants.
GET/bundles?operator=ORANGE_SN
Sans operator : forfaits de tous les opérateurs.
{
"success": true,
"data": [
{ "operator": "ORANGE_SN", "bundle_id": "b732d5d7c8c41f95562297a67b42deea-2",
"name": "Pass 1.5GB", "validity": "1J", "price": 500, "currency": "XOF" }
]
}
POST/bundle
| Champ | Type | Requis | Description |
|---|---|---|---|
phone | string | oui | Numéro du bénéficiaire |
bundle_id | string | oui | Identifiant obtenu via GET /bundles |
operator | string | recommandé | Opérateur du forfait (détecté par le numéro si absent) |
reference | string | recommandé | Votre référence unique (idempotence) |
curl -X POST https://airtime.unitech.sn/api/v1/bundle \
-H "Authorization: Bearer VOTRE_CLE_API" \
-H "Content-Type: application/json" \
-d '{"phone":"771234567","operator":"ORANGE_SN","bundle_id":"b732d5d7c8c41f95562297a67b42deea-2","reference":"CMD-1043"}'
La réponse a le même format que POST /airtime, avec "type": "bundle" et bundle_label.
GET/transaction
Statut d'une vente, par notre référence (AT-…) ou la vôtre. Sans paramètre reference, renvoie les dernières ventes (limit ≤ 100, filtre status).
curl "https://airtime.unitech.sn/api/v1/transaction?reference=CMD-1042" \ -H "Authorization: Bearer VOTRE_CLE_API"
GET/balance
{ "success": true, "data": { "balance": 49000, "currency": "XOF", "bonus_rate": 1 } }
GET/operators
Liste des opérateurs disponibles et de leurs limites. Opérateurs actuellement actifs :
| Code | Opérateur | Préfixes | Min | Max |
|---|---|---|---|---|
ORANGE_SN | Orange | 77,78 | 100 F | 50 000 F |
YAS_SN | Yas | 76 | 100 F | 50 000 F |
EXPRESSO_SN | Expresso | 70,72 | 100 F | 50 000 F |
Webhooks
Quand une vente atteint son statut final, nous envoyons un POST JSON à votre URL de webhook. Répondez 2xx rapidement ; sinon nous réessayons jusqu'à 6 fois avec un délai croissant. Traitez chaque événement de façon idempotente (par reference).
POST /votre/webhook
X-Airtime-Event: airtime.success
X-Airtime-Signature: t=1761476894,v1=5f2b9c...
{ "event": "airtime.success", "data": { "reference": "AT-7KQ3M9XH2TPA", "status": "success", ... } }
Vérifiez toujours la signature : HMAC-SHA256(secret, t + "." + corps_brut).
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_AIRTIME_SIGNATURE'] ?? '';
parse_str(str_replace(',', '&', $header), $sig); // ['t' => ..., 'v1' => ...]
$expected = hash_hmac('sha256', $sig['t'] . '.' . $raw, getenv('AIRTIME_WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int) $sig['t']) > 300) {
http_response_code(401); exit;
}
$event = json_decode($raw, true);
// ... mettre à jour votre commande
http_response_code(200);
Codes d'erreur
{ "success": false, "error": { "code": "INSUFFICIENT_BALANCE", "message": "Solde insuffisant (500 F)." } }
| HTTP | Code | Signification |
|---|---|---|
| 401 | UNAUTHENTICATED | Clé API absente |
| 401 | INVALID_API_KEY | Clé invalide ou révoquée |
| 403 | ACCOUNT_INACTIVE | Compte suspendu ou en attente |
| 403 | IP_NOT_ALLOWED | IP hors liste blanche |
| 402 | INSUFFICIENT_BALANCE | Solde du wallet insuffisant |
| 422 | VALIDATION_ERROR | Champ obligatoire manquant |
| 422 | INVALID_PHONE | Numéro invalide |
| 422 | INVALID_OPERATOR | Opérateur inconnu ou désactivé |
| 422 | OPERATOR_NOT_DETECTED | Opérateur non détectable, précisez operator |
| 422 | PHONE_OPERATOR_MISMATCH | Le numéro n'appartient pas à l'opérateur |
| 422 | AMOUNT_OUT_OF_RANGE | Montant hors limites de l'opérateur |
| 422 | INVALID_AMOUNT | Montant non entier |
| 422 | INVALID_REFERENCE | Format de référence invalide |
| 422 | INVALID_BUNDLE | Forfait introuvable pour cet opérateur |
| 404 | NOT_FOUND | Transaction introuvable |
| 429 | RATE_LIMITED | Trop de requêtes |
| 503 | MAINTENANCE | Service en maintenance |
| 500 | SERVER_ERROR | Erreur interne |
Wallet & bonus
Rechargez votre wallet par Wave depuis votre espace revendeur. Chaque recharge est créditée avec un bonus (1 % par défaut) : pour 20 000 F payés, vous recevez 20 200 F. Les ventes débitent la valeur faciale du crédit envoyé.