Référence API · v1
Référence API
Tous les endpoints de l’API REST mysms : paramètres, exemples dans quatre langages, réponses et codes de retour. Une seule URL de base, du JSON partout.
Aperçu Version v1
Aperçu
L’API mysms est une API REST : des URL orientées ressources, des corps JSON (UTF-8), les codes HTTP standards et une authentification par clé. Les numéros sont au format international E.164 (+212…) et les montants en dirhams hors taxes.
Cette référence est un aperçu : les endpoints peuvent encore évoluer avant l’ouverture publique. Les changements incompatibles passeront par une nouvelle version (/v2), jamais en silence.
Environnements
sk_live_…production- Envoie de vrais SMS et débite votre solde.
sk_test_…test- Mêmes requêtes et mêmes réponses, sans envoi ni débit.
https://api.mysms.ma/v1
Authorization: Bearer sk_live_••••••••Content-Type: application/jsonAccept: application/jsonIdempotency-Key: 4f1c2a9e-… # facultatif, POST uniquement
Authentification
Chaque requête porte votre clé API dans l’en-tête Authorization: Bearer. Les clés se créent dans le tableau de bord (Développeurs › Clés API) et peuvent être révoquées à tout moment.
Types de clés
sk_live_secrète- Accès complet en production. À garder côté serveur uniquement.
sk_test_secrète- Accès complet en mode test : aucun envoi réel.
rk_live_lecture seule- Statuts, statistiques et solde uniquement. Idéale pour les rapports et les assistants IA.
Ne mettez jamais une clé secrète dans une application mobile ou du JavaScript côté navigateur.
curl https://api.mysms.ma/v1/balance \ -H "Authorization: Bearer sk_test_••••••••"
{ "error": { "type": "authentication_error", "code": "invalid_api_key", "message": "Clé API invalide ou révoquée." }}
Idempotence
Un timeout réseau ne dit pas si le SMS est parti. Ajoutez un en-tête Idempotency-Key (un UUID ou l’identifiant de votre commande) à vos POST : si vous relancez la même requête, nous renvoyons la réponse d’origine au lieu d’envoyer un second message.
- Les clés sont conservées 24 heures.
- La même clé avec un corps différent renvoie
409. - Une réponse rejouée porte l’en-tête
Idempotent-Replayed: true. - Les SDK génèrent une clé automatiquement à chaque nouvel envoi et la réutilisent pour leurs relances.
# Premier appel : le message est créécurl https://api.mysms.ma/v1/messages -X POST \ -H "Authorization: Bearer $MYSMS_KEY" \ -H "Idempotency-Key: 4f1c2a9e-order-20481" \ -d '{ "to": "+212612345678", "from": "MyShop", "body": "…" }'# Même clé après un timeout : aucune double facturation# → 201, même id msg_9fK2, en-tête Idempotent-Replayed: true
Pagination
Les endpoints de liste sont paginés par curseur, ce qui reste stable même si de nouveaux messages arrivent pendant le parcours.
Paramètres
limitinteger- De 1 à 100 éléments par page (20 par défaut).
starting_afterstring- Renvoie les éléments qui suivent cet identifiant.
Réponse
dataarray- Les éléments de la page.
has_moreboolean- Indique s’il reste des pages.
next_cursorstring- Valeur à passer dans starting_after pour la page suivante.
// Parcourir toutes les pages (Node.js)let cursor;do { const page = await sms.messages.list({ limit: 100, starting_after: cursor }); page.data.forEach((m) => console.log(m.id, m.status)); cursor = page.has_more ? page.next_cursor : null;} while (cursor);// Les SDK exposent aussi un itérateur automatique :for await (const m of sms.messages.list({ status: 'failed' })) { /* … */ }
Erreurs
Une erreur renvoie un code HTTP standard et un objet error homogène. Basez votre logique sur code, stable, plutôt que sur message, rédigé pour les humains.
Codes HTTP
- 200 / 201Succès
- 400JSON mal formé
- 401Clé absente, invalide ou révoquée
- 402Solde insuffisant
- 403La clé n’a pas ce droit (lecture seule)
- 404Ressource introuvable
- 409Conflit (clé d’idempotence réutilisée avec un autre corps)
- 422Paramètre invalide
- 429Limite de débit atteinte
- 5xxErreur de notre côté, réessayez avec un délai
Les réponses 429 portent un en-tête Retry-After (en secondes). Toutes les réponses portent X-RateLimit-Limit et X-RateLimit-Remaining. La liste complète des codes d’erreur est dans la documentation.
{ "error": { "type": "invalid_request", "code": "invalid_phone_number", "message": "Le numéro +21261234 n’est pas un numéro valide.", "param": "to", "request_id": "req_Q2w9" }}
Messages
Envoyer des SMS et suivre leur livraison.
POST/v1/messages
Envoyer un SMS
Crée un message et le place en file d’envoi. La réponse est immédiate ; le statut évolue ensuite (queued → sent → delivered ou failed) et chaque changement est notifié à votre webhook.
Corps de la requête
tostringobligatoire- Numéro du destinataire au format E.164, par exemple +212612345678.
fromstringobligatoire- Sender ID validé sur votre compte (11 caractères alphanumériques au plus).
bodystringobligatoire- Texte du message. L’encodage (GSM-7 ou UCS-2) et le nombre de segments sont calculés automatiquement.
webhookstring- URL HTTPS qui recevra les événements de ce message. Remplace le webhook par défaut du compte.
scheduled_atdatetime- Date d’envoi différé (ISO 8601). Jusqu’à 30 jours à l’avance.
metadataobject- Jusqu’à 20 paires clé-valeur, renvoyées telles quelles dans la réponse et les webhooks.
Codes de retour
- 201Message accepté et placé en file d’envoi.
- 401Clé API absente ou invalide.
- 402Solde insuffisant pour envoyer ce message.
- 422Paramètre invalide (numéro, Sender ID non validé…).
- 429Limite de débit atteinte, réessayez après Retry-After.
curl https://api.mysms.ma/v1/messages -X POST \ -H "Authorization: Bearer $MYSMS_KEY" \ -H "Idempotency-Key: 4f1c2a9e-order-20481" \ -H "Content-Type: application/json" \ -d '{ "to": "+212612345678", "from": "MyShop", "body": "Commande #20481 confirmée.", "metadata": { "order_id": "20481" } }'
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const message = await sms.messages.send({ to: '+212612345678', from: 'MyShop', body: 'Commande #20481 confirmée.', metadata: { order_id: '20481' },}, { idempotencyKey: '4f1c2a9e-order-20481',});
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$message = $sms->messages->send([ 'to' => '+212612345678', 'from' => 'MyShop', 'body' => 'Commande #20481 confirmée.', 'metadata' => ['order_id' => '20481'],], idempotencyKey: '4f1c2a9e-order-20481');
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])message = client.messages.send( to="+212612345678", sender="MyShop", body="Commande #20481 confirmée.", metadata={"order_id": "20481"}, idempotency_key="4f1c2a9e-order-20481",)
{ "id": "msg_9fK2", "object": "message", "to": "+212612345678", "from": "MyShop", "body": "Commande #20481 confirmée.", "status": "queued", "segments": 1, "encoding": "gsm7", "price": 0.50, "currency": "MAD", "metadata": { "order_id": "20481" }, "created_at": "2026-10-21T09:41:02+01:00"}
{ "error": { "type": "invalid_request", "code": "invalid_phone_number", "message": "Le numéro +21261234 n’est pas un numéro valide.", "param": "to" }}
GET/v1/messages/{id}
Statut d’un message
Renvoie un message et son dernier statut connu, avec l’opérateur de destination et les horodatages d’envoi et de livraison.
Paramètres de chemin
idstringobligatoire- Identifiant du message, renvoyé à la création (msg_…).
Codes de retour
- 200Le message existe et appartient à votre compte.
- 404Identifiant inconnu (ou message d’un autre compte).
curl https://api.mysms.ma/v1/messages/msg_9fK2 \ -H "Authorization: Bearer $MYSMS_KEY"
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const message = await sms.messages.retrieve('msg_9fK2');
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$message = $sms->messages->retrieve('msg_9fK2');
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])message = client.messages.retrieve("msg_9fK2")
{ "id": "msg_9fK2", "object": "message", "to": "+212612345678", "status": "delivered", "operator": "maroc_telecom", "segments": 1, "price": 0.50, "sent_at": "2026-10-21T09:41:02+01:00", "delivered_at": "2026-10-21T09:41:03+01:00"}
{ "error": { "type": "not_found", "code": "resource_missing", "message": "Aucun message msg_0000 sur ce compte." }}
GET/v1/messages
Lister les messages
Liste vos messages, du plus récent au plus ancien, avec filtres par statut et par date. Paginé par curseur.
Paramètres de requête
statusstring- queued, sent, delivered, failed ou expired.
created_afterdatetime- Ne renvoie que les messages créés après cette date.
limitinteger- Nombre d’éléments par page, de 1 à 100 (20 par défaut).
starting_afterstring- Curseur : l’identifiant du dernier élément de la page précédente.
Codes de retour
- 200Une page de résultats (éventuellement vide).
curl "https://api.mysms.ma/v1/messages?status=failed&limit=20" \ -H "Authorization: Bearer $MYSMS_KEY"
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const page = await sms.messages.list({ status: 'failed', limit: 20,});
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$page = $sms->messages->list([ 'status' => 'failed', 'limit' => 20,]);
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])page = client.messages.list( status="failed", limit=20,)
{ "object": "list", "data": [ { "id": "msg_8aB1", "status": "failed", "error_code": "unreachable" }, { "id": "msg_7cD4", "status": "failed", "error_code": "invalid_number" } ], "has_more": true, "next_cursor": "msg_7cD4"}
Solde
Le crédit prépayé de votre compte.
GET/v1/balance
Lire le solde
Renvoie le crédit prépayé disponible, en dirhams hors taxes. Le crédit n’expire pas. Les recharges se font par virement bancaire depuis le tableau de bord (paiement par carte bientôt disponible).
Codes de retour
- 200Solde disponible et montant réservé par les envois programmés.
curl https://api.mysms.ma/v1/balance \ -H "Authorization: Bearer $MYSMS_KEY"
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const balance = await sms.balance.retrieve();
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$balance = $sms->balance->retrieve();
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])balance = client.balance.retrieve()
{ "object": "balance", "available": 1250.40, "reserved": 107.00, "currency": "MAD"}
Verify
Codes OTP générés, envoyés et vérifiés pour vous.
POST/v1/verify
Envoyer un code
Génère un code à usage unique, l’envoie par SMS et gère son expiration. Vous ne stockez jamais le code.
Corps de la requête
tostringobligatoire- Numéro à vérifier, au format E.164.
senderstring- Sender ID à utiliser. Par défaut, celui du compte.
langstring- Langue du message : fr, ar ou en (fr par défaut).
lengthinteger- Nombre de chiffres, de 4 à 8 (6 par défaut).
ttlinteger- Durée de validité en secondes, de 60 à 900 (300 par défaut).
Codes de retour
- 201Code généré et envoyé.
- 422Numéro invalide ou paramètre hors limites.
- 429Trop de demandes pour ce numéro, réessayez plus tard.
curl https://api.mysms.ma/v1/verify -X POST \ -H "Authorization: Bearer $MYSMS_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+212612345678", "sender": "WalletMA", "lang": "fr" }'
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const verification = await sms.verify.start({ to: '+212612345678', sender: 'WalletMA', lang: 'fr',});
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$verification = $sms->verify->start([ 'to' => '+212612345678', 'sender' => 'WalletMA', 'lang' => 'fr',]);
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])verification = client.verify.start( to="+212612345678", sender="WalletMA", lang="fr",)
{ "id": "vrf_3Hq8", "object": "verification", "to": "+212612345678", "status": "pending", "expires_at": "2026-10-21T09:46:02+01:00"}
POST/v1/verify/check
Vérifier un code
Compare le code saisi par l’utilisateur. Après cinq essais incorrects, la vérification est bloquée.
Corps de la requête
idstringobligatoire- Identifiant renvoyé par POST /v1/verify (vrf_…).
codestringobligatoire- Code saisi par l’utilisateur.
Codes de retour
- 200Résultat de la vérification (valid true ou false).
- 410Vérification expirée ou bloquée après trop d’essais.
curl https://api.mysms.ma/v1/verify/check -X POST \ -H "Authorization: Bearer $MYSMS_KEY" \ -H "Content-Type: application/json" \ -d '{ "id": "vrf_3Hq8", "code": "731044" }'
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const result = await sms.verify.check({ id: 'vrf_3Hq8', code: '731044',});
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$result = $sms->verify->check([ 'id' => 'vrf_3Hq8', 'code' => '731044',]);
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])result = client.verify.check( id="vrf_3Hq8", code="731044",)
{ "id": "vrf_3Hq8", "status": "approved", "valid": true}
{ "error": { "type": "invalid_request", "code": "verification_expired", "message": "Ce code a expiré. Demandez-en un nouveau." }}
Webhooks
Recevez chaque changement de statut sur votre URL, signé.
POST/v1/webhooks
Créer un webhook
Enregistre une URL qui recevra les événements de votre compte. Le secret de signature n’est renvoyé qu’une fois, à la création.
Corps de la requête
urlstringobligatoire- URL HTTPS publique de votre application.
eventsstring[]obligatoire- Événements à recevoir, par exemple message.delivered.
descriptionstring- Libellé libre, affiché dans le tableau de bord.
Codes de retour
- 201Webhook créé. Conservez le secret, il ne sera plus affiché.
- 422URL non HTTPS ou événement inconnu.
curl https://api.mysms.ma/v1/webhooks -X POST \ -H "Authorization: Bearer $MYSMS_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://myshop.ma/hooks/sms", "events": [ "message.delivered", "message.failed" ] }'
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const webhook = await sms.webhooks.create({ url: 'https://myshop.ma/hooks/sms', events: ['message.delivered', 'message.failed'],});
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$webhook = $sms->webhooks->create([ 'url' => 'https://myshop.ma/hooks/sms', 'events' => ['message.delivered', 'message.failed'],]);
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])webhook = client.webhooks.create( url="https://myshop.ma/hooks/sms", events=["message.delivered", "message.failed"],)
{ "id": "wh_5Lm2", "object": "webhook", "url": "https://myshop.ma/hooks/sms", "events": ["message.delivered", "message.failed"], "secret": "whsec_••••••••••••"}
Événements
Nous envoyons un POST JSON à votre URL. Répondez par un code 2xx en moins de 10 secondes ; sinon nous relançons avec un délai croissant (1 min, 5 min, 30 min, 2 h…) pendant 24 heures.
Types d’événements
message.queued- Le message est accepté et en file d’envoi.
message.sent- Le message a été remis au réseau de l’opérateur.
message.delivered- L’opérateur confirme la livraison sur le téléphone.
message.failed- Échec définitif, avec un error_code.
message.inbound- Une réponse SMS est arrivée sur votre numéro.
verify.approved- Un code Verify a été validé.
sender.updated- Le statut d’un Sender ID a changé.
balance.low- Votre solde passe sous le seuil d’alerte.
{ "id": "evt_2Rt6", "type": "message.delivered", "created_at": "2026-10-21T09:41:03+01:00", "data": { "id": "msg_9fK2", "status": "delivered", "operator": "maroc_telecom", "metadata": { "order_id": "20481" } }}
Signature
Chaque requête porte un en-tête MySms-Signature : un horodatage t et une signature v1, HMAC-SHA256 de t.corps avec le secret du webhook.
- Calculez la signature sur le corps brut, avant tout décodage JSON.
- Comparez en temps constant et rejetez les horodatages de plus de 5 minutes (protection contre le rejeu).
- Un même événement peut arriver deux fois : dédoublonnez sur son
id.
import crypto from 'node:crypto';// En-tête : MySms-Signature: t=1792569600,v1=5f2c…export function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('='))); const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));}
// En-tête : MySms-Signature: t=1792569600,v1=5f2c…$payload = $request->getContent();[$t, $v1] = sscanf($request->header('MySms-Signature'), 't=%d,v1=%s');$expected = hash_hmac('sha256', "{$t}.{$payload}", $webhookSecret);if (! hash_equals($expected, $v1) || abs(time() - $t) > 300) { abort(401);}
import hmac, hashlib, time# En-tête : MySms-Signature: t=1792569600,v1=5f2c…def verify(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) signed = f"{parts['t']}.".encode() + raw_body expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() fresh = abs(time.time() - int(parts["t"])) < 300 return fresh and hmac.compare_digest(expected, parts["v1"])
Sender ID
Le nom de votre marque comme expéditeur.
GET/v1/senders
Lister les Sender ID
Liste les noms d’expéditeur de votre compte et leur statut de validation auprès des opérateurs.
Codes de retour
- 200Sender ID du compte : approved, pending ou rejected.
curl https://api.mysms.ma/v1/senders \ -H "Authorization: Bearer $MYSMS_KEY"
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const senders = await sms.senders.list();
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$senders = $sms->senders->list();
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])senders = client.senders.list()
{ "object": "list", "data": [ { "id": "snd_1aZ9", "name": "MyShop", "status": "approved" }, { "id": "snd_2bY8", "name": "MyShopPromo", "status": "pending" } ], "has_more": false}
POST/v1/senders
Demander un Sender ID
Soumet un nouveau nom d’expéditeur pour validation. Le statut passe à approved une fois accepté ; un événement sender.updated est envoyé.
Corps de la requête
namestringobligatoire- 3 à 11 caractères alphanumériques, sans espace, proches de votre marque.
use_casestringobligatoire- transactional, otp ou marketing.
samplestring- Exemple de message, qui accélère la validation.
Codes de retour
- 201Demande enregistrée, en attente de validation.
- 409Ce nom existe déjà sur votre compte.
curl https://api.mysms.ma/v1/senders -X POST \ -H "Authorization: Bearer $MYSMS_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "MyShop", "use_case": "transactional", "sample": "Commande #20481 confirmée." }'
import MySms from '@mysms/node';const sms = new MySms(process.env.MYSMS_KEY);const sender = await sms.senders.create({ name: 'MyShop', use_case: 'transactional', sample: 'Commande #20481 confirmée.',});
use MySms\Client;$sms = new Client(env('MYSMS_KEY'));$sender = $sms->senders->create([ 'name' => 'MyShop', 'use_case' => 'transactional', 'sample' => 'Commande #20481 confirmée.',]);
import osfrom mysms import Clientclient = Client(os.environ["MYSMS_KEY"])sender = client.senders.create( name="MyShop", use_case="transactional", sample="Commande #20481 confirmée.",)
{ "id": "snd_3cX7", "object": "sender", "name": "MyShop", "status": "pending"}
Tarifs
Payez uniquement ce que vous envoyez
Crédit prépayé sans expiration, rechargé par virement bancaire (paiement par carte bientôt disponible). Les clés de test sont gratuites.
Commencez à envoyer en 5 minutes
Créez un compte gratuit, recevez 100 messages de test et constatez la rapidité de la livraison locale. Sans carte bancaire.