API SMS Pukal v1
API REST kecil untuk menghantar SMS pukal daripada perisian anda sendiri: JSON masuk dan keluar, kunci Bearer, harga dan semakan yang sama seperti konsol, dan webhook yang ditandatangani untuk laporan penghantaran. URL Asas https://smsbulk.cc/rest/v1
Gambaran Keseluruhan
Hantar sehingga 8,000 nombor setiap permintaan, setiap satu dengan nilai gabungan sendiri, dan API memberi harga setiap penerima dengan teks gabungan mereka sendiri, sama seperti konsol. Minta sebut harga dahulu dengan badan yang sama untuk mengetahui kos; tiada apa yang dihantar atau dicaj oleh sebut harga. Masa adalah ISO 8601 dalam UTC, wang adalah rentetan dalam dolar AS dengan lima perpuluhan.
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;Pengesahan
Buat kunci dalam konsol di bawah API dan webhook; kunci dibuka selepas top-up pertama anda yang dikreditkan. Hantarkannya pada setiap permintaan sebagai token Bearer. Kunci ditunjukkan sekali; anda boleh menyimpan sehingga 5 kunci dan membatalkan mana-mana daripadanya sekaligus.
Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKadar
Awam, tiada kunci diperlukan. Harga setiap segmen SMS untuk setiap destinasi, atau untuk satu kod 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"
}Kredit
GEThttps://smsbulk.cc/rest/v1/credit
{ "credit": "125.40000", "currency": "USD", "account": "A418820037715" }Sebut Harga
Memberi harga penghantaran tanpa menghantarnya. Mengambil badan yang sama seperti Hantar.
POSThttps://smsbulk.cc/rest/v1/quote
| Medan | Jenis | Maksud |
|---|---|---|
| to | string or string[] | Satu nombor atau senarai. Bentuk antarabangsa dengan + adalah paling selamat; nombor tempatan memerlukan default_country. |
| recipients | object[] | Sebagai ganti kepada: senarai { to, fields } di mana medan mengisi medan gabungan seperti {{name}}. |
| text | string | Mesej tersebut, sehingga 2,000 aksara dan 10 segmen selepas penggabungan. |
| default_country | string | Kod ISO digunakan untuk nombor yang ditulis tanpa +, seperti GB. Kembali ke lalai yang disimpan dalam Tetapan. |
| opt_out | boolean | Menambah wording opt-out yang anda simpan sebagai baris terakhir (simpan dalam Tetapan dahulu). |
{
"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": []
}Hantar
Mencipta kempen, mengambil jumlahnya daripada kredit anda dan mengatur giliran mesej. Menjawab 201 dengan kempen. Nombor yang tidak dapat dihantar disenaraikan dalam left_out dan tidak pernah dicaj.
POSThttps://smsbulk.cc/rest/v1/send
| Medan | Jenis | Maksud |
|---|---|---|
| to / recipients / text / default_country / opt_out | Seperti dalam Quote. | |
| send_at | string | Masa ISO 8601 untuk bermula, 5 minit hingga 90 hari ke hadapan. Tinggalkan untuk hantar sekarang. |
| name | string | Label anda untuk kempen, sehingga 80 aksara. |
| client_ref | string | Rujukan anda sendiri, sehingga 64 aksara, dikembalikan pada setiap mesej dan 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": []
}Kempen
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}
Satu kempen termasuk kiraan setiap status: dalam giliran, dihantar, dihantar, gagal dan dibatalkan. Halaman senarai mesej dengan after dan has_more; sehingga 1,000 setiap halaman.
Mesej
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"
}Batalkan kempen yang dijadualkan
POSThttps://smsbulk.cc/rest/v1/campaigns/{id}/cancel
Berfungsi sehingga masa mula. Keseluruhan kos kembali ke kredit anda dalam satu baris lejar dan status kempen menjadi dibatalkan.
Webhook
Tetapkan URL endpoint dalam konsol. Kami POST JSON untuk message.delivered, message.failed dan campaign.done, tambah ujian apabila anda menekan Hantar ujian acara. Setiap acara membawa empat pengepala, dan sebarang jawapan 2xx dalam 10 saat dikira sebagai diterima.
| Medan | Jenis | Maksud |
|---|---|---|
| Smsbulk-Event | header | Nama acara, seperti message.delivered. |
| Smsbulk-Delivery | header | ID penghantaran, sama pada setiap percubaan semula satu acara. |
| Smsbulk-Timestamp | header | Saat Unix apabila percubaan ini ditandatangani. |
| Smsbulk-Signature | header | sha256= diikuti oleh base64 HMAC-SHA256 daripada timestamp, baris baru dan badan mentah, dikunci dengan rahsia sbw_ anda. |
Penghantaran dicuba 8 kali: sekaligus, kemudian selepas 1 minit, 5 minit, 15 minit, 1 jam, 3 jam, 6 jam dan 12 jam. Setiap percubaan ditunjukkan dalam log penghantaran konsol dengan status HTTP dan permulaan jawapan anda.
{
"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"
}
}Sahkan tandatangan
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", ""))Ralat dan Had
Ralat menggunakan status HTTP dan badan JSON: {"error":{"code":"insufficient_credit","message":"..."}}. Tiada caj dikenakan apabila permintaan gagal.
| Medan | Jenis | Maksud |
|---|---|---|
| 400 bad_json | Badan tersebut bukan JSON. | |
| 401 unauthorized | Kunci hilang, rosak atau dibatalkan. | |
| 402 insufficient_credit | Jumlah melebihi saldo anda. Tambah dana dalam konsol. | |
| 404 not_found | Tiada endpoint, kempen, mesej atau kod ISO tersebut dalam akaun ini. | |
| 409 not_scheduled | Pembatalan diminta untuk kempen yang telah bermula. | |
| 422 text_missing, text_too_long, too_many_segments | Mesej kosong, melebihi 2,000 aksara, atau melebihi 10 segmen untuk penerima. | |
| 422 no_recipients, too_many, recipients_missing | Tiada nombor yang boleh dihantar, lebih daripada 8,000 nombor, atau tiada penerima atau 'to'. | |
| 422 schedule_range, bad_send_at | send_at di luar 5 minit hingga 90 hari, atau bukan masa yang sah. | |
| 422 opt_out_unset | opt_out adalah benar tetapi tiada teks disimpan dalam Tetapan. | |
| 429 rate_limited | Lebih daripada 300 permintaan dalam seminit untuk kunci ini. |
Setiap jawapan membawa X-RateLimit-Limit, X-RateLimit-Remaining dan X-RateLimit-Reset.
Kod yang ditinggalkan
Nombor yang dikeluarkan sebelum penetapan harga dikembalikan dalam left_out dengan kedudukan mereka dalam senarai anda, nilai yang anda hantar, kod dan sebab yang jelas.
| Medan | Jenis | Maksud |
|---|---|---|
| no_country_code | Tiada + dan tiada negara lalai. | |
| too_short / too_long | Kurang daripada 8 atau lebih daripada 15 digit. | |
| not_a_number | Huruf atau simbol yang bukan sebahagian daripada nombor. | |
| nanp_length | Nombor +1 tanpa sebelas digit. | |
| not_covered | Destinasi di luar senarai kami. | |
| duplicate | Nombor yang sama muncul lebih awal dalam permintaan. | |
| suppressed | Nombor tersebut berada dalam senarai jangan hantar anda. |
Status dan sebab
| Medan | Jenis | Maksud |
|---|---|---|
| queued | status | Menunggu untuk keluar dari barisan kami. |
| sent | status | Diserahkan kepada rangkaian, menunggu laporan penghantaran. Kebanyakan laporan tiba dalam beberapa minit; sesetengah rangkaian mengambil masa sehingga 24 jam. |
| delivered | status | Rangkaian mengesahkan telefon menerimanya. |
| failed | status | Tidak dihantar. Harga telah dikembalikan ke saldo anda. |
| canceled | status | Tidak dihantar kerana anda membatalkan. Dikembalikan. |
| Medan | Jenis | Maksud |
|---|---|---|
| network_error | sebab | Ralat rangkaian |
| switched_off | sebab | Telefon dimatikan atau di luar liputan |
| not_in_service | sebab | Nombor tidak berfungsi |
| blocked | sebab | Disekat oleh rangkaian atau penerima |
| landline | sebab | Talian tetap atau nombor yang tidak boleh menerima SMS |
| content_filter | sebab | Ditolak oleh penapis kandungan rangkaian |
| expired | sebab | Tamat tempoh: tiada laporan sebelum had masa rangkaian |
| unknown | sebab | Tindak balas rangkaian tidak diketahui |
| canceled | sebab | Dibatalkan sebelum dihantar |
Soalan mengenai API? Buka tiket dalam konsol, atau baca FAQ.