群发短信APIv1
一个用于从您自己的软件发送批量短信的小型REST API:JSON输入输出,Bearer密钥,与控制台相同的价格和检查,以及用于送达报告的签名Webhook。 基础URL https://smsbulk.cc/rest/v1
概述
每个请求最多可发送到8,000个号码,每个号码都有自己的合并值,API会像控制台一样为每个收件人计算合并文本的价格。首先使用相同的正文请求报价以了解成本;报价不会发送或收取任何费用。时间采用ISO 8601 UTC格式,金额为五位小数的美元字符串。
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和Webhook下创建密钥;密钥在首次充值后启用。在每个请求中将其作为Bearer令牌发送。密钥只显示一次;您最多可以保留5个密钥,并随时撤销其中任何一个。
Authorization: Bearer smsb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx费率
公开,无需密钥。每个目的地或一个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"
}余额
GEThttps://smsbulk.cc/rest/v1/credit
{ "credit": "125.40000", "currency": "USD", "account": "A418820037715" }报价
为发送定价而不实际发送。使用与Send相同的正文。
POSThttps://smsbulk.cc/rest/v1/quote
| 字段 | 类型 | 含义 |
|---|---|---|
| to | string or string[] | 一个号码或列表。带+的国际格式最安全;本地号码需要default_country。 |
| recipients | object[] | 代替to:一个{ to, fields }列表,其中fields填充合并字段,如{{name}}。 |
| text | string | 消息,合并后最多2,000个字符和10个分段。 |
| default_country | string | 用于不带+的号码的ISO代码,如GB。回退到设置中保存的默认值。 |
| 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个字符,在每条消息和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": []
}活动
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}
单个活动包括每种状态的计数:排队、已发送、已送达、失败和取消。消息列表页面带有after和has_more参数;每页最多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
在开始时间之前有效。整个费用将以一条账目行返回您的余额,活动状态变为已取消。
Webhook
在控制台中设置一个端点URL。我们会为message.delivered、message.failed和campaign.done事件POST JSON,并在您按下发送测试事件时进行测试。每个事件携带四个头信息,任何10秒内的2xx响应都视为已接收。
| 字段 | 类型 | 含义 |
|---|---|---|
| Smsbulk-Event | 头部 | 事件名称,如message.delivered。 |
| Smsbulk-Delivery | 头部 | 投递ID,在每次重试同一事件时相同。 |
| Smsbulk-Timestamp | 头部 | 此尝试签名时的Unix秒数。 |
| Smsbulk-Signature | 头部 | sha256=后跟timestamp、换行符和原始主体的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 | 此密钥每分钟请求超过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 | +1 号码不足十一位数字。 | |
| not_covered | 目标不在我们的列表中。 | |
| duplicate | 请求中重复出现相同号码。 | |
| suppressed | 该号码在您的禁止发送列表中。 |
状态和原因
| 字段 | 类型 | 含义 |
|---|---|---|
| queued | 状态 | 等待离开我们的队列。 |
| sent | 状态 | 已交给网络,等待送达报告。大多数报告在几分钟内到达;某些网络可能需要长达24小时。 |
| delivered | 状态 | 网络确认手机已收到。 |
| failed | 状态 | 未送达。费用已退还至您的余额。 |
| canceled | 状态 | 因您取消而未发送。已退款。 |
| 字段 | 类型 | 含义 |
|---|---|---|
| network_error | 原因 | 网络错误 |
| switched_off | 原因 | 手机关机或不在服务区 |
| not_in_service | 原因 | 号码已停用 |
| blocked | 原因 | 被网络或收件人屏蔽 |
| landline | 原因 | 固话或无法接收短信的号码 |
| content_filter | 原因 | 被网络内容过滤器拒绝 |
| expired | 原因 | 已过期:在网络时限前未收到报告 |
| unknown | 原因 | 未知的网络响应 |
| canceled | 原因 | 发送前已取消 |
对API有疑问?在控制台中提交工单,或阅读FAQ。