Aller au contenu

Référence API

Lots

Soumettez des messages et relisez ce qu’ils sont devenus.

Un 202 ne veut pas dire que tout a été accepté

Soumettre un lot

POST/public/v1/batches

Tarife le lot, réserve les fonds et met en file d’attente chaque destinataire accepté. Renvoie 202 : le lot est accepté et payé, mais rien n’a encore été livré.

Portée requise MESSAGES_SEND

Paramètres du corps

senderstringObligatoire

Le nom ou le numéro affiché sur le téléphone du destinataire. Doit être approuvé et enregistré auprès d’un opérateur ; vérifiez d’abord is_sendable sur le point de terminaison des identifiants d’expéditeur.

max 32 characters

bodystringObligatoire

Le modèle de message. Peut contenir des balises de fusion entre accolades, résolues pour chaque destinataire à partir de ses attributs.

max 4000 characters, not blank after trimming

message_classstringObligatoire

Décide de la priorité dans la file d’attente, de l’application ou non des heures de silence, des plafonds de fréquence appliqués et de la liste de suppression consultée.

TRANSACTIONAL | MARKETING | OTP

channelstringOptionnel

Le canal de livraison.

SMS | WHATSAPP — default SMS

recipientsarrayObligatoire

À qui envoyer. Chaque entrée porte un numéro et, éventuellement, les valeurs des balises de fusion de ce destinataire.

1–100000 items

recipients.msisdnstringObligatoire

Forme locale ou internationale. Normalisé au format E.164 avec le Burkina Faso comme région par défaut ; un numéro impossible à analyser devient une ligne de rejet plutôt qu’une erreur.

max 32 characters — local or E.164, default region BF

recipients.attributesobjectOptionnel

Valeurs des balises de fusion pour ce destinataire. Une balise sans attribut correspondant est remplacée par une chaîne vide.

scheduled_atstringOptionnel

Quand envoyer. Doit être dans le futur. Un lot qui finit avec plus de 30 minutes de retard est annulé et remboursé plutôt qu’envoyé.

ISO 8601, must be in the future

timezonestringOptionnel

Le fuseau horaire dans lequel la planification est exprimée. N’a de sens qu’accompagné de scheduled_at.

IANA name, max 64 characters — defaults to the account timezone

is_testbooleanOptionnel

Exclut le lot des statistiques de campagne. Il est tout de même facturé et tout de même livré — c’est un indicateur de reporting, pas un bac à sable.

default false

Requête

curl -X POST https://api.smsend.net/public/v1/batches \
  -H "Authorization: Bearer $SMSEND_API_KEY" \
  -H "Idempotency-Key: order-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "sender": "PRESTIGE",
    "body": "Bonjour {prenom}, votre commande {ref} est prête.",
    "message_class": "TRANSACTIONAL",
    "recipients": [
      { "msisdn": "+22670000001", "attributes": { "prenom": "Aïssata", "ref": "CMD-8842" } },
      { "msisdn": "+22670000002", "attributes": { "prenom": "Boureima", "ref": "CMD-8843" } }
    ]
  }'
<?php

$response = Http::withToken(getenv('SMSEND_API_KEY'))
    ->withHeaders(['Idempotency-Key' => 'order-1042'])
    ->post('https://api.smsend.net/public/v1/batches', [
        'sender' => 'PRESTIGE',
        'body' => 'Bonjour {prenom}, votre commande {ref} est prête.',
        'message_class' => 'TRANSACTIONAL',
        'recipients' => [
            ['msisdn' => '+22670000001', 'attributes' => ['prenom' => 'Aïssata', 'ref' => 'CMD-8842']],
            ['msisdn' => '+22670000002', 'attributes' => ['prenom' => 'Boureima', 'ref' => 'CMD-8843']],
        ],
    ]);

$batch = $response->json('data');
const response = await fetch('https://api.smsend.net/public/v1/batches', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SMSEND_API_KEY}`,
    'Idempotency-Key': 'order-1042',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sender: 'PRESTIGE',
    body: 'Bonjour {prenom}, votre commande {ref} est prête.',
    message_class: 'TRANSACTIONAL',
    recipients: [
      { msisdn: '+22670000001', attributes: { prenom: 'Aïssata', ref: 'CMD-8842' } },
      { msisdn: '+22670000002', attributes: { prenom: 'Boureima', ref: 'CMD-8843' } },
    ],
  }),
})

const { data } = await response.json()
import os
import requests

response = requests.post(
    "https://api.smsend.net/public/v1/batches",
    headers={
        "Authorization": f"Bearer {os.environ['SMSEND_API_KEY']}",
        "Idempotency-Key": "order-1042",
    },
    json={
        "sender": "PRESTIGE",
        "body": "Bonjour {prenom}, votre commande {ref} est prête.",
        "message_class": "TRANSACTIONAL",
        "recipients": [
            {"msisdn": "+22670000001", "attributes": {"prenom": "Aïssata", "ref": "CMD-8842"}},
            {"msisdn": "+22670000002", "attributes": {"prenom": "Boureima", "ref": "CMD-8843"}},
        ],
    },
)

batch = response.json()["data"]

Réponses

202Le lot a été accepté. Lisez les compteurs pour voir combien de destinataires sont passés.
{
  "data": {
    "batch_id": "01K3F7XQZ8V2N4M6P8R0T5CJWE",
    "status": "SCHEDULED",
    "accepted": 948,
    "rejected": 52,
    "rejections": [
      { "index": 17, "msisdn": "+22670000018", "reason_code": "SUPPRESSED" },
      { "index": 41, "msisdn": null, "reason_code": "INVALID_MSISDN" },
      { "index": 88, "msisdn": "+22670000089", "reason_code": "DUPLICATE_IN_BATCH" }
    ],
    "reserved_amount": 23700,
    "currency": "XOF",
    "total_segments": 948
  }
}

Erreurs possibles

CodeStatutSignification
IDEMPOTENCY_KEY_REQUIRED422Un lot a été soumis sans en-tête Idempotency-Key.
VALIDATION_FAILED422La requête était malformée : un paramètre manquant ou invalide, une longueur de clé d’idempotence incorrecte, une URL de webhook rejetée, ou une méthode ou un type de contenu non pris en charge.
REQUEST_IN_PROGRESS409Une autre requête portant cette clé d’idempotence est encore en vol. Attendez l’intervalle indiqué et réessayez.
IDEMPOTENCY_KEY_REUSED409Cette clé d’idempotence a déjà servi pour une requête avec un corps différent. Utilisez une nouvelle clé.
INSUFFICIENT_FUNDS402Le portefeuille prépayé ne peut pas couvrir le montant annoncé.
CREDIT_LIMIT_EXCEEDED402L’encours postpayé dépasserait le plafond de crédit du compte.
WALLET_IN_DEBIT402Le portefeuille est débiteur et ne peut pas servir à dépenser.
BATCH_EMPTY422Le lot ne contient aucun destinataire.
BATCH_BODY_EMPTY422Le corps du message est vide une fois les espaces retirés.
SCHEDULE_INVALID422L’heure planifiée est dans le passé, ou le fuseau horaire n’est pas un identifiant IANA valide.
SENDER_NOT_APPROVED422L’expéditeur n’est pas approuvé pour ce compte.
UNAUTHORIZED401Aucune clé, une clé malformée, un mauvais secret, une clé révoquée ou expirée, ou un compte suspendu. Tous indiscernables par construction.
INSUFFICIENT_SCOPE403La clé est valide mais ne porte pas la portée exigée par ce point de terminaison. La portée est nommée dans le detail.
ACCOUNT_SUSPENDED403Le compte est suspendu et ne peut pas utiliser l’API.
RATE_LIMITED429La limite de requêtes de la clé a été dépassée. Attendez l’intervalle indiqué dans Retry-After.
INTERNAL_ERROR500Une défaillance inattendue du côté de SMSend. Citez l’identifiant de requête à l’assistance.

Récupérer un lot

GET/public/v1/batches/{batch_id}

Lit l’état actuel d’un lot, y compris les compteurs cumulatifs par état et la liste complète des destinataires rejetés.

Portée requise MESSAGES_READ

Paramètres de chemin

batch_idstringObligatoire

L’identifiant renvoyé lors de la soumission du lot.

26-character ULID

Requête

curl https://api.smsend.net/public/v1/batches/01K3F7XQZ8V2N4M6P8R0T5CJWE \
  -H "Authorization: Bearer $SMSEND_API_KEY"
<?php

$batch = Http::withToken(getenv('SMSEND_API_KEY'))
    ->get('https://api.smsend.net/public/v1/batches/01K3F7XQZ8V2N4M6P8R0T5CJWE')
    ->json('data');
const response = await fetch(
  'https://api.smsend.net/public/v1/batches/01K3F7XQZ8V2N4M6P8R0T5CJWE',
  { headers: { Authorization: `Bearer ${process.env.SMSEND_API_KEY}` } },
)

const { data } = await response.json()
import os
import requests

batch = requests.get(
    "https://api.smsend.net/public/v1/batches/01K3F7XQZ8V2N4M6P8R0T5CJWE",
    headers={"Authorization": f"Bearer {os.environ['SMSEND_API_KEY']}"},
).json()["data"]

Réponses

200Le lot.
{
  "data": {
    "batch_id": "01K3F7XQZ8V2N4M6P8R0T5CJWE",
    "status": "SENDING",
    "accepted": 948,
    "rejected": 52,
    "rejections": [
      { "index": 17, "msisdn": "+22670000018", "reason_code": "SUPPRESSED" }
    ],
    "sender": "PRESTIGE",
    "channel": "SMS",
    "message_class": "TRANSACTIONAL",
    "scheduled_at": "2026-08-24T09:00:00+00:00",
    "counts": {
      "total": 948,
      "queued": 948,
      "submitted": 941,
      "sent": 935,
      "delivered": 900,
      "failed": 35,
      "cancelled": 0
    },
    "created_at": "2026-08-23T14:05:11+00:00"
  }
}

Erreurs possibles

CodeStatutSignification
NOT_FOUND404Ressource inexistante — y compris une ressource appartenant à un autre compte, délibérément indiscernable d’une ressource qui n’a jamais existé.
UNAUTHORIZED401Aucune clé, une clé malformée, un mauvais secret, une clé révoquée ou expirée, ou un compte suspendu. Tous indiscernables par construction.
INSUFFICIENT_SCOPE403La clé est valide mais ne porte pas la portée exigée par ce point de terminaison. La portée est nommée dans le detail.
RATE_LIMITED429La limite de requêtes de la clé a été dépassée. Attendez l’intervalle indiqué dans Retry-After.
INTERNAL_ERROR500Une défaillance inattendue du côté de SMSend. Citez l’identifiant de requête à l’assistance.

Lots de plus de 10 000 destinataires

Au-delà de 10 000 destinataires, la soumission est validée de façon asynchrone, et la réponse revient avec le statut VALIDATING et tous les compteurs à zéro. Ces zéros veulent dire « pas encore calculé », pas « rien n’a été accepté ».

{
  "data": {
    "batch_id": "01K3F7XQZ8V2N4M6P8R0T5CJWE",
    "status": "VALIDATING",
    "accepted": 0,
    "rejected": 0,
    "rejections": [],
    "reserved_amount": 0,
    "currency": "XOF",
    "total_segments": 0
  }
}

Les vrais chiffres arrivent soit lors d’une récupération ultérieure du lot, soit par un webhook BATCH_ACCEPTED. Un lot laissé en validation pendant 30 minutes est marqué en échec.

Motifs de rejet

Chaque entrée nomme la position, à partir de zéro, du destinataire dans le tableau que vous avez soumis, ce qui vous permet de la relier à vos propres données. Le numéro est nul quand l’entrée n’a pas pu être normalisée du tout.

ValeurSignification
INVALID_MSISDN
Le numéro n’a pas pu être analysé, ou n’est pas valide pour sa région.
DUPLICATE_IN_BATCH
Le même numéro normalisé apparaît plusieurs fois dans cette soumission.
UNROUTABLE_PREFIX
Aucun opérateur ne possède le préfixe du numéro.
SUPPRESSED
Le destinataire s’est désinscrit de cette classe de message.
DUPLICATE_RECENT_BODY
Le même message rendu a déjà été envoyé à ce numéro au cours des dernières 24 heures.
RATE_LIMIT_PER_MINUTE
Le plafond de destinataires par minute du compte a été atteint.
RATE_LIMIT_PER_DAY
Le plafond quotidien de destinataires du compte a été atteint.
QUIET_HOURS
Marketing uniquement. L’envoi est tombé en dehors des heures autorisées du compte.
FREQUENCY_CAP_DAILY
Marketing uniquement. Ce destinataire a déjà reçu le maximum de la journée.
FREQUENCY_CAP_WEEKLY
Marketing uniquement. Ce destinataire a déjà reçu le maximum de la semaine.

Statuts de lot

SETTLED est le seul état terminal qui signifie que le travail est fini et que les comptes sont soldés.

ValeurSignification
VALIDATING
Accepté mais pas encore tarifé ni payé. Atteignable uniquement sur le chemin asynchrone, et expire au bout de 30 minutes.
SCHEDULED
Tarifé et réservé, en attente de son heure planifiée.
QUEUED
Tarifé et réservé, en attente du routeur.
SENDING
En cours d’envoi.
SETTLEDTerminal
Terminé, comptes soldés et remboursement éventuel passé.
REJECTEDTerminal
Tous les destinataires ont été écartés avant la moindre réservation.
FAILEDTerminal
La validation elle-même a échoué.
CANCELLEDTerminal
Annulé avant ou pendant l’envoi, et remboursé.