Genel bakış
API'yi, mesajlar kendi sistemleriniz tarafından tetiklendiğinde kullanın: bir mağazadan sipariş güncellemeleri, bir giriş formundan kodlar, bir rezervasyon aracından hatırlatıcılar. En fazla 10,000 numara içeren bir POST /v1/messages isteği bir kampanya oluşturur, bakiyeden ücret keser ve her mesajı kuyruğa alır. API ile oluşturulan kampanyalar da konsolda teslimat raporlarıyla birlikte görünür.
- Tüm tutarlar, beş ondalık basamaklı string olarak ABD doları cinsindendir, örneğin "0.00534".
- Telefon numaraları uluslararası formattadır, başında + işareti olsun veya olmasın; boşluklar ve tireler kaldırılır.
- Zamanlar UTC zaman diliminde ISO 8601 formatındadır, örneğin 2026-12-01T09:00:00Z.
- Mesaj ve kampanya kimlikleri MSG-XXXX-XXXX-XXXX ve CMP-XXXX-XXXX-XXXX gibi görünür.
Kimlik Doğrulama
Konsolda API bölümü altında bir API anahtarı oluşturun (anahtarlar ilk bakiyenizi yükledikten sonra açılır). Her isteğin Authorization başlığında gönderin. Bir anahtar, bso_api_ ile başlayan ve ardından 36 karakterden oluşur; sadece bir kez gösterilir, bu nedenle sunucunuzda bir sır olarak saklayın. Yeni bir anahtar oluşturmak öncekini anında değiştirir.
Authorization: Bearer bso_api_0123456789abcdef0123456789abcdef0123 Content-Type: application/json
Sınırlar
| İstekler | Anahtar başına dakikada 300; fazlası 429 rate_limited yanıtı verir |
|---|---|
| İstek başına numara sayısı | 10,000 |
| Mesaj metni | Yazıldığı gibi 2,000 karakter, en fazla 10 segment |
| Zamanlama | send_at, 5 dakikadan 60 güne kadar ileri bir tarih |
| Gönderim hızı | Kuyruk başına dakikada 400 mesaj |
| Webhook yeniden denemeleri | Yaklaşık 22 saat boyunca 9 deneme |
Mesaj gönder
POST/v1/messages
Bir veya daha fazla numaraya bir metin gönderir. İstek kabul edildiğinde tam maliyet bakiyeden alınır; teslim edilemeyen her mesaj daha sonra iade edilir. Yanıt 201 durum kodunu ve her mesajla birlikte kampanyayı içerir.
| Alan | Tür | Açıklama |
|---|---|---|
togerekli | string | string[] | Uluslararası formatta bir numara veya en fazla 10,000 numaradan oluşan bir dizi. Tekrarlanan numaralar bir kez gönderilir; okunamayanlar atlananlar listesinde belirtilir. |
textgerekli | string | Mesaj, en fazla 2,000 karakter ve 10 segment. GSM-7 metni SMS başına 160 karakter, Unicode 70 karakter tutar. |
send_atisteğe bağlı | string | Gönderimi planlamak için ISO 8601 zamanı, 5 dakika ile 60 gün sonrası. Şimdi göndermek için boş bırakın. |
opt_outisteğe bağlı | boolean | true, konsol ayarlarından alınan abonelikten çıkma satırını sona yeni bir satır olarak ekler. |
client_refisteğe bağlı | string | Kendi referansınız, en fazla 64 harf, rakam ve _ . : -. Raporlarda ve webhook'larda döndürülür. |
curl -X POST https://bulksmsonline.cc/api/v1/messages \
-H "Authorization: Bearer $BSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to":["+447700900412","+33612345678"],"text":"Order 4521 has shipped. Track it at shop.example/t/4521","client_ref":"order-4521"}'const res = await fetch("https://bulksmsonline.cc/api/v1/messages", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.BSO_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ to: ["+447700900412", "+33612345678"], text: "Order 4521 has shipped.", client_ref: "order-4521" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const campaign = await res.json();import os, requests
res = requests.post("https://bulksmsonline.cc/api/v1/messages",
headers={"Authorization": f"Bearer {os.environ['BSO_API_KEY']}"},
json={"to": ["+447700900412", "+33612345678"], "text": "Order 4521 has shipped.", "client_ref": "order-4521"},
timeout=30)
res.raise_for_status()
campaign = res.json()<?php
$ch = curl_init("https://bulksmsonline.cc/api/v1/messages");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("BSO_API_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["to" => ["+447700900412", "+33612345678"], "text" => "Order 4521 has shipped.", "client_ref" => "order-4521"])]);
$campaign = json_decode(curl_exec($ch), true);HTTP/1.1 201 Created
{
"id": "CMP-7KQ2-M9XA-4HTD",
"status": "sending",
"channel": "api",
"recipients": 2,
"segments": 1,
"encoding": "GSM-7",
"cost": "0.02158",
"refunded": "0.00000",
"client_ref": "order-4521",
"send_at": null,
"counts": { "queued": 2, "submitted": 0, "delivered": 0, "failed": 0, "canceled": 0 },
"skipped": [],
"balance": "118.43120",
"messages": [
{ "id": "MSG-3RTW-81KD-ZQ0P", "to": "+447700900412", "country": "GB", "segments": 1, "price": "0.00834", "status": "queued" },
{ "id": "MSG-5HNA-4C2E-7YVB", "to": "+33612345678", "country": "FR", "segments": 1, "price": "0.01324", "status": "queued" }
]
}Gönderim tahmini al
POST/v1/quote
/v1/messages ile aynı gövdeyi alır, herhangi bir gönderme veya ücretlendirme yapmadan tam maliyeti, segmentleri ve kodlamayı döndürür. Kullanıcılarınıza onaylamadan önce bir fiyat göstermek için kullanın.
{
"recipients": 2,
"skipped": [],
"segments": 1,
"encoding": "GSM-7",
"cost": "0.02158",
"balance": "118.45278",
"countries": [ { "country": "GB", "recipients": 1, "cost": "0.00834" }, { "country": "FR", "recipients": 1, "cost": "0.01324" } ]
}Bakiye
GET/v1/balance
{ "balance": "118.43120", "currency": "USD" }Tarifeler
GET/v1/rates
GET/v1/rates/{country}
Her hedef için veya bir iki harfli ISO ülke kodu için bir SMS segmentinin fiyatı. Bu iki uç nokta herkese açıktır ve anahtar gerektirmez.
{ "country": "DE", "name": "Germany", "calling_code": "+49", "price": "0.01544", "currency": "USD", "per": "segment" }Mesaj durumu
GET/v1/messages/{id}
{
"id": "MSG-3RTW-81KD-ZQ0P",
"campaign_id": "CMP-7KQ2-M9XA-4HTD",
"to": "+447700900412",
"country": "GB",
"segments": 1,
"price": "0.00834",
"status": "delivered",
"error": null,
"created_at": "2026-11-07T09:00:02Z",
"submitted_at": "2026-11-07T09:00:03Z",
"final_at": "2026-11-07T09:00:08Z"
}Kampanyalar
GET/v1/campaigns/{id}
GET/v1/campaigns/{id}/messages?offset=0&limit=100
İlki, kampanyayı durum bazlı sayımlarla döndürür; ikincisi mesajlarını sayfalar, sayfa başına en fazla 1.000. Konsoldan gönderilen kampanyalar, sayfalarında gösterilen kimlik ile aynı şekilde okunabilir.
Planlanmış gönderimi iptal et
POST/v1/campaigns/{id}/cancel
send_at ile oluşturulmuş ve henüz başlamamış bir kampanyayı durdurur. Her mesaj iptal edilir ve tam maliyet bakiyeye iade edilir. Zaten gönderimde olan bir kampanya 409 not_scheduled ile yanıt verir.
Teslimat webhook'ları
Konsolda API altına her nihai durumu (teslim edildi veya başarısız) bir POST isteği olarak almak için bir HTTPS URL'si ekleyin. 8 saniye içinde herhangi bir 2xx durumu ile yanıt verin. Diğer yanıtlar ve zaman aşımları 20 saniye, 1, 5 ve 15 dakika, ardından 1, 3, 6 ve 12 saat sonra yeniden denenir, toplam 9 deneme. Aynı olay iki kez gelebilir, bu yüzden onu bir kez işlemek için event_id kullanın.
POST https://your-server.example/sms-status
Content-Type: application/json
X-BulkSMSOnline-Signature: t=1794042008,v1=5f0c2a...e91b
{
"type": "message.status",
"event_id": "ev_8f1c22d0a4b97e63c1f0",
"sent_at": "2026-11-07T09:00:09Z",
"data": {
"message_id": "MSG-3RTW-81KD-ZQ0P",
"campaign_id": "CMP-7KQ2-M9XA-4HTD",
"to": "+447700900412",
"country": "GB",
"status": "delivered",
"error": null,
"segments": 1,
"price": "0.00834",
"currency": "USD",
"client_ref": "order-4521",
"final_at": "2026-11-07T09:00:08Z"
}
}İmzayı doğrula
Başlık, Unix zamanı t ve webhook gizli anahtarınızla "t.raw_body" dizesinin HMAC-SHA256 imzası v1'i onaltılık olarak içerir. JSON'u ayrıştırmadan önce ham gövde üzerinde hesaplayın, sabit zamanda karşılaştırın ve beş dakikadan eski olayları reddedin.
import crypto from "node:crypto";
export function verify(rawBody, 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 = crypto.createHmac("sha256", secret).update(m[1] + "." + rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(m[2]));
}import hmac, hashlib, re, time
def verify(raw_body: 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_body, 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)) return false;
if (abs(time() - (int)$m[1]) > 300) return false;
$want = hash_hmac('sha256', $m[1] . '.' . $raw, $secret);
return hash_equals($want, $m[2]);
}Durumlar
| queued | Kabul edildi ve kuyruktan çıkmayı bekliyor |
|---|---|
| submitted | Hedef ağa teslim edildi |
| delivered | Telefon tarafından onaylandı (nihai) |
| failed | Teslim edilemedi; ücret iade edildi (nihai) |
| canceled | Gönderimden önce iptal edilen planlanmış bir kampanyanın parçası (nihai) |
Kampanya durumu scheduled, sending, complete veya canceled.
Hatalar
Hatalar bir HTTP durumu ve bir JSON gövdesi ile yanıt verir: { "error": { "code": "insufficient_balance", "message": "..." } }. Bir istek hata ile yanıt verdiğinde hiçbir şey ücretlendirilmez.
| HTTP | Kod | Anlam |
|---|---|---|
| 400 | invalid_request | Bir alan eksik veya geçersiz; mesaj hangisi olduğunu söyler. |
| 400 | invalid_json | Gövde JSON formatında değil. |
| 400 | no_valid_numbers | Hiçbir numara okunamadı. |
| 401 | unauthorized | Anahtar eksik, bozuk veya değiştirilmiş. |
| 402 | insufficient_balance | Bakiye maliyeti karşılamıyor; bakiye yükleyip tekrar gönderin. |
| 404 | not_found | Bilinmeyen kimlik veya uç nokta. |
| 409 | not_scheduled | Sadece henüz başlamamış bir planlanmış kampanya iptal edilebilir. |
| 413 | too_many_recipients | Bir istekte 10,000'den fazla numara. |
| 429 | rate_limited | Bir dakikada 300'den fazla istek. |
| 500 | server_error | İsteği tekrar deneyin; hiçbir ücret alınmadı. |