Documentation de l'API : /api/app-stats
URL de production
https://votre-domaine.com/api/app-stats
En local : http://localhost:3000/api/apps-stats ou http://localhost:3000/api/app-stats.
Méthode
GET uniquement.
Description
Retourne les statistiques de l'application associée à la clé API (agrégats SMS / OTP par canal, totaux, réseaux, courbe sur 7 jours, journaux récents masqués). Même logique métier que le tableau de bord admin pour une app donnée, mais sans session : uniquement le header api-key.
Exemple :
GET /api/app-stats HTTP/1.1
Host: votre-domaine.com
api-key: VOTRE_CLE_API_ICI
Authentification
| Header | Obligatoire | Description |
|---|---|---|
api-key | Oui | Clé API de l'application (identique à send-message / send-otp). |
Paramètres de requête (query)
Tous optionnels. Priorité de la période : month → sinon from / to → sinon mois civil en cours (à partir du 1er du mois, sans borne de fin explicite dans period.to).
| Paramètre | Format | Description |
|---|---|---|
month | YYYY-MM | Statistiques sur le mois civil (1er 00:00:00 → dernier jour 23:59:59). |
from | Date parsable par Date | Début (00:00:00). |
to | Idem | Fin (23:59:59). |
logsLimit | Entier 1–200 | Nombre max d'entrées par liste (logs.sms et logs.otp). Défaut : 50. |
monthinvalide → 400 avecsuccess: falseet un message explicite.fromseul outoseul est autorisé (intervalle ouvert d'un côté).logs: les entrées sont filtrées par la même période que les agrégats (baseFilter), triées parcreatedAtdécroissant.
Ce que l'API ne renvoie pas
Aucun champ de prix ou de coût (cost, totalCost, costMinor, etc.) : la réponse est limitée aux volumes, statuts, parties SMS, journaux masqués et métadonnées non sensibles.
Structure de la réponse 200 OK
Enveloppe :
{
"success": true,
"data": { }
}
data.app
| Champ | Type | Description |
|---|---|---|
appId | string | Identifiant métier de l'application. |
name | string ou objet i18n | Nom affiché (selon le document en base). |
data.period
| Champ | Type | Description |
|---|---|---|
from | string ISO date ou null | Borne inférieure effective ($gte). |
to | string ISO date ou null | Borne supérieure effective ($lte), si définie. |
month | string YYYY-MM ou null | Présent si le filtre month a été utilisé. |
data.stats
Six blocs homogènes, clés fixes :
messageNational,messageInternational,messageWhatsapp: SMS.otpNational,otpInternational,otpWhatsapp: OTP.
Chaque bloc message contient :
| Champ | Type | Description |
|---|---|---|
sent | number | Messages envoyés avec succès (statut sent). |
failed | number | Échecs (not_sent côté agrégation SMS). |
parts | number | Somme des parts sur les envois réussis. |
successRate | string | Pourcentage "0.00" à "100.00" : sent / (sent + failed). |
Chaque bloc OTP contient en plus :
| Champ | Type | Description |
|---|---|---|
verified | number | OTP vérifiés (lignes verified). |
verificationRate | string | "0.00"–"100.00" : verified / sent si sent > 0, sinon "0". |
data.totals
| Champ | Type | Description |
|---|---|---|
totalMessages | number | Somme des sent + failed sur les six blocs stats. |
totalSent | number | Somme des sent. |
totalFailed | number | Somme des failed. |
totalQueued | number | Documents SMS queued dans la période. |
totalNotSent | number | Documents SMS not_sent dans la période. |
totalParts | number | Somme des parts sur les six blocs. |
totalOtpVerified | number | Somme des verified des trois blocs OTP. |
data.networkStats
Compteurs agrégés (SMS + OTP) par réseau détecté :
| Clé | Type | Description |
|---|---|---|
mauritel | number | |
mattel | number | |
chinguitell | number | |
inconnu | number | |
international | number |
data.chartData
Tableau de 7 jours calendaires glissants (aujourd'hui inclus), libellés date en locale fr-FR (ex. lun. 7 avr.). Indépendant du filtre month / from / to des agrégats principaux.
| Champ | Type | Description |
|---|---|---|
date | string | Libellé court du jour. |
totalMessages | number | SMS + OTP (envoyés / échoués / vérifiés selon la logique du graphique). |
totalSent | number | |
smsSent | number | |
otpSent | number | Inclut sent et verified pour la courbe. |
otpVerified | number | |
deliveryRate | number | Pourcentage arrondi à une décimale (nombre, pas une chaîne). |
data.logs
| Champ | Type | Description |
|---|---|---|
limit | number | Plafond appliqué (identique à logsLimit clampé 1–200). |
sms | array | Dernières lignes SmsLog de la période. |
otp | array | Dernières lignes OtpLog de la période. |
Élément logs.sms[*] :
| Champ | Type | Description |
|---|---|---|
kind | "sms" | |
endpoint | string | send-otp ou send-message. |
status | string | sent, not_sent, queued. |
recipient | string | Numéro masqué (même règle que le modèle). |
messagePreview | string | null | Aperçu court. |
provider | string | null | |
parts | number | |
createdAt | string ISO | null | |
errorCode | string | null | |
errorMessage | string | null | |
messageId | string | null |
Élément logs.otp[*] : comme SMS, avec kind: "otp", plus :
| Champ | Type | Description |
|---|---|---|
channel | string | sms ou whatsapp. |
verifiedAt | string ISO | null | Si vérification. |
Les champs sensibles (corps de message complet, OTP, coûts) ne sont pas inclus.
Exemple de réponse statique (data)
Exemple illustratif (valeurs fictives) pour intégration / tests de parsing :
{
"success": true,
"data": {
"app": {
"appId": "demo-app",
"name": "Application démo"
},
"period": {
"from": "2026-04-01T00:00:00.000Z",
"to": "2026-04-30T23:59:59.999Z",
"month": "2026-04"
},
"stats": {
"messageNational": {
"sent": 120,
"failed": 3,
"parts": 125,
"successRate": "97.56"
},
"otpNational": {
"sent": 80,
"failed": 2,
"verified": 72,
"parts": 80,
"successRate": "97.56",
"verificationRate": "90.00"
},
"messageInternational": {
"sent": 10,
"failed": 1,
"parts": 12,
"successRate": "90.91"
},
"otpInternational": {
"sent": 5,
"failed": 0,
"verified": 4,
"parts": 5,
"successRate": "100.00",
"verificationRate": "80.00"
},
"messageWhatsapp": {
"sent": 0,
"failed": 0,
"parts": 0,
"successRate": 0
},
"otpWhatsapp": {
"sent": 15,
"failed": 1,
"verified": 14,
"parts": 15,
"successRate": "93.75",
"verificationRate": "93.33"
}
},
"totals": {
"totalMessages": 236,
"totalSent": 230,
"totalFailed": 7,
"totalQueued": 2,
"totalNotSent": 5,
"totalParts": 317,
"totalOtpVerified": 90
},
"networkStats": {
"mauritel": 180,
"mattel": 40,
"chinguitell": 25,
"inconnu": 3,
"international": 12
},
"chartData": [
{
"date": "lun. 7 avr.",
"totalMessages": 42,
"totalSent": 40,
"smsSent": 22,
"otpSent": 18,
"otpVerified": 15,
"deliveryRate": 95.2
},
{
"date": "mar. 8 avr.",
"totalMessages": 55,
"totalSent": 52,
"smsSent": 30,
"otpSent": 25,
"otpVerified": 22,
"deliveryRate": 94.5
},
{
"date": "mer. 9 avr.",
"totalMessages": 38,
"totalSent": 36,
"smsSent": 18,
"otpSent": 20,
"otpVerified": 17,
"deliveryRate": 94.7
},
{
"date": "jeu. 10 avr.",
"totalMessages": 61,
"totalSent": 58,
"smsSent": 35,
"otpSent": 26,
"otpVerified": 24,
"deliveryRate": 95.1
},
{
"date": "ven. 11 avr.",
"totalMessages": 48,
"totalSent": 45,
"smsSent": 28,
"otpSent": 20,
"otpVerified": 18,
"deliveryRate": 93.8
},
{
"date": "sam. 12 avr.",
"totalMessages": 22,
"totalSent": 21,
"smsSent": 12,
"otpSent": 10,
"otpVerified": 9,
"deliveryRate": 95.5
},
{
"date": "dim. 13 avr.",
"totalMessages": 30,
"totalSent": 29,
"smsSent": 15,
"otpSent": 15,
"otpVerified": 13,
"deliveryRate": 96.7
}
],
"logs": {
"limit": 50,
"sms": [
{
"kind": "sms",
"endpoint": "send-message",
"status": "sent",
"recipient": "+222****12",
"messagePreview": "Bonjour, v",
"provider": "national",
"parts": 1,
"createdAt": "2026-04-13T10:15:00.000Z",
"errorCode": null,
"errorMessage": null,
"messageId": "msg-static-001"
},
{
"kind": "sms",
"endpoint": "send-message",
"status": "not_sent",
"recipient": "+222****99",
"messagePreview": "Rappel: r",
"provider": "national",
"parts": 1,
"createdAt": "2026-04-13T09:40:00.000Z",
"errorCode": "TIMEOUT",
"errorMessage": "Gateway timeout",
"messageId": null
}
],
"otp": [
{
"kind": "otp",
"endpoint": "send-otp",
"status": "verified",
"recipient": "+222****45",
"messagePreview": "123***",
"provider": "national",
"channel": "sms",
"parts": 1,
"createdAt": "2026-04-13T10:05:00.000Z",
"verifiedAt": "2026-04-13T10:06:30.000Z",
"errorCode": null,
"errorMessage": null,
"messageId": "otp-static-001"
},
{
"kind": "otp",
"endpoint": "send-otp",
"status": "sent",
"recipient": "+222****88",
"messagePreview": "456***",
"provider": "national",
"channel": "whatsapp",
"parts": 1,
"createdAt": "2026-04-13T08:00:00.000Z",
"verifiedAt": null,
"errorCode": null,
"errorMessage": null,
"messageId": "otp-static-002"
}
]
}
}
}
Note : en production, successRate sur un bloc sans trafic peut être le nombre 0 ou la chaîne "0.00" selon le calcul ; prévoir les deux formes côté client si vous affichez sans normaliser.
Erreurs
| Code | Condition | Exemple de corps |
|---|---|---|
| 400 | month mal formé | { "success": false, "message": "Paramètre month invalide. ..." } |
| 401 | Clé absente | { "message": "API key is missing" } |
| 403 | Clé invalide | { "success": false, "message": "Invalid API key" } (selon middleware) |
| 403 | App non résolue | { "success": false, "message": "Application invalide" } |
| 405 | Méthode ≠ GET | { "success": false, "message": "Method not allowed" } |
| 500 | Erreur serveur | { "success": false, "message": "Échec de la récupération des statistiques" } |
Exemples cURL
Production — mois en cours (par défaut)
curl -sS -H "api-key: VOTRE_CLE_API" \
"https://votre-domaine.com/api/app-stats"
Production — mois précis et limite de journaux
curl -sS -H "api-key: VOTRE_CLE_API" \
"https://votre-domaine.com/api/app-stats?month=2026-03&logsLimit=100"
Production — plage de dates
curl -sS -H "api-key: VOTRE_CLE_API" \
"https://votre-domaine.com/api/app-stats?from=2026-01-01&to=2026-01-31"
Local (alias possible)
curl -sS -H "api-key: VOTRE_CLE_API" \
"http://localhost:3000/api/app-stats"
curl -sS -H "api-key: VOTRE_CLE_API" \
"http://localhost:3000/api/apps-stats"
Sécurité
- La clé API donne accès aux statistiques et aux aperçus de journaux de l'app : à traiter comme un secret (HTTPS obligatoire en production).
- Ne pas exposer la clé dans le code front-end public ; appeler l'API depuis un backend ou un job sécurisé.
Environnement local
Il faut un .env avec notamment MONGODB_URI et, si le cache KV est utilisé par le projet, KV_REST_API_URL et KV_REST_API_TOKEN (sinon la résolution par clé peut fonctionner sans cache selon la configuration du dépôt).