Introduction
L'API AdaSMS.ma envoie des SMS unitaires ou en masse vers les numéros marocains, permet de suivre chaque message, de partager des documents par lien sécurisé (AdaLink) et de consulter votre solde. Vos systèmes peuvent aussi être prévenus en temps réel par webhook.
URL de base
https://api.adasms.ma/v1Toutes les requêtes passent en HTTPS. Les corps de requête et de réponse sont en JSON (sauf l'envoi de fichiers AdaLink, en multipart/form-data).
Format des réponses
Chaque réponse contient success (true ou false). En cas d'erreur, message explique le problème et, pour les erreurs à traiter dans votre code, code donne une valeur stable (voir Codes d'erreur).
message) sont rédigés en français. Dans votre code, fiez-vous au statut HTTP et au champ code, jamais au texte du message.Authentification
Chaque requête porte votre clé API dans l'en-tête X-API-Key. Créez vos clés dans votre espace client, menu Clés API : une clé par application, que vous pouvez désactiver à tout moment.
X-API-Key: adasms_VOTRE_CLEUne clé se compose de adasms_ suivi de 48 caractères hexadécimaux.
Erreurs d'authentification
| Statut HTTP | Code | Cause |
|---|---|---|
| 401 | — | En-tête X-API-Key absent |
| 401 | — | Clé inconnue, désactivée ou mal formée |
| 403 | — | Compte suspendu |
| 403 | SENDER_EXPIRED | Clé liée à un Sender ID expiré ou inactif |
| 429 | RATE_LIMITED | Plus de 20 clés mal formées depuis la même adresse IP en 10 minutes |
Numéros de téléphone
AdaSMS.ma envoie uniquement vers les numéros marocains. Trois formats sont acceptés, partout (envoi unitaire, envoi en masse, AdaLink) ; espaces, tirets, points et parenthèses sont ignorés. Les numéros sont enregistrés et renvoyés au format +212XXXXXXXXX.
| Format | Exemple | Enregistré comme |
|---|---|---|
+212XXXXXXXXX | +212600000001 | +212600000001 |
212XXXXXXXXX | 212600000001 | +212600000001 |
0XXXXXXXXX | 06 00 00 00 01 | +212600000001 |
Un numéro illisible renvoie une erreur 400. Un numéro étranger (par exemple +33…) renvoie une erreur 400 avec le code NON_MOROCCAN_NUMBER, sans aucun débit ; dans un envoi en masse, toute la requête est alors refusée.
Segments et encodage
Un SMS est facturé par segment. Le nombre de segments dépend de la longueur du texte et de son encodage, détecté automatiquement :
| Encodage | 1 segment | Plusieurs segments |
|---|---|---|
| GSM-7 (alphabet standard) | 160 caractères | 153 caractères par segment |
| Unicode / UCS-2 (arabe, emojis, certains accents) | 70 caractères | 67 caractères par segment |
Un texte est limité à 10 segments (1 530 caractères GSM-7 ou 670 en Unicode) ; au-delà, l'API répond 400 TEXT_TOO_LONG. En GSM-7, les caractères ^ { } \ [ ] ~ | € comptent double.
ê â î ô û ë ï ç. Avec gsm_replace: true, ces caractères sont remplacés par leur équivalent GSM-7 avant l'envoi.Limites de débit
POST /messages, POST /bulk et GET /account sont limités par fenêtre de 60 secondes :
| Périmètre | Limite par fenêtre de 60 s |
|---|---|
| Par clé API | 600 requêtes |
| Par adresse IP | 1 200 requêtes |
Au-delà, l'API répond 429 avec le code RATE_LIMITED et l'en-tête Retry-After (délai en secondes). Pour envoyer beaucoup de messages, préférez POST /bulk (jusqu'à 1 000 destinataires par requête). Besoin d'une limite plus élevée ? Écrivez à support@adasms.ma.
Référence de l'API
Envoyer un SMS
/v1/messagesEnvoie un SMS à un numéro marocain. Le coût est débité au moment de l'envoi (prix unitaire de votre plan × nombre de segments). Si l'envoi échoue chez l'opérateur, le montant est recrédité sur votre solde.
Soumis à la limite de débit. Voir ›
Corps de la requête
Corps en JSON.
| Champ | Type | Requis | Description |
|---|---|---|---|
to | string | Oui | Numéro marocain : +212XXXXXXXXX, 212XXXXXXXXX ou 0XXXXXXXXX (espaces, tirets, points et parenthèses ignorés). Ramené au format +212XXXXXXXXX. |
text | string | Oui | Texte du SMS, 10 segments au maximum (1 530 caractères GSM-7 ou 670 en Unicode). Voir « Segments et encodage ». |
from | string | — | Votre Sender ID (nom d'expéditeur) validé. Obligatoire dès que vous avez au moins un Sender ID validé. Sans Sender ID validé, laissez-le vide : l'expéditeur partagé est utilisé pour un nombre limité d'envois de découverte. Si votre clé API est associée à un Sender ID, celui-ci est toujours utilisé. |
type | string | — | Étiquette libre (ex. otp, alerte, promo), reprise dans le message et les webhooks. Par défaut : transactional |
reference | string | — | Votre référence libre (ASCII imprimable, 64 caractères maximum), pour retrouver vos messages avec GET /messages?reference=…. |
gsm_replace | boolean | — | Remplace avant l'envoi les caractères Unicode courants par leur équivalent GSM-7 (ê→e, ç→c, «→", —→-, …→...). Le texte enregistré est le texte remplacé. Par défaut : false |
Requête
curl -X POST "https://api.adasms.ma/v1/messages" \
-H "X-API-Key: adasms_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"to": "+212600000001",
"text": "Votre code de vérification : 847291",
"from": "MonService",
"type": "otp",
"reference": "CMD-2026-001"
}'Réponse 202
Envoi accepté.
{
"success": true,
"messageId": "3f6c2a91-5b7e-4d0a-9c1e-2b8f4e6d7a10",
"status": "sent",
"providerMessageId": "9a8b7c6d-0000-4e1f-8a2b-3c4d5e6f7a8b",
"cost": "0.4900",
"channel": "sms",
"downgraded": false,
"segments": 1,
"encoding": "GSM7",
"reference": "CMD-2026-001"
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
messageId | string | |
status | string | sent = transmis à l'opérateur ; queued = en attente de transmission. Valeurs : queued, sent |
providerMessageId | string | Identifiant attribué par l'opérateur. |
cost | string | Montant débité en MAD HT (prix d'un segment × segments), 4 décimales. |
provider | string | Déconseillé Champ technique conservé pour compatibilité ; sa valeur peut changer sans préavis. Ne l'utilisez pas. |
channel | string | Valeurs : sms |
downgraded | boolean | true si le message est parti en SMS alors qu'une règle de votre compte prévoyait un autre canal. |
segments | integer | |
encoding | string | Valeurs : GSM7, UCS2 |
reference | string | null |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 400 | — | to ou text absent |
| 400 | — | Numéro illisible (formats acceptés, voir « Numéros de téléphone ») |
| 400 | NON_MOROCCAN_NUMBER | Numéro hors Maroc |
| 400 | — | reference invalide (ASCII imprimable, 64 caractères maximum) |
| 400 | TEXT_TOO_LONG | Texte de plus de 10 segments |
| 400 | DEFAULT_SENDER_FORBIDDEN | from absent alors que vous avez un Sender ID validé |
| 400 | DEFAULT_SENDER_QUOTA_EXCEEDED | Envois de découverte avec l'expéditeur partagé épuisés |
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 402 | INSUFFICIENT_CREDIT | Solde insuffisant |
| 403 | EMAIL_NOT_VERIFIED | Adresse e-mail du compte non confirmée |
| 429 | RATE_LIMITED | Limite de débit atteinte |
| 502 | — | Échec de l'envoi |
Rechercher par référence
/v1/messagesRetourne vos messages dont le champ reference est exactement la valeur fournie : 100 au maximum, du plus récent au plus ancien.
Paramètres de requête
| Champ | Type | Requis | Description |
|---|---|---|---|
reference | string | Oui | Référence fournie à l'envoi. |
Requête
curl "https://api.adasms.ma/v1/messages?reference=CMD-2026-001" \
-H "X-API-Key: adasms_VOTRE_CLE"Réponse 200
Messages trouvés (liste éventuellement vide).
{
"success": true,
"messages": [
{
"id": "3f6c2a91-5b7e-4d0a-9c1e-2b8f4e6d7a10",
"to": "+212600000001",
"from": "MonService",
"type": "otp",
"channel": "sms",
"status": "sent",
"text": "Votre code de vérification : 847291",
"cost": "0.49",
"billingOutcome": "charged",
"segments": 1,
"encoding": "GSM7",
"reference": "CMD-2026-001",
"providerMessageId": "9a8b7c6d-0000-4e1f-8a2b-3c4d5e6f7a8b",
"errorMessage": null,
"createdAt": "2026-11-10T09:00:00.000Z",
"updatedAt": "2026-11-10T09:00:01.200Z"
}
]
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
messages | array<object> | |
messages[].id | string | |
messages[].to | string | Numéro au format +212XXXXXXXXX. |
messages[].from | string | null | |
messages[].type | string | |
messages[].channel | string | Valeurs : sms |
messages[].status | string | queued = en attente ; sent = transmis à l'opérateur ; delivered = remis au téléphone (selon l'opérateur, pas encore disponible) ; failed = échec. Valeurs : queued, sent, delivered, failed |
messages[].text | string | |
messages[].cost | string | Montant débité en MAD HT ("0" si l'envoi a échoué). |
messages[].provider | string | null | Déconseillé Champ technique conservé pour compatibilité ; ne l'utilisez pas. |
messages[].billingOutcome | string | charged = facturé ; refunded = recrédité après un incident ; not_charged = refusé avant l'envoi, non facturé. Valeurs : charged, refunded, not_charged |
messages[].segments | integer | |
messages[].encoding | string | Valeurs : GSM7, UCS2 |
messages[].reference | string | null | |
messages[].providerMessageId | string | null | |
messages[].errorMessage | string | null | Motif de l'échec, texte libre. |
messages[].createdAt | string | |
messages[].updatedAt | string |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 400 | — | Paramètre reference absent |
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
Consulter un message
/v1/messages/{messageId}Retourne un message de votre compte.
Paramètres de chemin
| Champ | Type | Requis | Description |
|---|---|---|---|
messageId | string | Oui | Identifiant renvoyé à l'envoi (messageId). |
Requête
curl "https://api.adasms.ma/v1/messages/3f6c2a91-5b7e-4d0a-9c1e-2b8f4e6d7a10" \
-H "X-API-Key: adasms_VOTRE_CLE"Réponse 200
Le message.
{
"success": true,
"message": {
"id": "3f6c2a91-5b7e-4d0a-9c1e-2b8f4e6d7a10",
"to": "+212600000001",
"from": "MonService",
"type": "otp",
"channel": "sms",
"status": "sent",
"text": "Votre code de vérification : 847291",
"cost": "0.49",
"billingOutcome": "charged",
"segments": 1,
"encoding": "GSM7",
"reference": "CMD-2026-001",
"providerMessageId": "9a8b7c6d-0000-4e1f-8a2b-3c4d5e6f7a8b",
"errorMessage": null,
"createdAt": "2026-11-10T09:00:00.000Z",
"updatedAt": "2026-11-10T09:00:01.200Z"
}
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
message | object | |
message.id | string | |
message.to | string | Numéro au format +212XXXXXXXXX. |
message.from | string | null | |
message.type | string | |
message.channel | string | Valeurs : sms |
message.status | string | queued = en attente ; sent = transmis à l'opérateur ; delivered = remis au téléphone (selon l'opérateur, pas encore disponible) ; failed = échec. Valeurs : queued, sent, delivered, failed |
message.text | string | |
message.cost | string | Montant débité en MAD HT ("0" si l'envoi a échoué). |
message.provider | string | null | Déconseillé Champ technique conservé pour compatibilité ; ne l'utilisez pas. |
message.billingOutcome | string | charged = facturé ; refunded = recrédité après un incident ; not_charged = refusé avant l'envoi, non facturé. Valeurs : charged, refunded, not_charged |
message.segments | integer | |
message.encoding | string | Valeurs : GSM7, UCS2 |
message.reference | string | null | |
message.providerMessageId | string | null | |
message.errorMessage | string | null | Motif de l'échec, texte libre. |
message.createdAt | string | |
message.updatedAt | string |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 404 | — | Message introuvable |
Envoyer des SMS en masse
/v1/bulkEnvoie jusqu'à 1 000 SMS en une requête, avec un texte commun ou un texte par destinataire. Les numéros acceptent les mêmes formats que l'envoi unitaire. Les doublons (après mise au format +212) ne sont envoyés qu'une fois. Le coût total est vérifié puis débité avant l'envoi ; chaque message refusé par l'opérateur est recrédité. Si un seul numéro est invalide ou hors Maroc, toute la requête est refusée et rien n'est débité.
Soumis à la limite de débit. Voir ›
Corps de la requête
Corps en JSON.
| Champ | Type | Requis | Description |
|---|---|---|---|
recipients | array<string | object> | Oui | Numéros, ou objets {to, text, reference}. 1 000 au maximum. |
recipients[].to | string | Oui | Numéro marocain : +212XXXXXXXXX, 212XXXXXXXXX ou 0XXXXXXXXX (espaces, tirets, points et parenthèses ignorés). Ramené au format +212XXXXXXXXX. |
recipients[].text | string | — | Texte propre à ce destinataire (sinon le texte commun). |
recipients[].reference | string | — | Votre référence libre (ASCII imprimable, 64 caractères maximum), pour retrouver vos messages avec GET /messages?reference=…. |
text | string | — | Texte commun, obligatoire si un destinataire n'a pas son propre texte. |
from | string | — | Votre Sender ID validé (mêmes règles que POST /messages). |
type | string | — | Étiquette libre. Par défaut : bulk |
gsm_replace | boolean | — | Remplacement GSM-7 appliqué à chaque texte (voir POST /messages). Par défaut : false |
Requête
Texte commun
curl -X POST "https://api.adasms.ma/v1/bulk" \
-H "X-API-Key: adasms_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
"+212600000001",
"0600000002",
"212600000003"
],
"text": "Votre commande #4521 a été expédiée.",
"from": "MaBoutique"
}'Texte par destinataire
curl -X POST "https://api.adasms.ma/v1/bulk" \
-H "X-API-Key: adasms_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"from": "MaBoutique",
"recipients": [
{
"to": "+212600000001",
"text": "Bonjour Karim, votre solde est de 250 MAD.",
"reference": "CLI-001"
},
{
"to": "0600000002",
"text": "Bonjour Salma, votre solde est de 80 MAD.",
"reference": "CLI-002"
}
]
}'Réponse 202
Lot traité. Le détail de chaque message est disponible avec GET /bulk/{batchId}.
{
"success": true,
"batchId": "7c1d9e20-4a3b-4f6e-8d2c-1a0b9c8d7e6f",
"total": 3,
"status": "completed",
"estimatedCost": "1.4700"
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
batchId | string | |
total | integer | Nombre de destinataires après suppression des doublons. |
status | string | failed si tous les messages ont échoué. Valeurs : completed, failed |
estimatedCost | string | Montant débité avant l'envoi, en MAD HT ; les messages en échec sont recrédités. |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 400 | — | recipients absent ou vide |
| 400 | — | Plus de 1 000 destinataires |
| 400 | — | Numéro illisible ou texte absent (détail par destinataire dans errors) |
| 400 | NON_MOROCCAN_NUMBER | Numéro hors Maroc (détail par destinataire dans errors) |
| 400 | — | reference invalide (ASCII imprimable, 64 caractères maximum) |
| 400 | TEXT_TOO_LONG | Texte de plus de 10 segments |
| 400 | DEFAULT_SENDER_FORBIDDEN | from absent alors que vous avez un Sender ID validé |
| 400 | DEFAULT_SENDER_QUOTA_EXCEEDED | Envois de découverte avec l'expéditeur partagé épuisés |
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 402 | INSUFFICIENT_CREDIT | Solde insuffisant |
| 403 | EMAIL_NOT_VERIFIED | Adresse e-mail du compte non confirmée |
| 429 | RATE_LIMITED | Limite de débit atteinte |
| 502 | — | Échec de l'envoi |
Consulter un lot
/v1/bulk/{batchId}Retourne un lot de votre compte et le statut de chacun de ses messages.
Paramètres de chemin
| Champ | Type | Requis | Description |
|---|---|---|---|
batchId | string | Oui | Identifiant renvoyé à l'envoi (batchId). |
Requête
curl "https://api.adasms.ma/v1/bulk/7c1d9e20-4a3b-4f6e-8d2c-1a0b9c8d7e6f" \
-H "X-API-Key: adasms_VOTRE_CLE"Réponse 200
Le lot, ses messages et leur décompte.
{
"success": true,
"batch": {
"id": "7c1d9e20-4a3b-4f6e-8d2c-1a0b9c8d7e6f",
"clientId": "0b5e8f2a-1c3d-4e6f-9a7b-8c9d0e1f2a3b",
"type": "bulk",
"channel": "sms",
"status": "completed",
"totalMessages": 3,
"sentCount": 3,
"failedCount": 0,
"estimatedCost": "1.47",
"actualCost": "1.47",
"createdAt": "2026-11-10T09:00:00.000Z",
"updatedAt": "2026-11-10T09:00:01.200Z"
},
"messages": [
{
"id": "5e2a7b10-9c4d-4f1e-8a3b-6d7c8e9f0a1b",
"to": "+212600000001",
"status": "sent",
"providerMessageId": "9a8b7c6d-0000-4e1f-8a2b-3c4d5e6f7a8c",
"cost": "0.49",
"billingOutcome": "charged",
"errorMessage": null,
"createdAt": "2026-11-10T09:00:00.100Z"
}
],
"summary": {
"total": 3,
"sent": 3,
"failed": 0,
"pending": 0
}
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
batch | object | |
batch.id | string | |
batch.clientId | string | |
batch.type | string | |
batch.channel | string | Valeurs : sms |
batch.status | string | Valeurs : processing, completed, failed |
batch.totalMessages | integer | |
batch.sentCount | integer | |
batch.failedCount | integer | |
batch.estimatedCost | string | |
batch.actualCost | string | |
batch.createdAt | string | |
batch.updatedAt | string | |
messages | array<object> | |
messages[].id | string | |
messages[].to | string | |
messages[].status | string | Valeurs : queued, sent, delivered, failed |
messages[].providerMessageId | string | null | |
messages[].cost | string | |
messages[].billingOutcome | string | Valeurs : charged, refunded, not_charged |
messages[].errorMessage | string | null | |
messages[].createdAt | string | |
summary | object | |
summary.total | integer | |
summary.sent | integer | |
summary.failed | integer | |
summary.pending | integer | Messages encore en attente (queued). |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 404 | — | Lot introuvable |
Créer et envoyer un lien AdaLink
/v1/adalinkHéberge un document au Maroc, crée un lien sécurisé valable de 1 à 30 jours et l'envoie par SMS au destinataire. Le prix du lien (selon sa durée) est débité à la création ; le SMS qui porte le lien est facturé en plus, au prix de votre plan.
Corps de la requête
Corps en multipart/form-data.
| Champ | Type | Requis | Description |
|---|---|---|---|
file | fichier | Oui | Document à partager. PDF, JPG, PNG, WEBP, TXT ou CSV, 10 Mo au maximum. |
to | string | Oui | Numéro marocain : +212XXXXXXXXX, 212XXXXXXXXX ou 0XXXXXXXXX (espaces, tirets, points et parenthèses ignorés). Ramené au format +212XXXXXXXXX. |
durationDays | integer | Oui | Durée de validité du lien, en jours. Au-delà de 30 jours, contactez le support pour un devis. |
Requête
curl -X POST "https://api.adasms.ma/v1/adalink" \
-H "X-API-Key: adasms_VOTRE_CLE" \
-F "file=@facture_2026_11.pdf" \
-F "to=+212600000001" \
-F "durationDays=14"Réponse 201
Lien créé ; le SMS part aussitôt.
{
"success": true,
"adaLink": {
"id": "2d4f6a8c-0e1b-4c3d-8e5f-7a9b0c1d2e3f",
"token": "Xk9mT2pQrL4vN8wZ",
"url": "https://doc.adasms.ma/l/Xk9mT2pQrL4vN8wZ",
"expiresAt": "2026-11-24T09:00:00.000Z",
"recipientPhone": "+212600000001",
"channel": "sms",
"durationDays": 14,
"costMad": "14.7000",
"originalName": "facture_2026_11.pdf",
"fileSizeBytes": 182340,
"mimeType": "application/pdf",
"status": "active",
"accessCount": 0,
"createdAt": "2026-11-10T09:00:00.000Z",
"lastAccessedAt": null,
"revokedAt": null
}
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
adaLink | object | |
adaLink.id | string | |
adaLink.token | string | Partie secrète de l'URL : ne la diffusez qu'au destinataire. |
adaLink.url | string | Lien envoyé au destinataire. |
adaLink.expiresAt | string | |
adaLink.recipientPhone | string | |
adaLink.channel | string | Valeurs : sms |
adaLink.durationDays | integer | |
adaLink.costMad | string | Prix du lien en MAD HT (hors SMS). |
adaLink.originalName | string | |
adaLink.fileSizeBytes | integer | |
adaLink.mimeType | string | |
adaLink.status | string | Valeurs : active |
adaLink.accessCount | integer | |
adaLink.createdAt | string | |
adaLink.lastAccessedAt | string | null | |
adaLink.revokedAt | string | null |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 400 | — | Champ file absent |
| 400 | — | Format de fichier non accepté |
| 400 | — | Fichier de plus de 10 Mo |
| 400 | — | Numéro illisible |
| 400 | NON_MOROCCAN_NUMBER | Numéro hors Maroc |
| 400 | — | durationDays absent ou hors de 1 à 30 |
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 402 | INSUFFICIENT_CREDIT | Solde insuffisant |
| 500 | — | Création du lien impossible |
Envoyer des liens AdaLink en masse
/v1/adalink/bulkEnvoie jusqu'à 200 documents à partir d'une archive ZIP. Chaque fichier est nommé par le numéro de son destinataire (ex. 0600000001.pdf) ; un fichier CSV facultatif (colonnes fichier, telephone, duree) permet d'indiquer un autre numéro ou une durée par document. Les fichiers non retenus (format non accepté, numéro illisible ou hors Maroc, doublon) sont ignorés et comptés dans skipped. Le prix de tous les liens retenus est débité à l'acceptation ; les liens sont ensuite créés et envoyés en arrière-plan, et un lien qui échoue est recrédité.
Corps de la requête
Corps en multipart/form-data.
| Champ | Type | Requis | Description |
|---|---|---|---|
documents | fichier | Oui | Archive ZIP, 200 fichiers au maximum, chaque fichier nommé par le numéro du destinataire (ex. 0600000001.pdf). |
mapping | fichier | — | CSV facultatif, une ligne par fichier : fichier,telephone,duree (séparateur virgule ou point-virgule, ligne d'en-tête facultative). La durée est facultative. |
durationDays | integer | — | Durée par défaut des liens, en jours. Par défaut : 7 |
Requête
curl -X POST "https://api.adasms.ma/v1/adalink/bulk" \
-H "X-API-Key: adasms_VOTRE_CLE" \
-F "documents=@factures_novembre.zip" \
-F "durationDays=30"Réponse 202
Lot accepté ; suivez-le avec GET /adalink/bulk/{batchId}.
{
"success": true,
"batchId": "6b8d0f2a-4c5e-4a7b-9d1e-3f5a7b9c1d2e",
"totalFiles": 45,
"retained": 44,
"skipped": 1,
"warnings": [],
"estimatedCost": "858.0000"
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
batchId | string | |
totalFiles | integer | |
retained | integer | Fichiers retenus et facturés. |
skipped | integer | Fichiers ignorés (format, numéro illisible ou hors Maroc, doublon), non facturés. |
warnings | array<string> | |
estimatedCost | string | Montant débité en MAD HT (liens seulement, hors SMS). |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 400 | — | Champ documents absent |
| 400 | — | Le fichier n'est pas une archive ZIP |
| 400 | — | durationDays hors de 1 à 30 |
| 400 | — | Aucun fichier retenu |
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 402 | INSUFFICIENT_CREDIT | Solde insuffisant |
| 403 | — | Compte suspendu |
| 500 | — | Création du lien impossible |
Consulter un lot AdaLink
/v1/adalink/bulk/{batchId}Retourne un lot AdaLink de votre compte et le statut de chaque document retenu.
Paramètres de chemin
| Champ | Type | Requis | Description |
|---|---|---|---|
batchId | string | Oui | Identifiant renvoyé à l'envoi (batchId). |
Requête
curl "https://api.adasms.ma/v1/adalink/bulk/6b8d0f2a-4c5e-4a7b-9d1e-3f5a7b9c1d2e" \
-H "X-API-Key: adasms_VOTRE_CLE"Réponse 200
Le lot et ses documents.
{
"success": true,
"batch": {
"id": "6b8d0f2a-4c5e-4a7b-9d1e-3f5a7b9c1d2e",
"clientId": "0b5e8f2a-1c3d-4e6f-9a7b-8c9d0e1f2a3b",
"channel": "sms",
"durationDays": 30,
"totalFiles": 45,
"successCount": 44,
"failedCount": 0,
"skippedCount": 1,
"status": "completed",
"createdAt": "2026-11-10T09:00:00.000Z",
"completedAt": "2026-11-10T09:02:10.000Z"
},
"items": [
{
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"fileName": "0600000001.pdf",
"phone": "+212600000001",
"durationDays": 30,
"status": "sent",
"errorReason": null,
"adaLinkId": "2d4f6a8c-0e1b-4c3d-8e5f-7a9b0c1d2e3f"
}
]
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
batch | object | |
batch.id | string | |
batch.clientId | string | |
batch.channel | string | Valeurs : sms |
batch.durationDays | integer | |
batch.totalFiles | integer | |
batch.successCount | integer | |
batch.failedCount | integer | |
batch.skippedCount | integer | |
batch.status | string | Valeurs : pending, processing, completed, failed |
batch.createdAt | string | |
batch.completedAt | string | null | |
items | array<object> | |
items[].id | string | |
items[].fileName | string | |
items[].phone | string | |
items[].durationDays | integer | |
items[].status | string | Valeurs : pending, sent, failed |
items[].errorReason | string | null | |
items[].adaLinkId | string | null |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 404 | — | Lot introuvable |
Solde du compte
/v1/accountRetourne le solde, le plan, le mode de facturation et le prix d'un segment SMS de votre compte.
Soumis à la limite de débit. Voir ›
Requête
curl "https://api.adasms.ma/v1/account" \
-H "X-API-Key: adasms_VOTRE_CLE"Réponse 200
Informations du compte.
{
"success": true,
"account": {
"name": "Ma Société",
"plan": "starter",
"billingMode": "prepaid",
"balance": "549.8100",
"currency": "MAD",
"smsUnitPrice": "0.4900",
"estimatedSmsRemaining": 1122
}
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
account | object | |
account.name | string | |
account.plan | string | Code du plan (starter, business, enterprise). |
account.billingMode | string | Valeurs : prepaid, postpaid |
account.balance | string | Solde en MAD HT, 4 décimales, identique au tableau de bord. |
account.currency | string | Valeurs : MAD |
account.smsUnitPrice | string | Prix d'un segment SMS de votre plan, en MAD HT. |
account.estimatedSmsRemaining | integer | null | Nombre de SMS d'un segment GSM-7 que couvre le solde (partie entière de balance ÷ smsUnitPrice) ; 0 si le solde est nul ou négatif ; null en postpayé. |
Erreurs
| Statut HTTP | Code | Cause |
|---|---|---|
| 401, 403 | — | Clé API absente ou invalide, compte suspendu, Sender ID de la clé expiré : voir Authentification. |
| 429 | RATE_LIMITED | Limite de débit atteinte |
Webhooks
Un webhook prévient votre serveur d'un événement (SMS envoyé, SMS en échec, envoi en volume ou campagne terminés) par une requête POST en JSON. Créez-le dans votre espace client, menu Webhooks : URL de réception en HTTPS et événements choisis. Le secret de signature est affiché une seule fois, à la création (ou quand vous en générez un nouveau).
En-têtes envoyés
| En-tête | Valeur |
|---|---|
Content-Type | application/json |
X-AdaSMS-Event | Nom de l'événement, par exemple message.sent |
X-AdaSMS-Timestamp | Date d'envoi de la requête (ISO 8601) |
X-AdaSMS-Signature | sha256= suivi de la signature HMAC-SHA256 du corps brut, calculée avec votre secret |
Vérifier la signature (Node.js)
const crypto = require('crypto');
const express = require('express');
const app = express();
function signatureValide(corpsBrut, secret, entete) {
const attendue = 'sha256=' + crypto.createHmac('sha256', secret).update(corpsBrut).digest('hex');
return entete.length === attendue.length
&& crypto.timingSafeEqual(Buffer.from(attendue), Buffer.from(entete));
}
// Lire le corps brut : la signature porte sur les octets reçus.
app.post('/webhooks/adasms', express.raw({ type: 'application/json' }), (req, res) => {
const entete = req.get('X-AdaSMS-Signature') || '';
if (!signatureValide(req.body, process.env.ADASMS_WEBHOOK_SECRET, entete)) {
return res.sendStatus(401);
}
const evenement = JSON.parse(req.body.toString('utf8'));
console.log(evenement.event, evenement.data);
res.sendStatus(200);
});Vérifier la signature (PHP)
<?php
$corps = file_get_contents('php://input');
$entete = $_SERVER['HTTP_X_ADASMS_SIGNATURE'] ?? '';
$attendu = 'sha256=' . hash_hmac('sha256', $corps, getenv('ADASMS_WEBHOOK_SECRET'));
if (!hash_equals($attendu, $entete)) {
http_response_code(401);
exit;
}
$evenement = json_decode($corps, true);
http_response_code(200);Vérifier la signature (Python)
import hashlib, hmac, os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post('/webhooks/adasms')
def webhook():
attendu = 'sha256=' + hmac.new(os.environ['ADASMS_WEBHOOK_SECRET'].encode(),
request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(attendu, request.headers.get('X-AdaSMS-Signature', '')):
abort(401)
evenement = request.get_json()
return '', 200Réponse et nouvelles tentatives
Répondez par un statut 2xx en moins de 10 secondes. Sinon (erreur, délai dépassé), AdaSMS.ma renvoie le même événement 1 minute puis 5 minutes plus tard : 3 tentatives au total. Traitez les événements de façon idempotente (par exemple avec messageId), un même événement pouvant arriver deux fois.
Événements
| Événement | Déclenché quand |
|---|---|
message.sent | Un SMS envoyé avec POST /messages a été transmis à l'opérateur. |
message.failed | Un SMS envoyé avec POST /messages a été refusé ; son montant a été recrédité (cost vaut "0.0000"). |
batch.completed | Un envoi en volume lancé depuis le tableau de bord (ou une campagne) est terminé. Les lots envoyés avec POST /bulk sont traités pendant la requête : ils ne déclenchent pas ce webhook, la réponse contient déjà leur résultat. |
campaign.completed | Une campagne est terminée (status completed) ou a été arrêtée en cours d'envoi (status stopped). |
campaign.failed | Une campagne programmée n'a pas pu être lancée à l'heure prévue (par exemple solde insuffisant) ; rien n'a été débité. |
message.sent — SMS transmis à l'opérateur
{
"event": "message.sent",
"timestamp": "2026-11-10T09:00:01.000Z",
"data": {
"messageId": "3f6c2a91-5b7e-4d0a-9c1e-2b8f4e6d7a10",
"to": "+212600000001",
"from": "MonService",
"type": "otp",
"channel": "sms",
"status": "sent",
"cost": "0.4900",
"segments": 1,
"encoding": "GSM7",
"reference": "CMD-2026-001",
"billingOutcome": "charged",
"providerMessageId": "9a8b7c6d-0000-4e1f-8a2b-3c4d5e6f7a8b",
"clientTxnId": null,
"networkErrorCode": null,
"errorMessage": null,
"batchId": null,
"campaignId": null
}
}message.failed — SMS en échec
{
"event": "message.failed",
"timestamp": "2026-11-10T09:00:01.000Z",
"data": {
"messageId": "3f6c2a91-5b7e-4d0a-9c1e-2b8f4e6d7a11",
"to": "+212600000002",
"from": "MonService",
"type": "transactional",
"channel": "sms",
"status": "failed",
"cost": "0.0000",
"segments": 1,
"encoding": "GSM7",
"reference": null,
"billingOutcome": "not_charged",
"providerMessageId": null,
"clientTxnId": null,
"networkErrorCode": null,
"errorMessage": "Destinataire rejeté",
"batchId": null,
"campaignId": null
}
}batch.completed — Envoi en volume terminé
{
"event": "batch.completed",
"timestamp": "2026-11-10T09:05:00.000Z",
"data": {
"batchId": "7c1d9e20-4a3b-4f6e-8d2c-1a0b9c8d7e70",
"totalMessages": 250,
"sentCount": 248,
"failedCount": 2,
"actualCost": "121.5200",
"status": "completed",
"campaignId": null
}
}campaign.completed — Campagne terminée
{
"event": "campaign.completed",
"timestamp": "2026-11-10T10:00:00.000Z",
"data": {
"campaignId": "4e6a8c0e-2b3d-4f5a-9b7c-1d3e5f7a9b0c",
"totalMessages": 500,
"sentCount": 497,
"failedCount": 3,
"actualCost": "243.5300",
"status": "completed"
}
}campaign.failed — Campagne programmée non lancée
{
"event": "campaign.failed",
"timestamp": "2026-11-10T08:00:00.000Z",
"data": {
"campaignId": "4e6a8c0e-2b3d-4f5a-9b7c-1d3e5f7a9b0d",
"name": "Promo novembre",
"scheduledAt": "2026-11-10T08:00:00.000Z",
"error": "Solde insuffisant : coût 243.5300 MAD, solde 12.0000 MAD"
}
}Codes d'erreur
Valeurs stables du champ code, à utiliser dans votre code. Les autres erreurs n'ont pas de code : fiez-vous au statut HTTP.
| Code | Statut HTTP | Description |
|---|---|---|
NON_MOROCCAN_NUMBER | 400 | Le numéro n'est pas un numéro marocain (+212 suivi de 9 chiffres). Rien n'est débité. |
TEXT_TOO_LONG | 400 | Le texte dépasse 10 segments (1 530 caractères GSM-7 ou 670 en Unicode). |
DEFAULT_SENDER_FORBIDDEN | 400 | Vous avez un Sender ID validé, l'expéditeur partagé n'est plus disponible ; indiquez from. |
DEFAULT_SENDER_QUOTA_EXCEEDED | 400 | Vos envois de découverte avec l'expéditeur partagé sont épuisés ; demandez votre Sender ID. |
INSUFFICIENT_CREDIT | 402 | Solde insuffisant. Rien n'est débité ; rechargez votre compte. |
EMAIL_NOT_VERIFIED | 403 | L'adresse e-mail du compte n'est pas confirmée ; l'envoi est bloqué jusqu'à sa confirmation. |
SENDER_EXPIRED | 403 | La clé API est liée à un Sender ID expiré ou inactif ; renouvelez-le ou changez la clé. |
RATE_LIMITED | 429 | Trop de requêtes ; attendez le délai de l'en-tête Retry-After. |
Tarifs
Crédit prépayé, sans engagement ni abonnement. Le prix d'un SMS dépend de votre plan et se compte par segment (voir Segments et encodage). Le montant est débité à l'envoi et recrédité si l'opérateur refuse le message.
| Plan | Prix par segment | Volume |
|---|---|---|
| Starter | 0,49 MAD HT | moins de 10 000 SMS/mois |
| Business | 0,35 MAD HT | 10 000 à 99 999 SMS/mois |
| Enterprise | 0,30 MAD HT | 100 000+ SMS/mois |
AdaLink
Prix par lien, selon sa durée de validité : chaque jour est facturé au tarif de sa tranche. Le SMS qui porte le lien est facturé en plus, au prix de votre plan.
| Durée de validité | Tarif HT par jour | Exemple |
|---|---|---|
| 1 à 7 jours | 1,40 MAD | 1 j = 1,40 · 7 j = 9,80 MAD |
| 8 à 14 jours | 0,70 MAD | 14 j = 14,70 MAD |
| 15 à 30 jours | 0,30 MAD | 30 j = 19,50 MAD |
| Plus de 30 jours | Sur devis | — |
GET /account renvoie le prix d'un segment de votre plan (smsUnitPrice) et votre solde.
Démarrage rapide
- Créez votre compte sur app.adasms.ma et confirmez votre adresse e-mail : l'envoi est bloqué tant qu'elle n'est pas confirmée.
- Créez une clé API dans le menu Clés API et copiez-la (elle ne sera plus affichée).
- Envoyez votre premier SMS :
curl -X POST https://api.adasms.ma/v1/messages \
-H "X-API-Key: adasms_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{"to": "0600000001", "text": "Bonjour depuis AdaSMS.ma"}'Une réponse 202 avec "success": true confirme l'envoi. Sans Sender ID validé, laissez from vide : l'expéditeur partagé est utilisé pour vos envois de découverte. Pour envoyer sous votre nom, demandez un Sender ID depuis votre espace client.
Réservé à votre espace client
Vos clés API, le secret de vos webhooks, l'historique de vos messages, vos factures et vos Sender ID se gèrent dans votre espace client, app.adasms.ma. Une question ? support@adasms.ma.
