API SMS Massal v1
API REST kecil untuk mengirim SMS massal dari perangkat lunak Anda sendiri: JSON masuk dan keluar, kunci Bearer, harga dan pemeriksaan yang sama seperti di konsol, serta webhook yang ditandatangani untuk laporan pengiriman. URL Dasar https://smsbulk.cc/rest/v1
Ikhtisar
Kirim hingga 8,000 nomor per permintaan, masing-masing dengan nilai gabungannya sendiri, dan API memberi harga setiap penerima dengan teks gabungan mereka sendiri, persis seperti konsol. Minta kutipan terlebih dahulu dengan teks yang sama untuk mengetahui biayanya; tidak ada yang dikirim atau dibebankan oleh kutipan. Waktu adalah ISO 8601 dalam UTC, uang adalah string dalam dolar AS dengan lima desimal.
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;Autentikasi
Buat kunci di konsol di bawah API dan webhook; kunci terbuka setelah top-up pertama Anda yang dikreditkan. Kirimkan pada setiap permintaan sebagai token Bearer. Kunci ditampilkan sekali; Anda dapat menyimpan hingga 5 kunci dan mencabut salah satunya sekaligus.
Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTarif
Publik, tidak perlu kunci. Harga per segmen SMS untuk setiap tujuan, atau untuk satu kode 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" }Kutipan
Menghitung biaya pengiriman tanpa mengirimkannya. Mengambil teks yang sama seperti Kirim.
POSThttps://smsbulk.cc/rest/v1/quote
| Kolom | Tipe | Arti |
|---|---|---|
| to | string or string[] | Satu nomor atau daftar. Format internasional dengan + paling aman; nomor lokal memerlukan default_country. |
| recipients | object[] | Alih-alih ke: daftar { to, fields } di mana fields mengisi bidang gabungan seperti {{name}}. |
| text | string | Pesan, hingga 2,000 karakter dan 10 segmen setelah penggabungan. |
| default_country | string | Kode ISO digunakan untuk nomor yang ditulis tanpa +, seperti GB. Kembali ke default yang disimpan di Pengaturan. |
| opt_out | boolean | Menambahkan teks opt-out yang disimpan Anda sebagai baris terakhir (simpan terlebih dahulu di Pengaturan). |
{
"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": []
}Kirim
Membuat kampanye, mengambil totalnya dari kredit Anda dan mengantrekan pesan. Menjawab 201 dengan kampanye. Nomor yang tidak dapat dikirim dicantumkan di left_out dan tidak pernah dibebankan.
POSThttps://smsbulk.cc/rest/v1/send
| Kolom | Tipe | Arti |
|---|---|---|
| to / recipients / text / default_country / opt_out | Seperti dalam Quote. | |
| send_at | string | Waktu ISO 8601 untuk memulai, 5 menit hingga 90 hari ke depan. Biarkan kosong untuk mengirim sekarang. |
| name | string | Label Anda untuk kampanye, hingga 80 karakter. |
| client_ref | string | Referensi Anda sendiri, hingga 64 karakter, dikembalikan pada setiap pesan 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": []
}Kampanye
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 kampanye mencakup hitungan per status: dalam antrian, terkirim, terkirim, gagal, dan dibatalkan. Halaman daftar pesan dengan after dan has_more; hingga 1.000 per halaman.
Pesan
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 kampanye yang dijadwalkan
POSThttps://smsbulk.cc/rest/v1/campaigns/{id}/cancel
Berfungsi hingga waktu mulai. Seluruh biaya kembali ke kredit Anda dalam satu baris buku besar dan status kampanye menjadi dibatalkan.
Webhook
Atur URL endpoint di konsol. Kami POST JSON untuk message.delivered, message.failed, dan campaign.done, plus tes saat Anda menekan Kirim tes acara. Setiap acara membawa empat header, dan jawaban 2xx apa pun dalam 10 detik dianggap diterima.
| Kolom | Tipe | Arti |
|---|---|---|
| Smsbulk-Event | header | Nama acara, seperti message.delivered. |
| Smsbulk-Delivery | header | ID pengiriman, sama pada setiap percobaan ulang satu acara. |
| Smsbulk-Timestamp | header | Detik Unix saat percobaan ini ditandatangani. |
| Smsbulk-Signature | header | sha256= diikuti oleh base64 HMAC-SHA256 dari timestamp, baris baru, dan badan mentah, dikunci dengan rahasia sbw_ Anda. |
Pengiriman dicoba 8 kali: sekaligus, kemudian setelah 1 menit, 5 menit, 15 menit, 1 jam, 3 jam, 6 jam, dan 12 jam. Setiap percobaan ditampilkan di log pengiriman konsol dengan status HTTP dan awal jawaban 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"
}
}Verifikasi tanda tangan
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", ""))Kesalahan dan batasan
Kesalahan menggunakan status HTTP dan badan JSON: {"error":{"code":"insufficient_credit","message":"..."}}. Tidak ada biaya yang dikenakan saat permintaan gagal.
| Kolom | Tipe | Arti |
|---|---|---|
| 400 bad_json | Badan bukan JSON. | |
| 401 unauthorized | Kunci hilang, salah format, atau dicabut. | |
| 402 insufficient_credit | Total melebihi saldo Anda. Tambahkan dana di konsol. | |
| 404 not_found | Tidak ada endpoint, kampanye, pesan, atau kode ISO seperti itu di akun ini. | |
| 409 not_scheduled | Pembatalan diminta untuk kampanye yang sudah dimulai. | |
| 422 text_missing, text_too_long, too_many_segments | Pesan kosong, lebih dari 2,000 karakter, atau lebih dari 10 segmen untuk penerima. | |
| 422 no_recipients, too_many, recipients_missing | Tidak ada nomor yang dapat dikirim, lebih dari 8,000 nomor, atau tidak ada penerima maupun tujuan. | |
| 422 schedule_range, bad_send_at | send_at di luar 5 menit hingga 90 hari, atau bukan waktu yang valid. | |
| 422 opt_out_unset | opt_out benar tetapi tidak ada teks yang disimpan di Pengaturan. | |
| 429 rate_limited | Lebih dari 300 permintaan dalam satu menit untuk kunci ini. |
Setiap jawaban membawa X-RateLimit-Limit, X-RateLimit-Remaining, dan X-RateLimit-Reset.
Kode yang Ditinggalkan
Nomor yang dihapus sebelum penetapan harga kembali di left_out dengan posisi mereka dalam daftar Anda, nilai yang Anda kirim, kode, dan alasan sederhana.
| Kolom | Tipe | Arti |
|---|---|---|
| no_country_code | Tidak ada + dan tidak ada negara default. | |
| too_short / too_long | Kurang dari 8 atau lebih dari 15 digit. | |
| not_a_number | Huruf atau simbol yang bukan bagian dari nomor. | |
| nanp_length | Nomor +1 tanpa sebelas digit. | |
| not_covered | Tujuan di luar daftar kami. | |
| duplicate | Nomor yang sama muncul sebelumnya dalam permintaan. | |
| suppressed | Nomor tersebut ada di daftar jangan kirim Anda. |
Status dan alasan
| Kolom | Tipe | Arti |
|---|---|---|
| queued | status | Menunggu untuk keluar dari antrian kami. |
| sent | status | Diserahkan ke jaringan, menunggu laporan pengiriman. Kebanyakan laporan tiba dalam beberapa menit; beberapa jaringan membutuhkan hingga 24 jam. |
| delivered | status | Jaringan mengonfirmasi bahwa ponsel menerimanya. |
| failed | status | Tidak terkirim. Harga telah dikembalikan ke saldo Anda. |
| canceled | status | Tidak dikirim karena Anda membatalkan. Dana dikembalikan. |
| Kolom | Tipe | Arti |
|---|---|---|
| network_error | alasan | Kesalahan jaringan |
| switched_off | alasan | Ponsel mati atau di luar jangkauan |
| not_in_service | alasan | Nomor tidak aktif |
| blocked | alasan | Diblokir oleh jaringan atau penerima |
| landline | alasan | Telepon rumah atau nomor yang tidak dapat menerima SMS |
| content_filter | alasan | Ditolak oleh filter konten jaringan |
| expired | alasan | Kedaluwarsa: tidak ada laporan sebelum batas waktu jaringan |
| unknown | alasan | Respon jaringan tidak diketahui |
| canceled | alasan | Dibatalkan sebelum dikirim |
Pertanyaan tentang API? Buka tiket di konsol, atau baca FAQ.