Référence API · v1
Documentation de l'API v1
Le contrat historique de FasterMessage, maintenu à l'identique pour les intégrations en place. Il ne reçoit plus d'évolution : tout nouveau développement se fait sur l'API v2.
Guide de migration V1 → V2
La v2 n'est pas une réécriture de surface : elle corrige trois défauts structurels de la v1 — l'enveloppe de succès, l'authentification par la barre d'adresse et l'absence d'identifiant de requête. La migration est mécanique, mais elle n'est pas transparente.
Correspondance des points d’entrée
| API v1 | API v2 | Ce qui change |
|---|---|---|
| POST /v1/sms/send | POST /v2/sms/send | text devient content ; messageId devient client_reference ; la réponse n’est plus enveloppée. |
| GET /v1/sms/send | — | Supprimé. L’envoi en GET faisait transiter les identifiants par la barre d’adresse. Passer en POST. |
| GET /v1/sms/balance | GET /v2/balance | Renvoie désormais tous les canaux et la devise, au lieu d’un solde SMS nu. |
| — | GET /v2/messages/{id} | Nouveau. Le suivi ne dépend plus du seul webhook. |
| — | POST /v2/sms/batch | Nouveau. 500 envois par appel, avec bilan par ligne. |
| — | POST /v2/messages | Nouveau. Envoi unifié : WhatsApp, e-mail et voix par le même contrat. |
| GET /v1/ping | GET /v2/ping | Inchangé — ouvert, sans identifiants. |
Authentification
| v1 | v2 | |
|---|---|---|
| Nombre d’identifiants | Un seul (au choix parmi cinq transports). | Deux, tous deux exigés : Basic + X-Api-Key. |
| Transport | En-tête, query string ou corps. | En-têtes uniquement. |
| Environnement | Aucune notion dans le contrat — mais une clé fm_test_ y simule comme en v2 : rien n’est remis ni débité. | Déduit du préfixe de clé (fm_test_ / fm_live_). |
| Refus | 401 AUTHENTICATION_FAILED dans l’enveloppe. | 401 authentication_error, corps d’erreur normalisé. |
Format de réponse
{
"status": true,
"code": "SUBMITTED",
"description": "202: The request has been submitted and is being processed",
"messageId": "cmd-42",
"smsCount": 1
}{
"id": "msg_2f1c9d4e8a6b",
"channel": "sms",
"status": "queued",
"to": "22960000000",
"from": "FASTERMSG",
"units": 1,
"client_reference": "cmd-42",
"created_at": "2026-09-07T10:00:00.000Z"
}- Le succès n'est plus enveloppé : c'est le code HTTP qui porte l'issue.
- Les champs passent en
snake_case— la v1 mélangeaitcamelCaseet minuscules collées. statuschange de nature : booléen d'enveloppe en v1, état du message en v2 (queued,delivered…).smsCountdevientunits, et vaut pour les quatre canaux.- Toute réponse porte un
X-Request-Id, à citer au support.
Ruptures de compatibilité
| Rupture | Effet si non traitée | À faire |
|---|---|---|
status n’est plus un booléen | Un test if (res.status) passe désormais sur une chaîne — donc toujours vrai. | Tester le code HTTP, puis error.code. |
Plus de champ code en succès | Un test code === "SUBMITTED" ne matche plus jamais. | Tester status === "queued" sur la ressource. |
| Codes d’erreur renommés | INSUFFICIENT_BALANCE devient insufficient_balance, sous error. | Réécrire la table de correspondance des codes. |
| Identifiants en query refusés | Les appels qui passaient la clé en URL échouent en 401. | Basculer sur les en-têtes. |
| Envoi en GET supprimé | 404 sur GET /v2/sms/send. | Passer en POST. |
| Format des webhooks | La charge utile v2 ne porte plus uuid ni level. | Voir l’encart ci-dessous — vos rappels v1 ne changent pas. |
Authentification
La v1 accepte cinq transports pour un même identifiant, essayés dans cet ordre. Cette souplesse est au contrat : des intégrations en place dépendent de chacune de ces variantes.
| Rang | Transport | Forme |
|---|---|---|
| 1 | En-tête | Authorization: Basic <clé> |
| 2 | En-têtes | username + password |
| 3 | En-tête | x-api-key |
| 4 | Paramètre | username + password (query en GET, corps sinon) |
| 5 | Paramètre | x-api-key |
x-api-key: fm_live_xxxxxxxxxxxxxxxxxxxxxxxx Content-Type: application/json
Environnements
Pour éprouver une intégration sans facturer, deux voies : passer un compte en mode test depuis le backoffice, ou — c'est la voie recommandée — écrire l'intégration directement sur la v2.
Le bac à sable ci-dessous vise le même environnement de test que la vue v2 : il vous permet de comparer les deux contrats sur un appel identique.
Essayer l'API
Composez un appel au format v1 et exécutez-le depuis cette page. Le script se régénère en direct dans les quatre langages.
Environnement de test uniquement — n’utilisez jamais vos identifiants de production sur cette page. Les valeurs saisies restent dans votre navigateur : elles ne sont ni enregistrées, ni transmises à un tiers.
curl -X POST https://api.fastermessage.com/v1/sms/send \ -H "x-api-key: $FM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "FASTERMSG", "to": "", "text": "Votre code est 480912. Valable 5 min." }'
Renseignez les champs requis pour lancer l’appel.
Envoyer un SMS
Le même envoi existe en GET /v1/sms/send, avec les paramètres en query. Il est maintenu pour les intégrations en place mais ne doit plus être employé : il expose la clé dans l'URL.
Paramètres
| Champ | Type | Description |
|---|---|---|
| fromrequis | string | Nom alphanumérique (11 caractères max) ou numéro valide, approuvé sur le compte. Son absence répond MISSING_PARAMETERS_FROM : aucun expéditeur par défaut n'est appliqué. |
| torequis | string | Numéro du destinataire avec indicatif pays (ex. 2296xxxxxxxx). |
| textrequis | string | Contenu du message. content en est un alias historique, prioritaire si les deux sont fournis. |
| sendAtoptionnel | string | Date d’envoi programmé. Alias historique : date. |
| timezoneoptionnel | string | Fuseau appliqué à sendAt. |
| accentsoptionnel | boolean | Force l’encodage UCS-2 pour les accents et caractères spéciaux. Divise la capacité par segment. |
| messageIdoptionnel | string | Identifiant fourni par le client, repris dans les accusés de livraison — et jusque dans les réponses d’échec, pour corréler un refus. |
| dlrUrloptionnel | string | URL de rappel de l’accusé de livraison. |
| dlrMethodoptionnel | string | Méthode du rappel : POST (défaut) ou GET. |
curl -X POST https://api.fastermessage.com/v1/sms/send \ -H "x-api-key: $FM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "FASTERMSG", "to": "22960000000", "text": "Votre commande cmd-42 est prête.", "messageId": "cmd-42", "dlrUrl": "https://exemple.test/webhooks/dlr" }'
const response = await fetch('https://api.fastermessage.com/v1/sms/send', { method: 'POST', headers: { 'x-api-key': process.env.FM_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ "from": "FASTERMSG", "to": "22960000000", "text": "Votre commande cmd-42 est prête.", "messageId": "cmd-42", "dlrUrl": "https://exemple.test/webhooks/dlr" }), }); const data = await response.json(); console.log(response.status, data);
<?php $ch = curl_init('https://api.fastermessage.com/v1/sms/send'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'x-api-key: ' . getenv('FM_API_KEY'), 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'from' => 'FASTERMSG', 'to' => '22960000000', 'text' => 'Votre commande cmd-42 est prête.', 'messageId' => 'cmd-42', 'dlrUrl' => 'https://exemple.test/webhooks/dlr', ]), ]); $response = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); echo $status . PHP_EOL . $response . PHP_EOL;
import os import requests response = requests.post( "https://api.fastermessage.com/v1/sms/send", headers={"x-api-key": os.environ["FM_API_KEY"]}, json={ "from": "FASTERMSG", "to": "22960000000", "text": "Votre commande cmd-42 est prête.", "messageId": "cmd-42", "dlrUrl": "https://exemple.test/webhooks/dlr", }, timeout=15, ) print(response.status_code, response.json())
{
"status": true,
"code": "SUBMITTED",
"description": "202: The request has been submitted and is being processed",
"messageId": "cmd-42",
"smsCount": 1
}{
"status": false,
"code": "INSUFFICIENT_BALANCE",
"description": "402: Insufficient balance to process the request",
"messageId": "cmd-42"
}Consulter le solde
Renvoie le solde SMS, sans devise ni ventilation par canal — la v2 rend les quatre canaux et la devise du compte.
curl https://api.fastermessage.com/v1/sms/balance \ -H "x-api-key: $FM_API_KEY"
const response = await fetch('https://api.fastermessage.com/v1/sms/balance', { method: 'GET', headers: { 'x-api-key': process.env.FM_API_KEY, }, }); const data = await response.json(); console.log(response.status, data);
<?php $ch = curl_init('https://api.fastermessage.com/v1/sms/balance'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: ' . getenv('FM_API_KEY'), ], ]); $response = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); echo $status . PHP_EOL . $response . PHP_EOL;
import os import requests response = requests.get( "https://api.fastermessage.com/v1/sms/balance", headers={"x-api-key": os.environ["FM_API_KEY"]}, timeout=15, ) print(response.status_code, response.json())
{
"status": true,
"code": "SUCCESS",
"description": "200: The operation was successful",
"balance": 289009
}Accusés de livraison (DLR)
L'URL déclarée dans dlrUrl est appelée à chaque changement d'état. La méthode est POST par défaut, GET si vous l'avez configuré ainsi.
{
"uuid": "7d3b5e0c-1a84-4c1e-9f2a-2f1c9d4e8a6b",
"level": 3,
"status": "Delivered",
"receivedAt": "2026-09-07T10:00:06.900Z",
"messageId": "cmd-42",
"to": "22960000000",
"smsCount": 1
}Quatre champs sont toujours présents : uuid, level, status et receivedAt. Les autres n'apparaissent que si vous les avez activés dans vos variables DLR, depuis le backoffice.
Variables DLR activables
| Variable | Contenu |
|---|---|
| messageId | Votre identifiant, repris de la requête d’envoi. |
| from | Expéditeur employé. |
| to | Destinataire. |
| text | Contenu émis. |
| smsCount | Nombre de segments facturés. |
| sentAt | Horodatage de remise au transporteur. |
| messageStatusCode | Code d’état brut de l’opérateur. |
| messageStatus | Libellé d’état de l’opérateur. |
| description | Précision textuelle sur l’état. |
| reference | Référence interne du message. |
| batchuuid | Identifiant du lot, pour un envoi groupé. |
Sécurité
Chaque rappel porte l'en-tête X-Fastermessage-Signature — un HMAC-SHA256 de la charge utile — et s'annonce par l'agent Fastermessage-DLR/1.0. Vérifiez la signature avant de traiter, et servez votre point de terminaison en HTTPS.
Codes de statut
Chaque réponse porte un code et une description formatée "<code HTTP>: <message>". Ces valeurs sont gelées : ni les libellés, ni les codes HTTP ne changeront, y compris pour corriger une formulation — des intégrations les comparent à des chaînes fixes.
Codes d’envoi
| code | HTTP | Signification |
|---|---|---|
| SUBMITTED | 202 | The request has been submitted and is being processed |
| PROGRAMMED | 200 | The task is programmed |
| SENT | 200 | The message has been sent |
| DELIVERED | 200 | The message was delivered successfully |
| UNDELIVERED | 424 | The message could not be delivered |
| SUCCESS | 200 | The operation was successful |
Codes de refus
| code | HTTP | Signification |
|---|---|---|
| AUTHENTICATION_FAILED | 401 | Authentication failed |
| FORBIDDEN | 403 | Access is forbidden |
| MISSING_PARAMETERS | 400 | Required parameters are missing |
| MISSING_PARAMETERS_TO | 400 | Required parameter [to] is missing |
| MISSING_PARAMETERS_CONTENT | 400 | Required parameter [content] or [text] is missing |
| MISSING_PARAMETERS_FROM | 400 | Required parameter [from] is missing |
| INVALID_SENDERID | 400 | Invalid sender ID |
| INVALID_PHONE | 400 | Invalid phone number |
| INVALID_OPERATOR | 400 | Invalid operator |
| INVALID_DATE_TIMEZONE | 400 | Invalid date or timezone |
| UNDEFINED_PRICE | 400 | Price not defined for this operator/country |
| INSUFFICIENT_BALANCE | 402 | Insufficient balance to process the request |
| SUSPENDED | 423 | The resource is suspended |
| RATE_LIMIT_EXCEEDED | 429 | The rate limit has been exceeded |
| GATEWAY_UNAVAILABLE | 503 | Gateway unavailable, please try again later |
| INTERNAL_SERVER_ERROR | 500 | Internal server error occurred |
Limites
Le débit est plafonné par compte. Un dépassement répond RATE_LIMIT_EXCEEDED en 429.
- Pas d'envoi en lot : un message par appel.
- Pas d'idempotence : un rejeu après coupure réseau renvoie et refacture le message.
- Pas d'identifiant de requête : un incident ne peut pas être corrélé à nos journaux.
Ces trois manques sont comblés par la v2 — voir le guide de migration.