Aller au contenu principal

Utiliser l'API

Appeler votre passerelle depuis vos propres logiciels : URL de base, clés API et portées, premier SMS avec curl, erreurs, pagination, idempotence et référence interactive.

Tout ce que fait le dashboard passe par une API REST, et cette même API est ouverte à vos propres logiciels : un CRM qui envoie des rappels de rendez-vous, un site web qui envoie un code de confirmation, un script qui lit les réponses. L'API tourne sur votre passerelle, sur votre serveur. Vos appels ne passent jamais par nous.

Cette page s'adresse au développeur qui connecte une application à la passerelle. Elle couvre ce qu'il faut pour un premier appel qui fonctionne et les règles que suivent tous les endpoints. La liste complète des endpoints, avec tous leurs champs, se trouve dans la référence interactive intégrée à votre passerelle.

URL de base

L'API répond sous /api/v1 à l'adresse de votre passerelle :

Installation de la passerelle URL de base
Avec un nom de domaine (recommandé) https://sms.exemple.com/api/v1
Sur un réseau local, sans domaine http://<machine>.local:8080/api/v1
Depuis le serveur lui-même http://127.0.0.1:8080/api/v1

Les requêtes et les réponses sont en JSON (Content-Type: application/json). Les dates sont au format ISO 8601, en UTC (2026-09-21T09:14:02Z). Les numéros de téléphone sont toujours au format international E.164 : un +, l'indicatif du pays, puis le numéro, sans espace (+261341234567).

Le corps d'une requête est plafonné à 2 Mio, bien au-delà de ce qu'exige un envoi. Au-delà, la réponse est 413 PAYLOAD_TOO_LARGE.

Authentification : les clés API

Une application s'authentifie avec une clé API, envoyée dans l'en-tête X-API-KEY de chaque appel :

curl -H "X-API-KEY: sk_..." https://sms.exemple.com/api/v1/messages?limit=1

La clé voyage toujours dans cet en-tête, jamais dans l'URL : une URL finit dans les journaux des serveurs, dans l'historique du navigateur et dans les liens partagés, et une clé écrite là doit être considérée comme divulguée. Une clé placée dans les paramètres de l'URL n'est tout simplement pas lue.

Les clés se créent sur la page Clés API du dashboard, par un superadministrateur. Chaque clé a :

Réglage Ce qu'il fait
Une portée read, send ou admin : ce que la clé a le droit de faire (voir plus bas)
Adresses IP autorisées Facultatif. Une fois renseignées, les appels venant de toute autre adresse sont refusés
Appels par minute Facultatif. Protège contre la rafale d'un script qui s'emballe
Appels par jour Facultatif. Plafonne le volume total, compté par jour UTC

Le secret s'affiche une seule fois, à la création de la clé : la passerelle n'en garde qu'une empreinte. Créer, renouveler et révoquer des clés est expliqué dans Clés API.

Les trois portées

Les portées sont ordonnées : send peut tout ce que peut read, et admin peut tout.

Portée Ce qu'elle ouvre
read Toutes les lectures : messages, conversations, contacts, groupes, appareils, campagnes, statistiques, journal d'activité
send Tout ce que permet read, plus l'envoi d'un SMS (POST /messages) et la relance d'un message en échec (POST /messages/{id}/retry)
admin Toute l'API : configuration, contacts, modèles, règles, campagnes, webhooks, exports en masse, sauvegardes, journal technique et les clés API elles-mêmes

Quelques lectures exigent aussi admin, parce qu'elles livrent beaucoup de données d'un coup ou décrivent l'intérieur de l'installation : les exports en masse (/export/..., /contacts/export), les sauvegardes (/backups/...), la liste des comptes du dashboard (/users) et le journal technique (/logs/technical). Créer, modifier ou supprimer un compte du dashboard est refusé à toute clé, même admin : ces actions demandent une personne connectée au dashboard.

Donnez à chaque application la portée la plus restreinte qui suffit. Un site web qui envoie des codes de confirmation a besoin de send, pas d'admin.

Le dashboard n'utilise pas de clé

Le dashboard se connecte avec un cookie de session, pas avec une clé. C'est pourquoi certains endpoints visibles dans la référence (connexion, mot de passe, premier compte) ne servent à rien à une intégration : ils appartiennent au dashboard.

Un premier appel : envoyer un SMS et le suivre

1. Envoyer

curl -X POST https://sms.exemple.com/api/v1/messages \
  -H "X-API-KEY: sk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: commande-10482-confirmation" \
  -d '{"to": "+261341234567", "body": "Votre commande 10482 est prête."}'

La passerelle répond 202 Accepted dès que le message est mis en file, avant qu'il ne quitte le téléphone :

{
  "accepted": [
    {
      "id": "0192f0c1-7a3e-7c55-9d1e-2b8f4a6c1d20",
      "conversationId": "0192f0c1-7a3e-7c55-9d1e-2b8f4a6c1d21",
      "direction": "out",
      "phone": "+261341234567",
      "body": "Votre commande 10482 est prête.",
      "encoding": "ucs2",
      "segments": 1,
      "status": "queued",
      "attempts": 0,
      "createdAt": "2026-09-21T09:14:02Z"
    }
  ]
}

Options d'un envoi

Tous les champs de la requête :

Champ Par défaut Valeurs admises Effet
to obligatoire Un numéro E.164, ou une liste de 1 à 500 Un message est mis en file par numéro. Au-delà de 500, utilisez une campagne, qui étale l'envoi
body obligatoire Tout texte non vide Aucune limite de longueur : un long texte est découpé en plusieurs SMS (segments). Un accent ou un emoji hors de l'alphabet GSM fait passer tout le message en ucs2, qui tient moins de caractères par SMS
deviceId aucun : la passerelle choisit L'identifiant d'un téléphone appairé Impose ce téléphone et court-circuite le choix automatique et les règles de routage par préfixe. Si ce téléphone ne peut pas envoyer, le message passe par les nouvelles tentatives habituelles, puis finit failed
simSlot aucun : la passerelle choisit 0 ou 1 Impose un emplacement de SIM sur le téléphone choisi
scheduledAt aucun : envoi immédiat Une date ISO 8601 future Le message reste en file jusqu'à cette heure. Une date passée est refusée avec SCHEDULED_AT_IN_PAST
shortenLinks false true ou false true remplace chaque adresse http:// ou https:// de body par un lien court de votre passerelle qui compte les clics, avant la mise en file. La même adresse écrite deux fois donne un seul lien, partagé par tous les destinataires de la requête ; une adresse qui est déjà l'un de vos liens courts reste telle quelle. Le body renvoyé, enregistré et envoyé porte les liens courts : sa longueur et segments changent. GET /links renvoie shortUrlLength pour calculer cette longueur à l'avance. Voir Liens courts

Sans deviceId ni simSlot, le téléphone et la SIM sont choisis selon vos réglages de routage (mode de routage, règles par préfixe, quotas des SIM) : voir Routage. Le quota journalier de chaque SIM et les nouvelles tentatives (Paramètres > Envoi) s'appliquent aux messages envoyés par l'API exactement comme à ceux envoyés depuis le dashboard. La fenêtre d'envoi et la pause de nuit, elles, ne s'appliquent pas : elles ne retiennent que les campagnes, donc un envoi par l'API part à toute heure.

Les options de texte de Paramètres > Général, appareils (Retirer les diacritiques, Supprimer les espaces superflus, Tout en majuscules) s'appliquent aussi aux envois par l'API. Elles sont appliquées au moment où le message part vers un téléphone, avec les réglages de ce téléphone : la réponse à cette requête montre body, encoding et segments tels que vous les avez envoyés, et GET /messages/{id} les montre tels qu'ils sont partis une fois le message parti. Voir Paramètres.

La référence liste aussi templateId et variables sur cette requête. La version actuelle de la passerelle ne les applique pas à un envoi par l'API : envoyez le texte final dans body. Les modèles sont disponibles depuis le dashboard et dans les campagnes.

Avec shortenLinks, rejouer une requête avec la même Idempotency-Key et le même body d'origine renvoie le message déjà mis en file, bien que son body enregistré porte désormais les liens courts : ni second lien ni second message n'est créé.

Si un destinataire est invalide, ou s'est désinscrit, toute la requête est refusée et rien n'est mis en file : vous n'avez jamais à deviner quelle partie d'une requête est passée.

2. Suivre son statut

curl -H "X-API-KEY: sk_..." \
  https://sms.exemple.com/api/v1/messages/0192f0c1-7a3e-7c55-9d1e-2b8f4a6c1d20

Le champ status suit la vie du message :

Statut Signification
queued Accepté, en attente d'un téléphone
dispatched Confié à un téléphone, en attente de sa confirmation
sent Le téléphone a confirmé l'envoi du SMS
delivered L'opérateur a confirmé la remise au destinataire
failed Abandonné après les tentatives prévues. errorCode en donne la raison (liste des codes)
pending_quota_exceeded Toutes les SIM utilisables ont atteint leur quota du jour : il part quand un quota se libère
paused Retenu, par exemple par une campagne en pause
cancelled Retiré avant de partir. errorCode en donne la raison (CAMPAIGN_CANCELLED, CONTACT_BLACKLISTED)

Un message queued qui attend le rythme de ses SIM porte aussi holdReason (sim_cadence : l'intervalle minimal entre deux SMS par SIM ; sim_rate : la limite par minute) et heldUntil, l'heure de son prochain essai. Les deux valent null sinon. Il part de lui-même, sans consommer de tentative.

delivered dépend de l'envoi d'un accusé de réception par l'opérateur : certains n'en envoient pas, et un message peut alors rester à sent alors qu'il est bien arrivé. Le fonctionnement de la file, des nouvelles tentatives et des fenêtres d'envoi est expliqué dans Envoyer des messages.

Plutôt que de redemander sans cesse, vous pouvez laisser la passerelle vous appeler quand le statut change : voir Webhooks et les événements message.sent, message.delivered et message.failed.

Idempotence : relancer sans envoyer deux fois

Le réseau coupe au mauvais moment, votre script n'a pas reçu la réponse, et il réessaie. Sans précaution, le destinataire reçoit le SMS deux fois.

Pour l'éviter, ajoutez un en-tête Idempotency-Key à POST /messages, avec une valeur de votre choix qui identifie l'opération (entre 8 et 128 caractères, par exemple commande-10482-confirmation) :

  • la même clé renvoyée dans les 24 heures avec le même corps renvoie la réponse d'origine, sans rien envoyer. Cette réponse porte l'en-tête Idempotency-Replayed: true ;
  • la même clé envoyée avec un corps différent est refusée avec 409 IDEMPOTENCY_CONFLICT : une clé désigne une opération, pas un emplacement à réutiliser ;
  • une requête qui a échoué (réponse d'erreur) ne consomme pas la clé : vous pouvez la corriger et la renvoyer avec la même clé.

Les clés sont liées à l'appelant : une autre application qui choisirait la même valeur ne reçoit jamais votre réponse. Choisissez tout de même des valeurs propres à votre application (un UUID, ou votre numéro de commande préfixé du nom de l'application), car une valeur déjà utilisée pour un autre message est refusée. L'en-tête est facultatif, mais toute intégration qui réessaie d'elle-même devrait l'envoyer.

Pagination

Toutes les listes sont paginées par curseur, jamais par numéro de page :

Paramètre Rôle
limit Éléments par page, de 1 à 200. 50 par défaut
cursor Le nextCursor de la page précédente. À omettre pour la première page

La réponse contient la liste dans data, et le curseur de la page suivante dans nextCursor, qui vaut null sur la dernière page :

curl -H "X-API-KEY: sk_..." "https://sms.exemple.com/api/v1/messages?limit=100&direction=in"
{ "data": [ ... ], "nextCursor": "0192f0c1-6b2d-7a10-8c3e-5d4f3a2b1c09" }

Renvoyez le curseur tel quel, sans chercher à l'interpréter. L'historique des messages arrive du plus récent au plus ancien. Comme le curseur désigne le dernier élément reçu et non un numéro de page, les messages qui arrivent pendant que vous parcourez les pages ne les décalent pas : rien n'est sauté ni lu deux fois.

Les plafonds et leurs en-têtes

Quand la clé a un plafond d'appels par minute, chaque réponse porte :

En-tête Contenu
X-RateLimit-Limit Le plafond de la clé par minute
X-RateLimit-Remaining Les appels restants dans la minute en cours
X-RateLimit-Reset Quand la fenêtre se rouvre, en secondes Unix

Au-delà du plafond par minute ou du quota journalier, la réponse est 429 RATE_LIMITED avec un en-tête Retry-After, en secondes. Attendez ce délai avant de réessayer : un appel refusé compte quand même, donc réessayer plus vite n'aide pas. Le quota journalier est compté par jour UTC.

Les erreurs

Toutes les erreurs ont la même forme, quel que soit l'endpoint :

{
  "error": {
    "code": "INVALID_PHONE",
    "message": "recipient is not a valid international number",
    "details": { "recipient": "0341234567" },
    "requestId": "req_01J8XK2M9F"
  }
}
Champ Usage
code Un identifiant stable. C'est lui que votre code doit tester, jamais le message
message Une courte explication en anglais, pour un humain qui lit un journal
details Contexte facultatif : le champ en cause, le plafond, la valeur attendue
requestId À transmettre à qui consulte les journaux de la passerelle : il retrouve la requête aussitôt

Les codes que vous rencontrerez le plus souvent :

HTTP Code Que faire
401 MISSING_API_KEY Ajoutez l'en-tête X-API-KEY
403 INVALID_API_KEY La clé est inconnue ou révoquée. Vérifiez-la, ou créez-en une nouvelle
403 IP_NOT_ALLOWED L'appel vient d'une adresse hors de la liste autorisée de la clé
403 FORBIDDEN_SCOPE La portée de la clé ne couvre pas cette opération. Utilisez une clé de portée plus large
400 VALIDATION_ERROR Un champ manque ou sort des bornes. details indique lequel
400 INVALID_PHONE Un numéro n'est pas au format E.164 (+ et indicatif du pays)
400 EMPTY_BODY Le message n'a pas de texte
403 CONTACT_BLACKLISTED Un destinataire s'est désinscrit. Rien n'a été mis en file
422 SCHEDULED_AT_IN_PAST scheduledAt doit être dans le futur
404 NOT_FOUND L'identifiant n'existe pas sur cette passerelle
409 IDEMPOTENCY_CONFLICT Cette Idempotency-Key a déjà servi pour une autre requête
409 VALIDATION_ERROR Un nom qui doit être unique est déjà pris
413 PAYLOAD_TOO_LARGE Le corps dépasse 2 Mio
429 RATE_LIMITED Plafond atteint. Attendez Retry-After
403 LICENSE_BLOCKED La licence a été retirée. Les lectures fonctionnent toujours, les écritures sont refusées. Voir Licence
500 INTERNAL_ERROR Un problème inattendu sur la passerelle. Réessayez plus tard et transmettez le requestId

Un message d'erreur ne contient jamais le texte d'un SMS, un numéro de téléphone complet ni une clé. La liste complète des codes figure dans la référence, sous le schéma ErrorCode.

Ce qui ne produit pas d'erreur. Un téléphone hors ligne ou une SIM qui a atteint son quota du jour ne font pas refuser la requête : le message est accepté (202) et le problème apparaît plus tard, dans son statut. Quand toutes les SIM utilisables ont épuisé leur quota, le message attend en pending_quota_exceeded que le quota se libère. Quand aucun téléphone ne peut le prendre, la passerelle réessaie selon Paramètres > Envoi (4 tentatives par défaut, avec des délais croissants entre elles) et le marque failed après la dernière. Surveillez son statut, ou abonnez-vous à message.failed.

La référence interactive

Chaque passerelle embarque la référence complète de sa propre API, générée à partir du même contrat que le code qui répond. Elle ne peut pas décrire un endpoint que votre version n'a pas.

Ce que vous y trouvez
Référence de l'API dans le menu du dashboard Toutes les opérations regroupées par thème, avec leurs champs, réponses et codes d'erreur
https://<votre passerelle>/api/v1/ La même référence en page autonome, avec « Try it out » : collez une clé et lancez un appel
https://<votre passerelle>/api-docs/openapi.json Le contrat OpenAPI 3.1 brut, pour générer un client dans votre langage

Ces pages se lisent sans connexion : le contrat ne contient aucun secret et aucune de vos données. Lancer un appel depuis elles demande tout de même une clé valide, avec les mêmes contrôles que tout autre appel. Rien n'est chargé depuis internet pour les afficher.

Pour générer un client, pointez votre outil habituel (OpenAPI Generator, openapi-typescript, oapi-codegen, etc.) sur openapi.json.

Avant d'installer. La même référence se trouve sur notre site, sous Référence de l'API en haut de cette documentation (/docs/api-reference), avec son contrat sur /docs/openapi.json. Elle décrit la dernière version publiée, et ses exemples utilisent https://your-gateway.example.com/api/v1 comme URL de base : remplacez-la par l'adresse de votre passerelle. Rien de ce que vous faites sur cette page n'atteint une passerelle, et elle n'a pas de « Try it out ». Une fois votre passerelle en service, fiez-vous à sa propre référence : elle correspond exactement à la version installée.

Pour aller plus loin

  • Clés API : créer, restreindre, renouveler et révoquer des clés.
  • Webhooks : être appelé quand un SMS arrive ou qu'un statut change.
  • Sécurité : ce qui protège votre installation et ce que vous devez vérifier.

Rechercher dans la documentation

Saisissez quelques mots, puis choisissez une page.