面向开发者
API 参考文档
直接在你的代码中读取任意服务、任意国家的余额和现行价格。一个密钥放在请求头中,返回 JSON 数据。
第一个请求 #
全程都是走 HTTPS 的 JSON,用一把你自己签发的密钥认证。没有东西要安装,没有协议要签,也不用申请沙箱 —— 一把密钥加上 curl 就够了。
- 1 创建密钥 打开账户菜单,进入 API 密钥,创建一把。它只显示一次,之后我们只保留它的指纹,所以事后无处可查。
- 2 以 bearer 令牌的形式提交 每个调用都带一个 Authorization 请求头。除此之外没有第二条入口:不接受查询参数,不接受 cookie,也没有会话。
- 3 读取响应 每个响应都是一个带 ok 字段的 JSON 对象。ok 为 false 时,error 字段会用一个稳定的字符串说明原因,这个字符串是给你的代码看的。
如何出示密钥 #
一个账户一把密钥。除下面这个请求头以外的任何方式都不受支持,这是有意为之 —— 放在查询串里的密钥会进入访问日志、浏览器历史和 referrer 请求头,所以我们直接拒绝,而不是接受之后再悄悄泄露出去。
Authorization: Bearer sk_your_key_here
这把密钥会花钱 今天它只读取余额;等下单接口上线后,同一把密钥就能下单。请像对待密码一样对待它:不要提交到版本库,不要出现在截图里,需要时更换而不是传阅。签发一把新的,上一把当场作废。
格式与约定 #
| 基础地址 | https://smsactivate.io/api/v1 |
|---|---|
| 传输 | 只走 HTTPS:用普通 HTTP 调用会收到重定向,而重定向会丢掉你的 Authorization 头。 |
| 格式 | 双向都是 JSON。返回的永远是一个对象——裸数组从来不是合法的响应。 |
| 成功 | HTTP 200,且 "ok" 为 true。 |
| 失败 | 4xx 或 5xx 状态码,"ok" 为 false,外加一个拼写永不改变的 "error" 字符串。 |
| 金额 | 金额是美元数字——不是字符串,也不是美分:0.84 就是八十四美分。 |
| 未知字段 | 任何响应日后都可能增加字段。遇到不认识的字段请跳过,不要当成错误。 |
出错的时候 #
只根据 error 字符串分支判断,别的都不要用。旁边那句话是写给你看的,不是写给代码看的,随时可能改写;字符串不会。
| 状态码 | 错误 | 原因 |
|---|---|---|
| 401 | missing_key |
没有收到 Authorization 请求头,或者收到的不是 bearer 令牌。 |
| 401 | bad_key |
这把密钥我们不认识,或者它已经作废或被替换了。 |
| 400 | unknown_service |
没有服务对应这个代码。代码取自目录,不是屏幕上显示的名称。 |
| 404 | no_stock |
此刻这个「服务 + 国家」组合没有空闲线路。 |
| 404 | unknown_country |
这个产品下没有国家对应这个代码。两份目录覆盖的范围并不相同。 |
| 400 | unknown_tech |
这个国家不提供该制式的网络。只有美国按都市区出售。 |
| 429 | rate_limited |
调用过于频繁。Retry-After 请求头会给出需要等待的秒数。 |
可以压到多重 #
每分钟 600 次调用,按账户计,不按地址计。一把密钥本该只跑在一台服务器、也就是一个地址上 —— 按地址计数只会惩罚正常使用,同时让一把被盗的密钥在任何别处照样能用。超过上限会返回 429,并在 Retry-After 中给出秒数。
接口 #
账户与目录查询接口。每个接口都列出可接受的全部参数、返回的全部字段,以及可能出现的所有拒绝方式。
账户里有什么 #
GET
/api/v1/balance
当前的可用额度,以及按目录中现有的最低价,这些额度够做多少次激活。
返回字段
| 字段名 | 类型 | 说明 |
|---|---|---|
ok |
boolean | 状态码为 200 时,此字段存在且为 true。 |
balance |
number | 余额中的美元数,精确到美分。 |
currency |
string | 固定为 "USD";之所以返回,是为了让你什么都不必假设。 |
codes_at_cheapest |
integer | 按目录最低价计算,余额能买多少条验证码。这是一个用来感知规模的估算,不是报价;目录为空时为 null。 |
cheapest_code |
number | 这个最低价本身,便于你按另一个数字自行重算估算值。 |
请求
curl -s "https://smsactivate.io/api/v1/balance" \
-H "Authorization: Bearer $SMSACTIVATE_KEY"
响应
{
"ok": true,
"balance": 42.5,
"currency": "USD",
"codes_at_cheapest": 425,
"cheapest_code": 0.1
}
一个国家里的一个服务 #
GET
/api/v1/pricing?service={service}&country={country}
单个组合的实时价格和实时空闲数。两者都会漂移:价格跟随当地持有该服务的最便宜运营商,空闲数则是运营商在那一刻报上来的数值。
请求参数
| 名称 | 说明 | |
|---|---|---|
service |
必填 | 服务的代码,比如 telegram——小写,按目录里的拼写。 |
country |
可省略 | 国家的代码,比如 england。去掉它,一次调用就会返回所有国家——见下文。 |
返回字段
| 字段名 | 类型 | 说明 |
|---|---|---|
ok |
boolean | 状态码为 200 时,此字段存在且为 true。 |
service |
string | 服务代码,原样返回。 |
country |
string | 国家代码,原样返回。 |
price |
number | 在有货的最便宜运营商上,一条验证码的美元价格。 |
stock |
integer | 运营商此刻报告的空闲号码数——一个实时读数,每分钟都在变。 |
请求
curl -s "https://smsactivate.io/api/v1/pricing?service=telegram&country=england" \
-H "Authorization: Bearer $SMSACTIVATE_KEY"
响应
{
"ok": true,
"service": "telegram",
"country": "england",
"price": 0.84,
"stock": 61213
}
拒绝方式 missing_key bad_key unknown_service no_stock rate_limited
一个服务,所有国家 #
GET
/api/v1/pricing?service={service}
不填国家,就会返回所有承载该服务的国家。排序用的是本站自己的规则 —— 先看实际交付情况,同等情况下再看价格 —— 而不是原始价格;否则一条两分钱、但谁也收不到东西的线路会被顶到最前面。
请求参数
| 名称 | 说明 | |
|---|---|---|
service |
必填 | 服务的代码,比如 telegram。 |
返回字段
| 字段名 | 类型 | 说明 |
|---|---|---|
ok |
boolean | 状态码为 200 时,此字段存在且为 true。 |
service |
string | 你发送的服务代码。 |
name |
string | 网站上显示的名称,比如 "Telegram"。 |
countries |
array | 每个国家一个对象,按上文说明的顺序排列。 |
countries[].country |
string | 国家的代码,可直接传给配对调用。 |
countries[].name |
string | 它的英文名称。 |
countries[].price |
number | 一条验证码的美元价格。 |
countries[].stock |
integer | 此刻空闲的号码数。 |
请求
curl -s "https://smsactivate.io/api/v1/pricing?service=telegram" \
-H "Authorization: Bearer $SMSACTIVATE_KEY"
响应
{
"ok": true,
"service": "telegram",
"name": "Telegram",
"countries": [
{ "country": "england", "name": "United Kingdom", "price": 0.84, "stock": 61213 },
{ "country": "poland", "name": "Poland", "price": 0.91, "stock": 22140 }
]
}
移动会话价格 #
GET
/api/v1/proxy-pricing?country={country}&tech={tech}
不指定国家,返回我们放置调制解调器的那四个国家,每个附运营商数量和起步价。指定国家,则返回当地全部运营商及各时长的价格。时长以天为单位:1、7、30、365。
请求参数
| 名称 | 说明 | |
|---|---|---|
country |
可省略 | 国家的代码,比如 us。去掉它,返回的就是国家列表。 |
tech |
可省略 | 网络制式,4g 或 5g。只有美国两者都卖;其他地方该字段被忽略,返回唯一可用的号段。 |
返回字段
| 字段名 | 类型 | 说明 |
|---|---|---|
ok |
boolean | 状态码为 200 时,此字段存在且为 true。 |
country |
string | 你发送的国家代码。 |
tech |
string | 价格所对应的制式,已套用默认值之后。 |
techs |
array | 该国在售的每一种制式。 |
terms |
array | 可选的时长,以天计。 |
carriers |
array | 每家运营商一个对象。 |
carriers[].carrier |
string | 运营商的代码,下单时传入。 |
carriers[].name |
string | 运营商的显示名称,比如 "T-Mobile"。 |
carriers[].prices |
object | 各时长的美元价格,以天数为键。 |
请求
curl -s "https://smsactivate.io/api/v1/proxy-pricing?country=us" \
-H "Authorization: Bearer $SMSACTIVATE_KEY"
响应
{
"ok": true,
"country": "us",
"tech": "4g",
"techs": ["4g", "5g"],
"terms": [1, 7, 30, 365],
"carriers": [
{ "carrier": "us-t-mobile", "name": "T-Mobile", "prices": { "1": 8.67, "7": 52, "30": 130, "365": 1300 } },
{ "carrier": "us-verizon", "name": "Verizon", "prices": { "1": 8.67, "7": 52, "30": 130, "365": 1300 } }
]
}
拒绝方式 missing_key bad_key unknown_country unknown_tech rate_limited
租用价格 #
GET
/api/v1/rental-pricing?country={country}&days={days}
不指定国家,返回所有可以持有线路的国家,每个附起步价。指定国家,则返回当地全部运营商,以及你查询的那个时长在每家上的价格。
请求参数
| 名称 | 说明 | |
|---|---|---|
country |
可省略 | 国家的代码,比如 fr。去掉它,返回的就是国家列表。 |
days |
可省略 | 以天计的时长——7、14 或 30。其他任何值都按最短时长处理。 |
返回字段
| 字段名 | 类型 | 说明 |
|---|---|---|
ok |
boolean | 状态码为 200 时,此字段存在且为 true。 |
country |
string | 你发送的国家代码。 |
days |
integer | 价格所对应的时长。 |
terms |
array | 在售的每一种时长,以天计。 |
operators |
array | 每家运营商一个对象,价格最低的在前。 |
operators[].operator |
string | 运营商的代码,下单时传入。 |
operators[].name |
string | 它的显示名称。 |
operators[].type |
string | physical、virtual 或 premium 三者之一。 |
operators[].price |
number | 整个时长的美元价格。 |
请求
curl -s "https://smsactivate.io/api/v1/rental-pricing?country=fr&days=30" \
-H "Authorization: Bearer $SMSACTIVATE_KEY"
响应
{
"ok": true,
"country": "fr",
"days": 30,
"terms": [7, 14, 30],
"operators": [
{ "operator": "fr-lycamobile", "name": "Lycamobile", "type": "virtual", "price": 14.32 },
{ "operator": "fr-orange", "name": "Orange", "type": "physical", "price": 20.76 }
]
}
示例代码 #
可直接运行的完整程序,而非单行片段——包括最容易出错的两个环节。
Shell #
找出某个服务最便宜的国家,然后确认余额够不够。
#!/usr/bin/env bash
set -euo pipefail
: "${SMSACTIVATE_KEY:?export your key first}"
API="https://smsactivate.io/api/v1"
auth=(-H "Authorization: Bearer $SMSACTIVATE_KEY")
# The catalogue is already ordered: the first country is the one to take.
best=$(curl -sf "${API}/pricing?service=telegram" "${auth[@]}" \
| jq -r ".countries[0] | \"\(.country) \(.price)\"")
country=${best% *}
price=${best#* }
balance=$(curl -sf "${API}/balance" "${auth[@]}" | jq -r .balance)
# ⚠️ Compare as numbers, not as strings: "9.5" > "10" is true in a string sort.
if awk "BEGIN{exit !($balance >= $price)}"; then
echo "ok: $country at \$$price, balance \$$balance"
else
echo "top up first: need \$$price, have \$$balance" >&2
exit 1
fi
JavaScript #
同样的事在 Node 里做一遍,并妥当处理错误结构。
const API = "https://smsactivate.io/api/v1";
const key = process.env.SMSACTIVATE_KEY;
async function call(path) {
const r = await fetch(API + path, {
headers: { Authorization: `Bearer ${key}` },
});
const body = await r.json();
// A non-2xx always carries { ok:false, error }. Branch on `error`, never on
// the sentence — the string is stable, the wording is not.
if (!r.ok || !body.ok) {
if (body.error === "rate_limited") {
const wait = Number(r.headers.get("Retry-After") || 60);
await new Promise((s) => setTimeout(s, wait * 1000));
return call(path);
}
throw new Error(body.error ?? `http_${r.status}`);
}
return body;
}
const { countries } = await call("/pricing?service=telegram");
const { balance } = await call("/balance");
const best = countries[0];
console.log(`${best.name}: $${best.price} (${best.stock} in stock)`);
console.log(balance >= best.price ? "balance covers it" : "top up first");
Python #
再用 Python 做一遍,同样在遇到 429 时重试。
import os, time, requests
API = "https://smsactivate.io/api/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['SMSACTIVATE_KEY']}"
def call(path):
r = S.get(API + path, timeout=20)
body = r.json()
if not r.ok or not body.get("ok"):
if body.get("error") == "rate_limited":
time.sleep(int(r.headers.get("Retry-After", 60)))
return call(path)
raise RuntimeError(body.get("error", f"http_{r.status_code}"))
return body
countries = call("/pricing?service=telegram")["countries"]
balance = call("/balance")["balance"]
best = countries[0]
print(f"{best['name']}: ${best['price']} ({best['stock']} in stock)")
print("balance covers it" if balance >= best["price"] else "top up first")
还没有的部分 #
今天这把密钥能读取账户和目录。下一步是下单,它会在同一个基础地址上、用同一把密钥应答 —— 所以现在照着这些接口写的代码,等下单上线时不需要拆掉重写。
| 下单取号 | 暂未支持 暂未提供。目前下单在网站上完成。 |
|---|---|
| 轮询验证码 | 暂未支持 计划中,与下单一起上线。 |
| 释放号码 | 暂未支持 计划中,与下单一起上线。 |
| 通过 API 租用 | 暂未支持 计划中。 |
| Webhook | 暂未支持 暂未提供——等有订单事件可推送时,它会一起到来。 |
这一页会随之扩充:现有内容留在原处,新增的接口出现在「接口」一节下。
更新记录 #
- 首个公开版本:API 密钥、GET /balance、GET /pricing。