Démarrage rapide
- Créez un compte et rechargez votre solde dans la console.
- Ouvrez Clés API et créez une clé. Les clés peuvent être créées après le premier rechargement crédité. La clé n'est affichée qu'une fois ; conservez-la comme un secret.
- Envoyez une requête POST à
/smsavec les numéros et le texte. Lisez l'ID de campagne retourné, puis suivez la livraison dans la console ou via les webhooks.
Authentification
Chaque requête, sauf la liste publique des tarifs, contient votre clé dans l'en-tête Authorization. Les clés commencent par sbk_live_. Une nouvelle clé remplace immédiatement l'ancienne.
Authorization: Bearer sbk_live_YOUR_KEYEnvoyer un SMS
POST/sms
Crée une campagne, la facture depuis le solde et met chaque message en file d'attente. Les numéros sont nettoyés et dédoublonnés ; ceux qui ne peuvent pas être utilisés sont retournés dans dropped avec une raison et ne sont pas facturés.
| Champ | Type | Description |
|---|---|---|
toobligatoire | chaîne ou tableau | Un numéro ou un tableau de jusqu'à 10,000 numéros au format international, par exemple "+447700900461" |
textobligatoire | chaîne | Texte du message, jusqu'à 10 segments. Le texte GSM-7 tient dans 160 caractères par segment, UCS-2 dans 70 |
schedule_atoptionnel | chaîne | Heure ISO 8601 de 5 minutes à 60 jours à l'avance. Omettez pour envoyer maintenant |
client_refoptionnel | chaîne | Votre propre référence, jusqu'à 64 caractères parmi a-z A-Z 0-9 _ . : -, retournée dans les rapports et webhooks |
opt_outoptionnel | booléen | true ajoute la mention de désinscription définie dans vos paramètres de compte |
curl -X POST https://sendbulksms.cc/api/v1/sms \
-H "Authorization: Bearer sbk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["+447700900461", "+5511955550123"],
"text": "Hi, your order 4821 ships today.",
"client_ref": "orders-4821"
}'const res = await fetch("https://sendbulksms.cc/api/v1/sms", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.SENDBULKSMS_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ to: ["+447700900461", "+5511955550123"], text: "Hi, your order 4821 ships today.", client_ref: "orders-4821" }),
});
const campaign = await res.json();
console.log(res.status, campaign.id, campaign.cost);import os, requests
r = requests.post(
"https://sendbulksms.cc/api/v1/sms",
headers={"Authorization": "Bearer " + os.environ["SENDBULKSMS_KEY"]},
json={"to": ["+447700900461", "+5511955550123"], "text": "Hi, your order 4821 ships today.", "client_ref": "orders-4821"},
timeout=30,
)
print(r.status_code, r.json()["id"])<?php
$ch = curl_init("https://sendbulksms.cc/api/v1/sms");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SENDBULKSMS_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["to" => ["+447700900461", "+5511955550123"], "text" => "Hi, your order 4821 ships today.", "client_ref" => "orders-4821"]),
]);
$campaign = json_decode(curl_exec($ch), true);
echo $campaign["id"];Réponse : la campagne avec son coût et les 1 000 premiers messages. Récupérez plus avec les messages de campagne.
HTTP/1.1 201 Created
{
"id": "CP-7KQ2-M9XA-4HTD",
"status": "sending",
"source": "api",
"recipients": 2,
"segments": 1,
"encoding": "GSM-7",
"cost": "0.01078",
"refunded": "0.00000",
"client_ref": "orders-4821",
"scheduled_at": null,
"created_at": "2026-11-07T10:32:04.118Z",
"done_at": null,
"counts": { "queued": 2, "sent": 0, "delivered": 0, "failed": 0, "canceled": 0 },
"dropped": [],
"balance": "248.30422",
"messages": [
{ "id": "SM-2RBW-8MZN-QF6K", "campaign_id": "CP-7KQ2-M9XA-4HTD", "to": "+447700900461", "country": "GB", "parts": 1, "price": "0.00819", "status": "queued", "error": null, "created_at": "2026-11-07T10:32:04.118Z", "sent_at": null, "done_at": null }
]
}Estimer un envoi
POST/estimate
Prend le même corps que /sms et retourne les destinataires, segments, encodage, coût total et le coût par pays sans facturer ni envoyer quoi que ce soit.
{ "recipients": 2, "dropped": [], "segments": 1, "encoding": "GSM-7", "cost": "0.01078", "balance": "248.31500",
"countries": [ { "country": "GB", "recipients": 1, "cost": "0.00819" }, { "country": "BR", "recipients": 1, "cost": "0.00259" } ] }Statut du message
GET/sms/{message_id}
Retourne un message : numéro, pays, segments, prix, statut, l'erreur pour les messages échoués et l'heure de chaque étape.
Campagnes
GET/campaigns/{campaign_id}
La campagne avec son coût, remboursements et un décompte des messages par statut.
GET/campaigns/{campaign_id}/messages?offset=0&limit=100
Messages dans l'ordre où ils ont été mis en file d'attente, jusqu'à 1 000 par page.
POST/campaigns/{campaign_id}/cancel
Annule une campagne programmée qui n'a pas commencé et rembourse son coût intégral au solde.
Solde et tarifs
GET/balance
{ "balance": "248.31500", "currency": "USD" }GET/rates
Public, aucune clé nécessaire. Chaque pays avec son indicatif et prix par segment. /rates/GB retourne un pays.
{ "updated": "2026-11-07", "rates": [ { "country": "GB", "name": "United Kingdom", "calling_code": "+44", "price": "0.00819", "currency": "USD", "unit": "SMS segment" } ] }Webhooks de livraison
Définissez une URL de webhook dans la console sous Clés API. Quand un message atteint un statut final (livré ou échoué), nous POSTons un rapport dessus. Répondez avec n'importe quel statut 2xx dans les 8 secondes ; sinon l'appel est réessayé jusqu'à 8 fois sur environ 11 heures. Utilisez id pour ignorer un rapport déjà traité.
POST https://your-server.example/sms-status
Content-Type: application/json
X-SendBulkSMS-Signature: t=1794047524,v1=5f0c3b9e...
{
"event": "message.status",
"id": "evt_8c1d2e4f6a7b9c0d1e2f",
"created_at": "2026-11-07T10:32:09.004Z",
"data": {
"message_id": "SM-2RBW-8MZN-QF6K",
"campaign_id": "CP-7KQ2-M9XA-4HTD",
"to": "+447700900461",
"country": "GB",
"status": "delivered",
"error": null,
"parts": 1,
"price": "0.00819",
"client_ref": "orders-4821",
"done_at": "2026-11-07T10:32:08.912Z"
}
}Vérifiez la signature avant de faire confiance à un rapport : HMAC-SHA256 de t + "." + raw body avec votre secret de webhook, comparé en temps constant, et rejetez les temps de plus de 5 minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
// raw = the request body exactly as received (string), header = X-SendBulkSMS-Signature
export function verify(raw, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const want = Buffer.from(createHmac("sha256", secret).update(m[1] + "." + raw).digest("hex"));
const got = Buffer.from(m[2]);
return want.length === got.length && timingSafeEqual(want, got);
}import hmac, hashlib, re, time
def verify(raw: bytes, header: str, secret: str) -> bool:
m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
if not m or abs(time.time() - int(m.group(1))) > 300:
return False
want = hmac.new(secret.encode(), m.group(1).encode() + b"." + raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, m.group(2))<?php
function verify(string $raw, string $header, string $secret): bool {
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header, $m) || abs(time() - (int)$m[1]) > 300) return false;
$want = hash_hmac('sha256', $m[1] . '.' . $raw, $secret);
return hash_equals($want, $m[2]);
}Statuts
| Statut | Signification |
|---|---|
queued | Accepté et facturé, en attente dans la file |
sent | Transmis à la route, en attente du rapport de remise |
delivered | Le réseau a confirmé la livraison au terminal |
failed | Non livré ; le montant est remboursé et error contient la raison |
canceled | Partie d'une campagne planifiée annulée avant son début ; remboursé |
Erreurs
Les erreurs utilisent des codes de statut HTTP et un corps JSON : { "error": { "code": "insufficient_balance", "message": "..." } }. Une requête renvoyant une erreur ne facture jamais le solde.
| HTTP | Code | Quand |
|---|---|---|
| 400 | bad_requestUn champ est manquant ou invalide ; le message indique lequel | Un champ est manquant ou invalide ; le message indique lequel |
| 400 | no_valid_numbersAucun numéro dans "to" n'a pu être utilisé | Aucun numéro dans "to" n'a pu être utilisé |
| 401 | unauthorizedLa clé API est manquante ou invalide | La clé API est manquante ou invalide |
| 402 | insufficient_balanceLe solde ne couvre pas l'envoi ; rien n'a été facturé | Le solde ne couvre pas l'envoi ; rien n'a été facturé |
| 404 | not_foundLa campagne, le message ou le point de terminaison n'existe pas dans votre compte | La campagne, le message ou le point de terminaison n'existe pas dans votre compte |
| 409 | not_scheduledSeule une campagne planifiée non démarrée peut être annulée | Seule une campagne planifiée non démarrée peut être annulée |
| 413 | too_many_recipientsPlus de 10,000 numéros dans une requête | Plus de 10,000 numéros dans une requête |
| 429 | rate_limitedPlus de 300 requêtes par minute pour cette clé | Plus de 300 requêtes par minute pour cette clé |
| 500 | server_errorRéessayez ; aucune facturation n'est effectuée pour une requête échouée | Réessayez ; aucune facturation n'est effectuée pour une requête échouée |
Limites
- 300 requêtes par minute par clé API.
- 10,000 numéros par requête d'envoi ; 10 segments par message.
- Les messages partent à raison de 200 par minute maximum par file, dans l'ordre d'acceptation.
- Planification de 5 minutes à 60 jours à l'avance.
- Les numéros de votre liste d'exclusion sont ignorés et renvoyés dans dropped avec le code suppressed ; ils ne sont pas facturés.