クイックスタート
- コンソールでアカウントを作成し、残高をチャージしてください。
- APIキーを開き、キーを作成します。最初のチャージが反映された後にキーを作成できます。キーは一度だけ表示されます。秘密として保管してください。
- 番号とテキストを指定して
/smsにPOSTリクエストを送信します。返されたキャンペーンIDを読み取り、コンソールまたはウェブフックで配信を追跡します。
認証
公開料金リストを除くすべてのリクエストには、Authorizationヘッダーにキーを含めます。キーはsbk_live_で始まります。新しいキーは即座に古いキーを置き換えます。
Authorization: Bearer sbk_live_YOUR_KEYSMS送信
POST/sms
キャンペーンを作成し、残高から料金を引き、各メッセージをキューに入れます。番号はクリーンアップされ重複が除去されます。使用できない番号はdroppedに理由と共に返され、課金されません。
| フィールド | タイプ | 説明 |
|---|---|---|
to必須 | 文字列または配列 | 1つの番号または最大10,000個の番号の配列(国際形式、例: "+447700900461") |
text必須 | 文字列 | メッセージテキスト、最大10セグメント。GSM-7テキストは1セグメント160文字、UCS-2は70文字まで |
schedule_at任意 | 文字列 | ISO 8601形式の時刻(5分後から60日後まで)。省略すると即時送信 |
client_ref任意 | 文字列 | 独自の参照(最大64文字、a-z A-Z 0-9 _ . : -)、レポートとウェブフックで返されます |
opt_out任意 | ブール値 | trueにすると、アカウント設定のオプトアウト行が追加されます |
curl -X POST https://sendbulksms.cc/api/v1/sms \
-H "Authorization: Bearer sbk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["+447700900461", "+5511955550123"],
"text": "Hi, your order 4821 ships today.",
"client_ref": "orders-4821"
}'const res = await fetch("https://sendbulksms.cc/api/v1/sms", {
method: "POST",
headers: { Authorization: "Bearer " + process.env.SENDBULKSMS_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ to: ["+447700900461", "+5511955550123"], text: "Hi, your order 4821 ships today.", client_ref: "orders-4821" }),
});
const campaign = await res.json();
console.log(res.status, campaign.id, campaign.cost);import os, requests
r = requests.post(
"https://sendbulksms.cc/api/v1/sms",
headers={"Authorization": "Bearer " + os.environ["SENDBULKSMS_KEY"]},
json={"to": ["+447700900461", "+5511955550123"], "text": "Hi, your order 4821 ships today.", "client_ref": "orders-4821"},
timeout=30,
)
print(r.status_code, r.json()["id"])<?php
$ch = curl_init("https://sendbulksms.cc/api/v1/sms");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SENDBULKSMS_KEY"), "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["to" => ["+447700900461", "+5511955550123"], "text" => "Hi, your order 4821 ships today.", "client_ref" => "orders-4821"]),
]);
$campaign = json_decode(curl_exec($ch), true);
echo $campaign["id"];レスポンス: キャンペーンとそのコスト、最初の1,000件のメッセージ キャンペーンメッセージでさらに取得
HTTP/1.1 201 Created
{
"id": "CP-7KQ2-M9XA-4HTD",
"status": "sending",
"source": "api",
"recipients": 2,
"segments": 1,
"encoding": "GSM-7",
"cost": "0.01078",
"refunded": "0.00000",
"client_ref": "orders-4821",
"scheduled_at": null,
"created_at": "2026-11-07T10:32:04.118Z",
"done_at": null,
"counts": { "queued": 2, "sent": 0, "delivered": 0, "failed": 0, "canceled": 0 },
"dropped": [],
"balance": "248.30422",
"messages": [
{ "id": "SM-2RBW-8MZN-QF6K", "campaign_id": "CP-7KQ2-M9XA-4HTD", "to": "+447700900461", "country": "GB", "parts": 1, "price": "0.00819", "status": "queued", "error": null, "created_at": "2026-11-07T10:32:04.118Z", "sent_at": null, "done_at": null }
]
}送信見積もり
POST/estimate
/smsと同じボディを受け取り、受信者、セグメント、エンコーディング、総コスト、国別コストを返します(課金や送信は行われません)。
{ "recipients": 2, "dropped": [], "segments": 1, "encoding": "GSM-7", "cost": "0.01078", "balance": "248.31500",
"countries": [ { "country": "GB", "recipients": 1, "cost": "0.00819" }, { "country": "BR", "recipients": 1, "cost": "0.00259" } ] }メッセージステータス
GET/sms/{message_id}
1件のメッセージを返します: 番号、国、セグメント、価格、ステータス、失敗時のエラー、各ステップの時刻
キャンペーン
GET/campaigns/{campaign_id}
キャンペーンとそのコスト、返金、ステータス別メッセージ数
GET/campaigns/{campaign_id}/messages?offset=0&limit=100
キューに入れられた順のメッセージ、1ページあたり最大1,000件
POST/campaigns/{campaign_id}/cancel
開始前の予約キャンペーンをキャンセルし、全額を残高に返金します
残高と料金
GET/balance
{ "balance": "248.31500", "currency": "USD" }GET/rates
公開、キー不要。各国の通話コードとセグメントあたりの料金。/rates/GBは1か国を返します
{ "updated": "2026-11-07", "rates": [ { "country": "GB", "name": "United Kingdom", "calling_code": "+44", "price": "0.00819", "currency": "USD", "unit": "SMS segment" } ] }配信ウェブフック
APIキーの下でウェブフックURLを設定します。メッセージが最終ステータス(配信済みまたは失敗)になると、レポートをPOSTします。8秒以内に2xxステータスで応答してください。それ以外の場合、約11時間にわたり最大8回再試行されます。idを使用して既に処理したレポートを無視できます。
POST https://your-server.example/sms-status
Content-Type: application/json
X-SendBulkSMS-Signature: t=1794047524,v1=5f0c3b9e...
{
"event": "message.status",
"id": "evt_8c1d2e4f6a7b9c0d1e2f",
"created_at": "2026-11-07T10:32:09.004Z",
"data": {
"message_id": "SM-2RBW-8MZN-QF6K",
"campaign_id": "CP-7KQ2-M9XA-4HTD",
"to": "+447700900461",
"country": "GB",
"status": "delivered",
"error": null,
"parts": 1,
"price": "0.00819",
"client_ref": "orders-4821",
"done_at": "2026-11-07T10:32:08.912Z"
}
}レポートを信頼する前に署名を確認してください: t + "." + raw bodyのHMAC-SHA256をウェブフックシークレットで計算し、一定時間で比較し、5分以上古い時刻は拒否します。
import { createHmac, timingSafeEqual } from "node:crypto";
// raw = the request body exactly as received (string), header = X-SendBulkSMS-Signature
export function verify(raw, 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 = Buffer.from(createHmac("sha256", secret).update(m[1] + "." + raw).digest("hex"));
const got = Buffer.from(m[2]);
return want.length === got.length && timingSafeEqual(want, got);
}import hmac, hashlib, re, time
def verify(raw: 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, 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) || abs(time() - (int)$m[1]) > 300) return false;
$want = hash_hmac('sha256', $m[1] . '.' . $raw, $secret);
return hash_equals($want, $m[2]);
}ステータス
| ステータス | 意味 |
|---|---|
queued | 受理され、キュー内で順番待ち中 |
sent | ルートに渡され、配信レポート待ち |
delivered | ネットワークが端末への配信を確認 |
failed | 未配信。料金は返金され、エラーに理由が記録されます |
canceled | 開始前の予約キャンペーンの一部がキャンセルされました。返金済み |
エラー
エラーはHTTPステータスコードとJSONボディを使用します: { "error": { "code": "insufficient_balance", "message": "..." } }。エラーが返されたリクエストでは残高はチャージされません
| HTTP | コード | 条件 |
|---|---|---|
| 400 | bad_requestフィールドが不足しているか無効です。メッセージで具体的に示します | フィールドが不足しているか無効です。メッセージで具体的に示します |
| 400 | no_valid_numbers"to"内の番号がすべて無効でした | "to"内の番号がすべて無効でした |
| 401 | unauthorizedAPIキーが不足しているか無効です | APIキーが不足しているか無効です |
| 402 | insufficient_balance残高が送信分をカバーしていません。チャージは発生しませんでした | 残高が送信分をカバーしていません。チャージは発生しませんでした |
| 404 | not_foundキャンペーン、メッセージまたはエンドポイントがアカウント内に存在しません | キャンペーン、メッセージまたはエンドポイントがアカウント内に存在しません |
| 409 | not_scheduled開始前の予約キャンペーンのみキャンセル可能です | 開始前の予約キャンペーンのみキャンセル可能です |
| 413 | too_many_recipients1リクエストあたり10,000件を超える番号が指定されました | 1リクエストあたり10,000件を超える番号が指定されました |
| 429 | rate_limitedこのキーで1分あたり300リクエストを超えました | このキーで1分あたり300リクエストを超えました |
| 500 | server_error再試行してください。失敗したリクエストでは課金されません | 再試行してください。失敗したリクエストでは課金されません |
制限
- APIキーごとに1分あたり300リクエストまで
- 1リクエストあたり10,000件までの番号。メッセージごとに10セグメント
- メッセージはキューごとに1分あたり最大200件のペースで、受理順に送信されます
- 5分後から60日後まで予約可能
- 送信不可リストの番号はスキップされ、droppedにsuppressedコードで返されます。課金されません