Envoyer des SMS
Ce guide présente le point de terminaison d'envoi unitaire, POST /v1/sms/messages. Construisez une charge utile JSON avec un destinataire, un expéditeur, un corps et une catégorie. Bird renvoie 202 Accepted avec un identifiant de message et effectue la livraison de façon asynchrone. Chaque requête envoie un message à un seul destinataire. Pour envoyer de nombreux messages en une fois, utilisez l'envoi par lot. Pour envoyer un modèle plutôt que votre propre texte, fournissez un objet template à la place de text et category. Les modèles intégrés refusent from ; les modèles de l'espace de travail l'exigent.
Avant d'envoyer : activez le pays de destination
Votre espace de travail n'envoie des SMS que vers les pays de sa liste de destinations autorisées. Le pays d'origine de votre organisation est celui de l'adresse d'enregistrement de son profil d'entreprise, ou celui de l'adresse de facturation si l'adresse d'enregistrement n'indique aucun pays. La liste autorisée de votre espace de travail est vide au départ. Chaque fois que le pays d'origine est défini ou modifié, Bird ajoute ce pays à la liste. Bird refuse un envoi vers un pays absent de la liste avec 422 SMSDestinationNotEnabled, avant de déterminer l'expéditeur. Activez les pays que vous desservez dans le tableau de bord, sous SMS > Destinations.
Sur la même page, vous pouvez définir une limite de dépenses quotidienne pour chaque pays, dans la devise de votre portefeuille. Pour en définir une, il vous faut un portefeuille d'organisation et le droit de le consulter ; ce n'est pas nécessaire pour en supprimer une. La limite s'applique que le pays soit activé ou non. Dès que les dépenses du jour vers ce pays l'atteignent, Bird refuse les nouveaux messages vers ce pays avec 422 SMSDestinationDailySpendExceeded jusqu'à minuit UTC. Les messages de vérification sont également refusés. Une dépense est comptée au moment où le message est facturé : les messages déjà acceptés peuvent donc dépasser la limite à hauteur de leur coût. Une modification de limite met jusqu'à une minute à s'appliquer aux envois.
Un envoi minimal
La plus petite charge utile valide en texte libre comprend un destinataire to, un expéditeur from, un corps text et une category.
const msg = await bird.sms.send({
from: "+15557654321",
to: "+14155550100",
text: "Your verification code is 123456.",
category: "authentication",
});
console.log(msg.id, msg.status);msg = client.sms.send(
from_="+15557654321",
to="+15551234567",
text="Your verification code is 123456.",
category="authentication",
)
print(msg.id, msg.status)msg, err := client.Sms.Send(context.Background(), bird.SmsSendParams{
From: "+15557654321",
To: "+15551234567",
Text: "Your verification code is 123456.",
Category: bird.SMSCategoryAuthentication,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)$message = $bird->sms->send(
from: '+15557654321',
to: '+15551234567',
text: 'Your verification code is 123456.',
category: 'authentication',
);
echo $message->getId(), ' ', $message->getStatus();bird sms send --body-file - <<'JSON'
{
"to": "+14155550100",
"from": "+15557654321",
"text": "Your verification code is 123456.",
"category": "authentication",
"options": {
"smart_encoding": true
},
"tags": [
{
"name": "campaign",
"value": "signup"
}
],
"metadata": {
"user_id": "usr_12345"
}
}
JSONcurl -X POST https://eu1.platform.bird.com/v1/sms/messages \
-H "Authorization: Bearer bk_eu1_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+31612345678",
"from": "Bird",
"text": "Your Bird verification code is 481920. It expires in 10 minutes.",
"category": "authentication"
}'Utilisez votre hôte régional (https://us1.platform.bird.com ou https://eu1.platform.bird.com) avec une clé bk_{region}_... correspondante. La réponse contient le message accepté :
{
"id": "sms_01ky7qmwgpfkybj9ecrnwjx714",
"direction": "outbound",
"status": "accepted",
"to": "+31612345678",
"from": "Bird",
"text": "Your Bird verification code is 481920. It expires in 10 minutes.",
"category": "authentication",
"segments": { "count": 1, "encoding": "GSM_7BIT", "characters": 64 },
"cost": null,
"carrier": null,
"mcc_mnc": null,
"sent_at": null,
"delivered_at": null,
"created_at": "2026-07-23T14:56:34.326Z"
}status: accepted signifie que Bird a reçu le message et le traite ; cost vaut null car la tarification a lieu pendant le traitement. La suite est décrite dans le modèle asynchrone.
Construire la charge utile
Destinataire
to désigne un seul destinataire au format E.164 : un + initial, l'indicatif du pays et le numéro de l'abonné, par exemple +31612345678. Un message part vers un seul destinataire, sans cc, bcc ni tableau de destinataires. Pour joindre de nombreuses personnes, envoyez un lot.
Expéditeur
from est l'expéditeur que voit le destinataire. Il est obligatoire pour un envoi de texte libre, sauf si messaging_profile_id désigne un profil de messagerie doté d'un expéditeur pour le pays de destination ou d'un expéditeur par défaut. Il prend l'une des deux formes suivantes, et les formes acceptées dépendent du pays de destination :
- Un identifiant d'expéditeur alphanumérique : de 3 à 11 lettres, chiffres, espaces, tirets, traits de soulignement ou points, avec au moins une lettre et aucun séparateur au début ni à la fin, par exemple
BirdouAcme-Co. Il doit contenir une lettre : une suite de chiffres ponctuée comme555 555est donc rejetée. Certains pays exigent un enregistrement, et d'autres, dont les États-Unis, n'acceptent pas les expéditeurs alphanumériques. Les destinataires ne peuvent pas y répondre. - Un numéro dont votre espace de travail est propriétaire, au format E.164 ou en chiffres seuls. Toute valeur
fromcomposée uniquement de chiffres est traitée comme numérique et recherchée parmi vos expéditeurs : un numéro quelconque qui ne vous appartient pas est donc rejeté. C'est le numéro lui-même qui détermine s'il fonctionne comme numéro long, numéro gratuit ou code court, et non le nombre de chiffres saisis. Une valeurfromà 6 chiffres n'est pas un code court parce qu'elle compte 6 chiffres ; elle l'est si le numéro que vous possédez en est un.
Un expéditeur non valide pour la destination est rejeté avec une 422 qui indique la raison (par exemple SMSAlphaNotSupported là où les expéditeurs alphanumériques ne sont pas disponibles). Lors d'un envoi de modèle intégré, from n'est pas accepté : Bird choisit un expéditeur selon la destination et la catégorie. Un modèle de l'espace de travail exige votre propre expéditeur from, ou un profil de messagerie qui en fournit un.
La réservation d'un identifiant d'expéditeur, les exigences de chaque pays à son égard et son enregistrement pays par pays sont décrits dans Identifiants d'expéditeur SMS.
Pour regrouper les expéditeurs et les valeurs par défaut d'un programme, créez un profil de messagerie et indiquez-le dans messaging_profile_id. Si from est omis, Bird envoie depuis l'expéditeur du profil pour le pays de destination, ou depuis son expéditeur par défaut. Si category est omis, la catégorie par défaut du profil s'applique, et options.smart_encoding, cost_center, validity_period et options.callback_url reprennent les paramètres du profil. Les valeurs définies sur le message sont prioritaires.
Corps et catégorie
text est le corps du message, d'au moins un caractère. Il est facturé et distribué en segments ; un envoi est plafonné à 12 segments (environ 1 836 caractères GSM-7, ou 804 si le corps utilise l'encodage étendu UCS-2). Un corps qui dépasse ce plafond est rejeté avec une 422 au lieu d'être tronqué.
category est obligatoire pour un envoi de texte libre, sauf si le profil de messagerie indiqué dans messaging_profile_id définit une catégorie par défaut. Il classe le message comme transactional, marketing, authentication ou service, ce qui indique à Bird et aux opérateurs pourquoi vous envoyez le message. Un code de vérification à usage unique utilise authentication ; une promotion utilise marketing. Choisissez la catégorie qui correspond à l'objet du message.
Tags et métadonnées
Ces champs associent vos propres données à un envoi, mais chacun a un rôle différent :
tagssont des paires{name, value}structurées (20 au maximum par envoi ; nom de 1 à 32 caractères, valeur de 1 à 64, ASCII[A-Za-z0-9_-]uniquement, sensibles à la casse, noms uniques au sein d’un envoi). Ce sont des dimensions de filtrage à part entière : filtrez la liste des messages par tag. Utilisez-les pour des libellés à faible cardinalité, commecampaignouexperiment_variant.metadataest un objet JSON libre (2 Ko au maximum une fois sérialisé). Il est stocké, renvoyé lors des lectures API et repris dans chaque événement webhook, mais ne sert pas de dimension de filtrage. Utilisez-le pour transmettre du contexte aller-retour : identifiants internes, clés étrangères, toute donnée que vous voulez récupérer avec chaque événement.referenceest une chaîne de 1 à 255 caractères qui vous permet de retrouver le message, comme un numéro de commande ou de ticket.?reference=sur la liste des messages la recherche à l’identique, en respectant la casse, et plusieurs messages peuvent partager la même valeur. Elle est renvoyée lors des lectures et reprise dans chaque événement webhook. Ne la définissez que si vous devez effectuer une recherche dessus, et placez toute autre donnée dansmetadata.
{
"tags": [{ "name": "campaign", "value": "spring-2026" }],
"metadata": { "user_id": "usr_12345", "order_id": "ord_98765" },
"reference": "ord_98765"
}Référence des champs
| Champ | Type | Obligatoire | Limites / remarques |
|---|---|---|---|
to | chaîne (E.164) | oui | Un destinataire par message |
from | chaîne | oui* | Numéro E.164 qui vous appartient, identifiant d'expéditeur alphanumérique (3 à 11 caractères, dont au moins une lettre) ou code court (5 à 6 chiffres) |
text | chaîne | oui* | Au moins 1 caractère ; limité à 12 segments |
category | chaîne | oui* | transactional, marketing, authentication ou service |
template | objet | oui* | {slug, parameters?, language?} pour un modèle intégré ou un modèle d'espace de travail |
tags | {name, value}[] | non | 20 au maximum ; nom de 1 à 32 caractères, valeur de 1 à 64 caractères ; [A-Za-z0-9_-] uniquement |
metadata | objet | non | JSON libre, 2 Ko maximum une fois sérialisé |
reference | chaîne | non | 1 à 255 caractères ; correspondance exacte pour ?reference= dans la liste des messages |
messaging_profile_id | chaîne | non | Un profil de messagerie qui fournit l'expéditeur et les valeurs par défaut ; les valeurs définies sur le message sont prioritaires |
validity_period | entier | non | De 60 à 172 800 secondes après l'acceptation ; omettez-le pour utiliser la valeur du profil de messagerie ou les règles propres à l'opérateur |
options | objet | non | Paramètres de traitement propres au message, comme smart_encoding (voir segments et encodage) et callback_url (voir URL de rappel) |
* Les envois de texte libre exigent from, text et category, mais un profil de messagerie peut fournir from et category. Les envois de modèles exigent template à la place de text et category (et refusent ces deux champs). Les modèles intégrés refusent from ; les modèles d'espace de travail l'exigent.
Envoyer avec un modèle
Définissez template pour envoyer un modèle intégré ou un modèle d'espace de travail publié. La création de modèles d'espace de travail est en préversion API. Le catalogue, les variables et les règles d'expéditeur sont décrits dans Modèles SMS.
Segments et encodage
SMS est facturé par segment. Un message qui tient dans l'encodage GSM-7 dispose de 160 caractères pour un segment unique ; avec UCS-2 (déclenché par des emoji, des caractères CJK ou d'autres caractères hors GSM), la limite descend à 70. Les messages plus longs sont découpés en plusieurs segments, avec une limite par segment légèrement inférieure. Chaque réponse indique le segments appliqué : le count facturable, le encoding et le nombre de caractères. Les segments sont l'unité de facturation ; voir coût.
Lorsque seuls des caractères typographiques font sortir un corps de message de GSM-7, l'encodage intelligent peut réduire son nombre de segments. Définissez options.smart_encoding sur true : Bird remplace alors les guillemets courbes, les tirets, les points de suspension et les caractères similaires par leurs équivalents GSM-7 avant l'envoi. Cette option est désactivée par défaut, car elle modifie le corps que vous avez rédigé.
Pour le jeu de caractères complet, les caractères de la table d'extension qui occupent deux emplacements, la taille des emoji, les remplacements effectués par l'encodage intelligent et le calcul des segments, consultez Limites de caractères.
Envoi par lot
POST /v1/sms/batches envoie jusqu'à 100 messages indépendants en une seule requête, selon le modèle d'envoi en masse SMS : un seul appel API pour de nombreux destinataires. Les requêtes par lot utilisent la politique de limitation du débit sms_batch, distincte de la politique sms_send des envois unitaires. Le corps est un objet JSON dont le tableau messages contient les objets message décrits dans Construire la charge utile :
const result = await bird.sms.sendBatch({
messages: [
{
from: "+15557654321",
to: "+15551111111",
text: "Hi Alice!",
category: "marketing",
},
{
from: "+15557654321",
to: "+15552222222",
text: "Hi Bob!",
category: "marketing",
},
],
});batch = client.sms.send_batch(
messages=[
{
"from_": "+15557654321",
"to": "+15551111111",
"text": "Hi Alice!",
"category": "marketing",
},
{
"from_": "+15557654321",
"to": "+15552222222",
"text": "Hi Bob!",
"category": "marketing",
},
]
)
for msg in batch.data:
print(msg.id, msg.status)batch, err := client.Sms.SendBatch(context.Background(), bird.SmsSendBatchParams{
Messages: []bird.SmsSendParams{
{
From: "+15557654321", To: "+15551111111",
Text: "Hi Alice!", Category: bird.SMSCategoryMarketing,
},
{
From: "+15557654321", To: "+15552222222",
Text: "Hi Bob!", Category: bird.SMSCategoryMarketing,
},
},
})
if err != nil {
log.Fatal(err)
}
for _, msg := range batch.Data {
fmt.Println(msg.Id, *msg.Status)
}$batch = $bird->sms->sendBatch(messages: [
(new SMSMessageSendRequest())
->setFrom('+15557654321')
->setTo('+15551111111')
->setText('Hi Alice!')
->setCategory('marketing'),
(new SMSMessageSendRequest())
->setFrom('+15557654321')
->setTo('+15552222222')
->setText('Hi Bob!')
->setCategory('marketing'),
]);
foreach ($batch->getData() ?? [] as $item) {
echo $item->getId(), ' ', $item->getStatus(), "\n";
}curl -X POST "https://{region}.platform.bird.com/v1/sms/batches" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "+15551111111",
"from": "+15557654321",
"text": "Hi Alice!",
"category": "marketing"
},
{
"to": "+15552222222",
"from": "+15557654321",
"text": "Hi Bob!",
"category": "marketing"
}
]
}'La validation fonctionne en tout ou rien : si un seul message du lot est invalide, toute la requête est rejetée avec une 422 et rien n'est envoyé. Un lot n'est donc jamais appliqué partiellement. En cas de succès, la réponse 202 contient chaque message accepté, dans l'ordre de soumission, sous data, ainsi qu'un summary avec le accepted_count. Chaque message est ensuite indépendant : l'échec d'un destinataire n'affecte jamais les autres.
Le modèle asynchrone : ce que signifie 202
Un envoi réussi renvoie 202 Accepted avec un identifiant de message et status: accepted. Les échecs de requête sont renvoyés immédiatement : un champ invalide, un corps qui dépasse la limite de segments, un pays de destination que vous n'avez pas activé ou un expéditeur invalide renvoient une 422. Un espace de travail sans solde dans son portefeuille reçoit une 402.
La remise se fait de manière asynchrone. Le message passe à sent lorsque Bird le transmet à l'opérateur. Un accusé de remise définit ensuite delivered, undelivered, failed ou expired, visible via les événements et webhooks et les endpoints de lecture. Ce fonctionnement a trois conséquences :
- Le coût est calculé après l'acceptation. Le champ
costd'un message vautnullau moment de l'acceptation, puis il est renseigné lorsque Bird calcule le prix de l'envoi pendant le traitement. Relisez le message ou attendez l'événement de remise pour voir le montant calculé jusqu'ici ; la page coût et facturation décrit les composantes et les cas où l'une d'elles reste sans prix. - Un message peut être rejeté après le 202. Si le débit échoue pendant le traitement, le message se termine en
rejectedavec un webhooksms.rejectedet vous n'êtes pas facturé ; un portefeuille épuisé apparaît sous la formelast_error.code: insufficient_balance. - Les lectures peuvent brièvement être en retard sur le 202. Le message apparaît sur les endpoints de lecture peu après le
202. Un404obtenu juste après un envoi se résout donc en quelques instants.
Champs réservés
Bird rejette les champs de requête suivants avec 422 SMSUnsupportedFeature :
scheduled_at, media_urls, broadcast_id, campaign_id, audience_id, contact_id, topic_id, personalization, options.max_price_per_segment, options.track_clicks (lorsque true)
N’incluez pas ces champs dans un envoi.
Réessayer en toute sécurité
Envoyez l’en-tête Idempotency-Key avec une valeur unique pour chaque envoi logique. Si une requête aboutit sans renvoyer de réponse, rejouez la même requête avec la même clé. Bird renvoie le résultat d’origine au lieu d’envoyer un message en double. Consultez idempotence pour le format des clés et leur durée de conservation.
Coût et facturation
Les SMS sortants sont facturés par segment. Le montant dépend du pays de destination et de l’opérateur. Certaines routes ajoutent une surtaxe tierce, comme les frais d’opérateur 10DLC aux États-Unis.
Le champ cost d’un message ventile le coût en composantes nommées. transaction_amount correspond au montant facturé par Bird pour acheminer le message, passthrough_amount correspond aux frais tiers répercutés, et amount est la somme des composantes déjà tarifées, exprimée en currency_code. Une composante non tarifée vaut null et non "0.00000" : un message dont la surtaxe n’a jamais été déterminée indique donc dans amount le seul coût d’acheminement. La référence des messages documente chaque champ.
La surtaxe est déterminée au mieux. Bird la calcule lors de l’enregistrement de l’accusé de réception, dans un délai limité. Si elle n’est pas déterminée dans ce délai, passthrough_amount reste définitivement à null : Bird ne fait pas de nouvelle tentative, et amount reste égal au coût d’acheminement.
Les SMS entrants sont facturés sur deux lignes : le tarif entrant par segment et, le cas échéant, une surtaxe d’opérateur entrante. Les deux figurent dans le champ cost du message reçu : le tarif sous transaction_amount, la surtaxe sous passthrough_amount. Contrairement à la surtaxe sortante, la surtaxe entrante est tarifée à l’acceptation du message et non à la remise : elle n’est donc jamais renseignée plus tard.
Consultez le coût et les segments de chaque message dans le journal SMS.
Étapes suivantes
- Modèles SMS : envoyez un modèle intégré et laissez Bird choisir l’expéditeur.
- Journal SMS : retrouvez un message et examinez son cycle de vie, ses segments et son coût.
- Événements : recevez les événements de remise dans vos systèmes.
- URL de rappel SMS : envoyez les événements d'un message ou d'un profil vers leur propre point de terminaison HTTPS.
- Métriques SMS : surveillez le taux de remise, le taux d'échec et le volume accepté.
- Idempotence : réessayez sans risque avec l'en-tête
Idempotency-Key. - Envoyer votre premier SMS : une vidéo qui présente la même configuration dans le tableau de bord
Ressources associées
Poursuivez avec la documentation, les guides et les exemples sur ce sujet.