API SMS bulk v1
Una piccola API REST per l'invio di SMS in blocco dal tuo software: JSON in entrata e in uscita, chiavi Bearer, gli stessi prezzi e controlli della console e webhook firmati per i report di consegna. URL di base https://smsbulk.cc/rest/v1
Panoramica
Invia fino a 8,000 numeri per richiesta, ciascuno con i propri valori di unione, e l'API calcola il prezzo per ogni destinatario con il proprio testo unito, esattamente come nella console. Richiedi prima un preventivo con lo stesso corpo per conoscere il costo; nulla viene inviato o addebitato con un preventivo. Gli orari sono in formato ISO 8601 in UTC, il denaro è una stringa in dollari statunitensi con cinque decimali.
curl https://smsbulk.cc/rest/v1/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipients": [
{ "to": "+14155550142", "fields": { "name": "Maria" } },
{ "to": "+447400123456", "fields": { "name": "Tom" } }
],
"text": "Hi {{name}}, your order ships today.",
"client_ref": "order-batch-17"
}'const res = await fetch("https://smsbulk.cc/rest/v1/send", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
recipients: [
{ to: "+14155550142", fields: { name: "Maria" } },
{ to: "+447400123456", fields: { name: "Tom" } },
],
text: "Hi {{name}}, your order ships today.",
client_ref: "order-batch-17",
}),
});
const data = await res.json();
console.log(res.status, data.campaign?.id, data.campaign?.cost);import requests
res = requests.post(
"https://smsbulk.cc/rest/v1/send",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"recipients": [
{"to": "+14155550142", "fields": {"name": "Maria"}},
{"to": "+447400123456", "fields": {"name": "Tom"}},
],
"text": "Hi {{name}}, your order ships today.",
"client_ref": "order-batch-17",
},
timeout=30,
)
data = res.json()
print(res.status_code, data.get("campaign", {}).get("id"))<?php
$body = json_encode([
"recipients" => [
["to" => "+14155550142", "fields" => ["name" => "Maria"]],
["to" => "+447400123456", "fields" => ["name" => "Tom"]],
],
"text" => "Hi {{name}}, your order ships today.",
"client_ref" => "order-batch-17",
]);
$ch = curl_init("https://smsbulk.cc/rest/v1/send");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_API_KEY", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => $body,
]);
$data = json_decode(curl_exec($ch), true);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $data["campaign"]["id"] ?? "", PHP_EOL;Autenticazione
Crea una chiave nella console sotto API e webhook; le chiavi si attivano dopo il primo accredito. Inviarla in ogni richiesta come token Bearer. Una chiave viene mostrata una sola volta; puoi conservare fino a 5 chiavi e revocarne qualsiasi in qualsiasi momento.
Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTariffe
Pubblico, nessuna chiave necessaria. Il prezzo per segmento SMS per ogni destinazione, o per un codice ISO.
GEThttps://smsbulk.cc/rest/v1/rates
GEThttps://smsbulk.cc/rest/v1/rates/{ISO}
{
"country": "US",
"name": "United States",
"dial_code": "+1",
"rate": "0.00485",
"currency": "USD",
"per": "message segment"
}Credito
GEThttps://smsbulk.cc/rest/v1/credit
{ "credit": "125.40000", "currency": "USD", "account": "A418820037715" }Preventivo
Calcola il costo di un invio senza inviarlo. Accetta lo stesso corpo di Send.
POSThttps://smsbulk.cc/rest/v1/quote
| Campo | Tipo | Significato |
|---|---|---|
| to | string or string[] | Un numero o una lista. La forma internazionale con + è la più sicura; i numeri locali necessitano di default_country. |
| recipients | object[] | Invece di to: una lista di { to, fields } dove fields riempie i campi di unione come {{name}}. |
| text | string | Il messaggio, fino a 2,000 caratteri e 10 segmenti dopo l'unione. |
| default_country | string | Codice ISO utilizzato per i numeri scritti senza +, come GB. Ripiega sul valore predefinito salvato nelle Impostazioni. |
| opt_out | boolean | Aggiunge la tua frase di opt-out salvata come ultima riga (salvala prima nelle Impostazioni). |
{
"recipients": 2,
"messages": 2,
"segments": { "min": 1, "max": 1 },
"encoding": "GSM-7",
"total": "0.01240",
"currency": "USD",
"credit": "125.40000",
"credit_after": "125.38760",
"countries": [
{ "country": "US", "recipients": 1, "rate": "0.00485", "segments": 1, "subtotal": "0.00485" },
{ "country": "GB", "recipients": 1, "rate": "0.00755", "segments": 1, "subtotal": "0.00755" }
],
"left_out": []
}Invia
Crea una campagna, prende il totale dal tuo credito e mette in coda i messaggi. Risponde con 201 e la campagna. I numeri che non possono essere inviati sono elencati in left_out e non vengono mai addebitati.
POSThttps://smsbulk.cc/rest/v1/send
| Campo | Tipo | Significato |
|---|---|---|
| to / recipients / text / default_country / opt_out | Come in Quote. | |
| send_at | string | Ora ISO 8601 per iniziare, da 5 minuti a 90 giorni in anticipo. Ometti per inviare ora. |
| name | string | La tua etichetta per la campagna, fino a 80 caratteri. |
| client_ref | string | Il tuo riferimento personale, fino a 64 caratteri, restituito in ogni messaggio e webhook. |
HTTP/1.1 201 Created
{
"campaign": {
"id": "C507318264409",
"name": null,
"status": "sending",
"source": "api",
"test": false,
"recipients": 2,
"cost": "0.01240",
"refunded": "0.00000",
"net_cost": "0.01240",
"currency": "USD",
"send_at": null,
"created_at": "2026-11-07T14:02:11.000Z",
"started_at": "2026-11-07T14:02:11.000Z",
"finished_at": null,
"client_ref": "order-batch-17"
},
"messages": 2,
"left_out": []
}Campagne
GEThttps://smsbulk.cc/rest/v1/campaigns?limit=25&before={id}
GEThttps://smsbulk.cc/rest/v1/campaigns/{id}
GEThttps://smsbulk.cc/rest/v1/campaigns/{id}/messages?status=failed&limit=100&after={message id}
Una singola campagna include i conteggi per stato: in coda, inviato, consegnato, fallito e annullato. Elenco messaggi con paginazione dopo e has_more; fino a 1.000 per pagina.
Messaggi
GEThttps://smsbulk.cc/rest/v1/messages/{id}
GEThttps://smsbulk.cc/rest/v1/messages?to=%2B14155550142&status=delivered&client_ref=order-batch-17&limit=50
{
"id": "M220947781305",
"campaign": "C507318264409",
"to": "+14155550142",
"country": "US",
"segments": 1,
"encoding": "GSM-7",
"price": "0.00485",
"status": "delivered",
"reason": null,
"reason_text": null,
"refunded": false,
"client_ref": "order-batch-17",
"created_at": "2026-11-07T14:02:11.000Z",
"sent_at": "2026-11-07T14:02:12.000Z",
"finished_at": "2026-11-07T14:02:16.000Z"
}Annulla una campagna programmata
POSThttps://smsbulk.cc/rest/v1/campaigns/{id}/cancel
Funziona fino all'orario di inizio. L'intero costo viene restituito al tuo credito in una riga del registro e lo stato della campagna diventa annullato.
Webhook
Imposta un URL endpoint nella console. Inviamo JSON per message.delivered, message.failed e campaign.done, più un test quando premi Invia evento di test. Ogni evento porta quattro intestazioni e qualsiasi risposta 2xx entro 10 secondi viene considerata ricevuta.
| Campo | Tipo | Significato |
|---|---|---|
| Smsbulk-Event | intestazione | Nome dell'evento, come message.delivered. |
| Smsbulk-Delivery | intestazione | ID di consegna, lo stesso per ogni tentativo di un evento. |
| Smsbulk-Timestamp | intestazione | Secondi Unix quando questo tentativo è stato firmato. |
| Smsbulk-Signature | intestazione | sha256= seguito dal base64 HMAC-SHA256 di timestamp, una nuova riga e il corpo grezzo, chiavato con il tuo segreto sbw_. |
La consegna viene tentata 8 volte: subito, poi dopo 1 minuto, 5 minuti, 15 minuti, 1 ora, 3 ore, 6 ore e 12 ore. Ogni tentativo viene mostrato nel registro di consegna della console con il suo stato HTTP e l'inizio della tua risposta.
{
"id": "dlv_3f9a1c07b2e44d58a6c1",
"event": "message.failed",
"created": "2026-11-07T14:02:16.000Z",
"data": {
"message": "M220947781305",
"campaign": "C507318264409",
"to": "+447400123456",
"country": "GB",
"status": "failed",
"reason": "not_in_service",
"segments": 1,
"price": "0.00755",
"client_ref": "order-batch-17"
}
}Verifica la firma
import crypto from "node:crypto";
// raw = the request body exactly as received (string), secret = your sbw_ signing secret
export function verify(raw, headers, secret) {
const ts = headers["smsbulk-timestamp"];
const want = "sha256=" + crypto.createHmac("sha256", secret).update(ts + "\n" + raw).digest("base64");
const got = headers["smsbulk-signature"] || "";
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
return fresh && got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want));
}import base64, hashlib, hmac, time
def verify(raw: bytes, headers, secret: str) -> bool:
ts = headers.get("Smsbulk-Timestamp", "")
mac = hmac.new(secret.encode(), ts.encode() + b"\n" + raw, hashlib.sha256).digest()
want = "sha256=" + base64.b64encode(mac).decode()
fresh = abs(time.time() - int(ts or 0)) < 300
return fresh and hmac.compare_digest(want, headers.get("Smsbulk-Signature", ""))Errori e limiti
Gli errori utilizzano lo stato HTTP e un corpo JSON: {"error":{"code":"insufficient_credit","message":"..."}}. Non viene addebitato nulla quando una richiesta fallisce.
| Campo | Tipo | Significato |
|---|---|---|
| 400 bad_json | Il corpo non è JSON. | |
| 401 unauthorized | Chiave mancante, malformata o revocata. | |
| 402 insufficient_credit | L'importo totale supera il tuo credito. Aggiungi fondi nella console. | |
| 404 not_found | Nessun endpoint, campagna, messaggio o codice ISO trovato su questo account. | |
| 409 not_scheduled | È stata richiesta la cancellazione di una campagna già avviata. | |
| 422 text_missing, text_too_long, too_many_segments | Messaggio vuoto, supera 2,000 caratteri o supera 10 segmenti per un destinatario. | |
| 422 no_recipients, too_many, recipients_missing | Nessun numero inviabile, più di 8,000 numeri, o nessun destinatario specificato. | |
| 422 schedule_range, bad_send_at | send_at fuori dall'intervallo di 5 minuti a 90 giorni, o non è un orario valido. | |
| 422 opt_out_unset | opt_out è vero ma non è stata salvata alcuna dicitura nelle Impostazioni. | |
| 429 rate_limited | Più di 300 richieste in un minuto per questa chiave. |
Ogni risposta include X-RateLimit-Limit, X-RateLimit-Remaining e X-RateLimit-Reset.
Codici esclusi
I numeri rimossi prima della determinazione del prezzo vengono restituiti in left_out con la loro posizione nella tua lista, il valore inviato, un codice e la ragione chiara.
| Campo | Tipo | Significato |
|---|---|---|
| no_country_code | Nessun prefisso + e nessun paese predefinito. | |
| too_short / too_long | Meno di 8 o più di 15 cifre. | |
| not_a_number | Lettere o simboli che non fanno parte di un numero. | |
| nanp_length | Un numero +1 senza undici cifre. | |
| not_covered | Una destinazione fuori dalla nostra lista. | |
| duplicate | Lo stesso numero è apparso precedentemente nella richiesta. | |
| suppressed | Il numero è nella tua lista di non invio. |
Stati e motivi
| Campo | Tipo | Significato |
|---|---|---|
| queued | stato | In attesa di lasciare la nostra coda. |
| sent | stato | Consegnato alla rete, in attesa del rapporto di consegna. La maggior parte dei rapporti arriva entro pochi minuti; alcune reti richiedono fino a 24 ore. |
| delivered | stato | La rete ha confermato che il telefono lo ha ricevuto. |
| failed | stato | Non consegnato. Il prezzo è stato rimborsato sul tuo credito. |
| canceled | stato | Non inviato perché hai annullato. Rimborsato. |
| Campo | Tipo | Significato |
|---|---|---|
| network_error | motivo | Errore di rete |
| switched_off | motivo | Telefono spento o fuori copertura |
| not_in_service | motivo | Numero non in servizio |
| blocked | motivo | Bloccato dalla rete o dal destinatario |
| landline | motivo | Numero fisso o numero che non può ricevere SMS |
| content_filter | motivo | Respinto dal filtro contenuti della rete |
| expired | motivo | Scaduto: nessun report prima del limite di tempo della rete |
| unknown | motivo | Risposta della rete sconosciuta |
| canceled | motivo | Annullato prima dell'invio |
Domande sull'API? Apri un ticket nella console o leggi le FAQ.