Bulk-SMS-API v1
Eine kleine REST-API zum Versenden von Massen-SMS aus Ihrer eigenen Software: JSON ein- und ausgehend, Bearer-Schlüssel, die gleichen Preise und Prüfungen wie in der Konsole und signierte Webhooks für Zustellberichte. Basis-URL https://smsbulk.cc/rest/v1
Übersicht
Senden Sie bis zu 8,000 Nummern pro Anfrage, jede mit ihren eigenen Merge-Werten, und die API berechnet jeden Empfänger mit seinem eigenen zusammengeführten Text, genau wie die Konsole. Fordern Sie zuerst einen Kostenvoranschlag mit demselben Text an, um die Kosten zu kennen; durch einen Kostenvoranschlag wird nichts gesendet oder berechnet. Zeiten sind ISO 8601 in UTC, Geldbeträge sind Zeichenketten in US-Dollar mit fünf Dezimalstellen.
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;Authentifizierung
Erstellen Sie einen Schlüssel in der Konsole unter API und Webhooks; Schlüssel werden nach Ihrer ersten gutgeschriebenen Aufladung freigeschaltet. Senden Sie ihn bei jeder Anfrage als Bearer-Token. Ein Schlüssel wird einmal angezeigt; Sie können bis zu 5 Schlüssel behalten und jeden davon sofort widerrufen.
Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPreise
Öffentlich, kein Schlüssel erforderlich. Der Preis pro SMS-Segment für jedes Ziel oder für einen ISO-Code.
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"
}Guthaben
GEThttps://smsbulk.cc/rest/v1/credit
{ "credit": "125.40000", "currency": "USD", "account": "A418820037715" }Kostenvoranschlag
Berechnet den Versand, ohne ihn zu senden. Verwendet denselben Text wie Senden.
POSThttps://smsbulk.cc/rest/v1/quote
| Feld | Typ | Bedeutung |
|---|---|---|
| to | string or string[] | Eine Nummer oder eine Liste. Die internationale Form mit + ist am sichersten; lokale Nummern benötigen default_country. |
| recipients | object[] | Anstelle von to: eine Liste von { to, fields }, wobei fields Merge-Felder wie {{name}} ausfüllen. |
| text | string | Die Nachricht, bis zu 2,000 Zeichen und 10 Segmenten nach dem Zusammenführen. |
| default_country | string | ISO-Code für Nummern, die ohne + geschrieben sind, wie GB. Greift auf den in den Einstellungen gespeicherten Standard zurück. |
| opt_out | boolean | Fügt Ihren gespeicherten Abmeldehinweis als letzte Zeile hinzu (speichern Sie ihn zuerst in den Einstellungen). |
{
"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": []
}Senden
Erstellt eine Kampagne, zieht den Gesamtbetrag von Ihrem Guthaben ab und reiht die Nachrichten ein. Antwortet mit 201 und der Kampagne. Nummern, die nicht gesendet werden können, werden in left_out aufgeführt und nie berechnet.
POSThttps://smsbulk.cc/rest/v1/send
| Feld | Typ | Bedeutung |
|---|---|---|
| to / recipients / text / default_country / opt_out | Wie in Quote. | |
| send_at | string | ISO 8601-Zeit zum Starten, 5 Minuten bis 90 Tage im Voraus. Lassen Sie es weg, um jetzt zu senden. |
| name | string | Ihr Label für die Kampagne, bis zu 80 Zeichen. |
| client_ref | string | Ihr eigener Verweis, bis zu 64 Zeichen, wird bei jeder Nachricht und jedem Webhook zurückgegeben. |
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": []
}Kampagnen
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}
Eine einzelne Kampagne enthält Zählungen pro Status: eingereiht, gesendet, zugestellt, fehlgeschlagen und abgebrochen. Nachrichtenlisten-Seite mit after und has_more; bis zu 1.000 pro Seite.
Nachrichten
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"
}Eine geplante Kampagne abbrechen
POSThttps://smsbulk.cc/rest/v1/campaigns/{id}/cancel
Funktioniert bis zum Startzeitpunkt. Die gesamten Kosten werden in einer Buchungszeile auf Ihr Guthaben zurückerstattet und der Kampagnenstatus wird auf abgebrochen gesetzt.
Webhooks
Setzen Sie eine Endpunkt-URL in der Konsole. Wir POSTEN JSON für message.delivered, message.failed und campaign.done, plus Test, wenn Sie Testereignis senden drücken. Jedes Ereignis trägt vier Header, und jede 2xx-Antwort innerhalb von 10 Sekunden gilt als empfangen.
| Feld | Typ | Bedeutung |
|---|---|---|
| Smsbulk-Event | Header | Ereignisname, wie message.delivered. |
| Smsbulk-Delivery | Header | Lieferungs-ID, die bei jedem erneuten Versuch eines Ereignisses gleich ist. |
| Smsbulk-Timestamp | Header | Unix-Sekunden, als dieser Versuch signiert wurde. |
| Smsbulk-Signature | Header | sha256= gefolgt von der base64 HMAC-SHA256 von Zeitstempel, einer neuen Zeile und dem rohen Body, mit Ihrem sbw_ Geheimnis verschlüsselt. |
Die Zustellung wird 8 Mal versucht: sofort, dann nach 1 Minute, 5 Minuten, 15 Minuten, 1 Stunde, 3 Stunden, 6 Stunden und 12 Stunden. Jeder Versuch wird im Zustellungsprotokoll der Konsole mit seinem HTTP-Status und dem Anfang Ihrer Antwort angezeigt.
{
"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"
}
}Signatur überprüfen
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", ""))Fehler und Limits
Fehler verwenden den HTTP-Status und einen JSON-Body: {"error":{"code":"insufficient_credit","message":"..."}}. Es wird nichts berechnet, wenn eine Anfrage fehlschlägt.
| Feld | Typ | Bedeutung |
|---|---|---|
| 400 bad_json | Der Body ist kein JSON. | |
| 401 unauthorized | Fehlender, fehlerhafter oder widerrufener Schlüssel. | |
| 402 insufficient_credit | Der Gesamtbetrag übersteigt Ihr Guthaben. Laden Sie Guthaben in der Konsole auf. | |
| 404 not_found | Es gibt keinen solchen Endpunkt, keine Kampagne, keine Nachricht oder keinen ISO-Code in diesem Konto. | |
| 409 not_scheduled | Es wurde eine Stornierung für eine Kampagne angefordert, die bereits gestartet ist. | |
| 422 text_missing, text_too_long, too_many_segments | Nachricht leer, über 2,000 Zeichen oder über 10 Segmente für einen Empfänger. | |
| 422 no_recipients, too_many, recipients_missing | Keine sendbare Nummer, mehr als 8,000 Nummern oder weder an noch Empfänger. | |
| 422 schedule_range, bad_send_at | send_at außerhalb von 5 Minuten bis 90 Tagen oder keine gültige Zeit. | |
| 422 opt_out_unset | opt_out ist wahr, aber es ist kein Text in den Einstellungen gespeichert. | |
| 429 rate_limited | Mehr als 300 Anfragen pro Minute für diesen Schlüssel. |
Jede Antwort enthält X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset.
Ausgelassene Codes
Nummern, die vor der Preisberechnung entfernt wurden, werden in left_out mit ihrer Position in Ihrer Liste, dem gesendeten Wert, einem Code und dem Grund zurückgegeben.
| Feld | Typ | Bedeutung |
|---|---|---|
| no_country_code | Kein + und kein Standardland. | |
| too_short / too_long | Weniger als 8 oder mehr als 15 Ziffern. | |
| not_a_number | Buchstaben oder Symbole, die nicht Teil einer Nummer sind. | |
| nanp_length | Eine +1 Nummer ohne elf Ziffern. | |
| not_covered | Ein Ziel außerhalb unserer Liste. | |
| duplicate | Die gleiche Nummer erschien früher in der Anfrage. | |
| suppressed | Die Nummer steht auf Ihrer Nicht-Senden-Liste. |
Status und Gründe
| Feld | Typ | Bedeutung |
|---|---|---|
| queued | Status | Warten darauf, unsere Warteschlange zu verlassen. |
| sent | Status | An das Netzwerk übergeben, warten auf den Zustellbericht. Die meisten Berichte kommen innerhalb von Minuten an; einige Netzwerke benötigen bis zu 24 Stunden. |
| delivered | Status | Das Netzwerk hat bestätigt, dass das Telefon es erhalten hat. |
| failed | Status | Nicht zugestellt. Der Betrag wurde auf Ihr Guthaben zurückerstattet. |
| canceled | Status | Nicht gesendet, weil Sie storniert haben. Rückerstattet. |
| Feld | Typ | Bedeutung |
|---|---|---|
| network_error | Grund | Netzwerkfehler |
| switched_off | Grund | Telefon ausgeschaltet oder außerhalb der Reichweite |
| not_in_service | Grund | Nummer nicht im Dienst |
| blocked | Grund | Vom Netzwerk oder Empfänger blockiert |
| landline | Grund | Festnetznummer oder Nummer, die keine SMS empfangen kann |
| content_filter | Grund | Vom Inhaltsfilter des Netzwerks abgelehnt |
| expired | Grund | Abgelaufen: Kein Bericht vor der Zeitbegrenzung des Netzwerks |
| unknown | Grund | Unbekannte Netzwerkantwort |
| canceled | Grund | Vor dem Senden abgebrochen |
Fragen zur API? Öffnen Sie ein Ticket in der Konsole oder lesen Sie die FAQ.