一括SMS API v1
独自のソフトウェアからバルクSMSを送信するための小型REST API: JSON入出力、Bearerキー、コンソールと同じ価格とチェック、配信レポートのための署名付きウェブフック。 ベースURL https://smsbulk.cc/rest/v1
概要
1リクエストあたり最大8,000件の番号に送信し、それぞれに独自のマージ値を設定できます。APIはコンソールと同様に、各受信者のマージされたテキストに基づいて価格を計算します。まず同じ本文で見積もりをリクエストしてコストを確認してください。見積もりでは何も送信されず、課金も発生しません。時間はISO 8601形式でUTC、金額は5桁の小数部を持つUSドルの文字列です。
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;認証
コンソールのAPIとウェブフックの下でキーを作成します。初回のクレジットチャージ後にキーが有効になります。すべてのリクエストでBearerトークンとして送信してください。キーは一度だけ表示されます。最大5個のキーを保持でき、いつでも任意のキーを無効にできます。
Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx料金
公開されており、キーは不要です。すべての宛先または1つのISOコードに対するSMSセグメントごとの価格です。
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"
}クレジット
GEThttps://smsbulk.cc/rest/v1/credit
{ "credit": "125.40000", "currency": "USD", "account": "A418820037715" }見積もり
送信せずに送信の価格を計算します。Sendと同じ本文を取ります。
POSThttps://smsbulk.cc/rest/v1/quote
| フィールド | タイプ | 意味 |
|---|---|---|
| to | string or string[] | 1つの番号またはリスト。+付きの国際形式が最も安全です。ローカル番号にはdefault_countryが必要です。 |
| recipients | object[] | toの代わりに、fieldsが{{name}}などのマージフィールドを埋める{ to, fields }のリスト。 |
| text | string | メッセージは、マージ後2,000文字、10セグメントまで。 |
| default_country | string | +なしで書かれた番号(GBなど)に使用されるISOコード。設定に保存されたデフォルトにフォールバックします。 |
| opt_out | boolean | 保存したオプトアウトの文言を最後の行として追加します(最初に設定に保存してください)。 |
{
"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": []
}送信
キャンペーンを作成し、その合計をクレジットから引き出してメッセージをキューに入れます。キャンペーンとともに201を返します。送信できない番号はleft_outにリストされ、課金されることはありません。
POSThttps://smsbulk.cc/rest/v1/send
| フィールド | タイプ | 意味 |
|---|---|---|
| to / recipients / text / default_country / opt_out | Quoteと同様。 | |
| send_at | string | 開始するISO 8601時間、5分から90日前。省略するとすぐに送信します。 |
| name | string | キャンペーンのラベル、最大80文字。 |
| client_ref | string | 独自の参照、最大64文字、すべてのメッセージとウェブフックで返されます。 |
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": []
}キャンペーン
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}
1つのキャンペーンには、キューに入れられた、送信された、配信された、失敗した、キャンセルされた各ステータスのカウントが含まれます。メッセージリストページはafterとhas_moreでページングされ、1ページあたり最大1,000件です。
メッセージ
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"
}予定されたキャンペーンをキャンセル
POSThttps://smsbulk.cc/rest/v1/campaigns/{id}/cancel
開始時刻まで有効です。全額が1つの台帳行としてクレジットに戻り、キャンペーンステータスがキャンセルになります。
ウェブフック
コンソールでエンドポイントURLを設定します。message.delivered、message.failed、campaign.doneのJSONをPOSTし、Send test eventを押すとテストも送信します。各イベントには4つのヘッダーが付き、10秒以内の2xx応答は受信済みとみなされます。
| フィールド | タイプ | 意味 |
|---|---|---|
| Smsbulk-Event | ヘッダー | message.deliveredなどのイベント名。 |
| Smsbulk-Delivery | ヘッダー | 配信ID、1つのイベントの再試行ごとに同じ。 |
| Smsbulk-Timestamp | ヘッダー | この試行が署名されたUnix秒。 |
| Smsbulk-Signature | ヘッダー | sha256=に続いて、タイムスタンプ、改行、および生の本文のbase64 HMAC-SHA256、sbw_シークレットでキー化されます。 |
配信は8回試行されます: 即時、1分後、5分後、15分後、1時間後、3時間後、6時間後、12時間後。各試行はコンソールの配信ログにHTTPステータスと応答の冒頭とともに表示されます。
{
"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"
}
}署名を確認する
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", ""))エラーと制限
エラーはHTTPステータスとJSONボディを使用します: {"error":{"code":"insufficient_credit","message":"..."}}。リクエストが失敗しても課金されません。
| フィールド | タイプ | 意味 |
|---|---|---|
| 400 bad_json | 本文はJSONではありません。 | |
| 401 unauthorized | キーが欠落している、不正な形式、または取り消されています。 | |
| 402 insufficient_credit | 合計がクレジットを超えています。コンソールで資金を追加してください。 | |
| 404 not_found | このアカウントにはそのエンドポイント、キャンペーン、メッセージ、またはISOコードはありません。 | |
| 409 not_scheduled | 既に開始されたキャンペーンに対してキャンセルが要求されました。 | |
| 422 text_missing, text_too_long, too_many_segments | メッセージが空であるか、2,000文字を超えているか、または受信者に対して10セグメントを超えています。 | |
| 422 no_recipients, too_many, recipients_missing | 送信可能な番号がないか、8,000番号を超えているか、またはtoもrecipientsも指定されていません。 | |
| 422 schedule_range, bad_send_at | send_atが5分から90日の範囲外であるか、または有効な時間ではありません。 | |
| 422 opt_out_unset | opt_outがtrueですが、設定に文言が保存されていません。 | |
| 429 rate_limited | このキーに対して1分間に300回以上のリクエストがあります。 |
すべての回答にはX-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Resetが含まれます。
除外コード
価格設定前に削除された番号は、left_outにリスト内の位置、送信した値、コード、および理由とともに返されます。
| フィールド | タイプ | 意味 |
|---|---|---|
| no_country_code | +がなく、デフォルトの国もありません。 | |
| too_short / too_long | 8桁未満または15桁を超えています。 | |
| not_a_number | 数字の一部ではない文字や記号があります。 | |
| nanp_length | 11桁ではない +1 の番号です。 | |
| not_covered | リスト外の宛先です。 | |
| duplicate | 同じ番号がリクエストの前半に出現しました。 | |
| suppressed | その番号は送信禁止リストにあります。 |
ステータスと理由
| フィールド | タイプ | 意味 |
|---|---|---|
| queued | ステータス | キューから出るのを待っています。 |
| sent | ステータス | ネットワークに渡され、配信レポートを待っています。ほとんどのレポートは数分以内に到着しますが、一部のネットワークでは24時間かかることがあります。 |
| delivered | ステータス | ネットワークが電話に届いたことを確認しました。 |
| failed | ステータス | 配信されませんでした。料金はクレジットに返金されました。 |
| canceled | ステータス | キャンセルしたため送信されませんでした。返金されました。 |
| フィールド | タイプ | 意味 |
|---|---|---|
| network_error | 理由 | ネットワークエラー |
| switched_off | 理由 | 電話がオフまたは圏外です |
| not_in_service | 理由 | サービス外の番号です |
| blocked | 理由 | ネットワークまたは受信者によってブロックされました |
| landline | 理由 | SMSを受信できない固定電話または番号 |
| content_filter | 理由 | ネットワークのコンテンツフィルターによって拒否されました |
| expired | 理由 | 期限切れ: ネットワークの制限時間内にレポートがありませんでした |
| unknown | 理由 | 不明なネットワーク応答 |
| canceled | 理由 | 送信前にキャンセルされました |
APIについての質問ですか?コンソールでチケットを開くか、FAQを読んでください。