SMSPING · 使用指南

用 Python 调用 SMSPing API 接收短信验证码

这篇教程用不到 80 行 Python 走完一个完整流程:查服务和国家、选一个有库存的报价、购买一次性号码、等短信、确认订单。下面所有接口和规则都来自接码 API 指南和开发者文档。上线前请再对照文档确认最新的字段和限制。

购买号码会扣除 SMSPing 账户里的真实余额。下面的脚本默认只做演练(dry run),加上 --buy 才会真正购买。

准备

  • Python 3.8 或更高版本,以及 requests(pip install requests)。
  • 一个有余额的 SMSPing 账户和 API 密钥:登录后在账户设置的 API 管理中生成密钥,完整密钥只显示一次。
  • 密钥只放在服务端,比如环境变量 SMSPING_API_KEY。不要写进浏览器代码、URL 或公开仓库。

接口一览

所有接口都在 https://smsping.net/api/customer 下。

步骤 请求 是否需要密钥
列出服务和国家 GET /catalog 否
某个组合的价格和库存 GET /catalog/offers?country=<id>&service=<id> 否
购买号码 POST /orders 是
查询订单和短信 GET /orders/:id 是
收到短信后完成 POST /orders/:id/finish 是
等待中取消 POST /orders/:id/cancel 是

成功响应是 {"code": 0, "data": ...}。失败时返回非 2xx 状态码,响应体里有 message,通常还有 errorCode,比如 INSUFFICIENT_BALANCE(余额不足)或 PRICE_CHANGED(价格变了)。

第 1 步:查服务和国家的 ID

GET /catalog 是公开接口,返回 data.services 和 data.countries,每一项都有一个 id,后面的请求要用:

curl -s https://smsping.net/api/customer/catalog

比如 Telegram 是 telegram,美国是 usa。请以接口返回为准,不要自己猜。

第 2 步:选报价

GET /catalog/offers 必须同时带 country 和 service。每个报价有 operator(运营商)、price(币种见响应里的 currency,即 USD)和 stock(库存)。脚本只保留有库存的报价并选最便宜的,这是示例自己的选择,你也可以固定某个运营商。

第 3 步:带幂等键购买

POST /orders 需要 country、service、operator、expectedPrice(刚查到的价格)和 idempotencyKey(16 到 100 位,只能是字母、数字、_ 或 -)。

  • 发请求之前先把幂等键存下来。如果请求超时,同一笔购买要用同一个键、同样的参数重试;换新键可能会再买一单。
  • 遇到 PRICE_CHANGED,重新查报价,确认新价格后再买。
  • HTTP 200 不代表已经分配到号码,要看 data.status。如果是 review,保留订单号,查询该订单或联系客服,不要重复购买。

第 4 步:轮询验证码

调用 GET /orders/:id,两次查询至少间隔 5 秒。status 变成 received 时,验证码在 code 字段里(短信内容在 sms 里)。其他终态有 finished、canceled、timeout、failed 和 review。脚本里 10 分钟停止轮询是我们自己设的本地保护,不是 API 规定。

第 5 步:完成或取消

收到短信后调用 POST /orders/:id/finish。还在等待时可以调用 POST /orders/:id/cancel。只有确认取消或超时且没收到短信才会退款,所以请检查最终的 status 和 refunded 字段。

频率限制

目前每个账户每分钟 60 次请求。收到 HTTP 429 时,按 Retry-After 头里的秒数等待后,重发同一个请求。如果没有这个头,脚本默认等 5 秒,这是示例自己的选择。

完整脚本

"""Receive an SMS verification code with the SMSPing API (Python 3.8+, `pip install requests`).

Set SMSPING_API_KEY in your environment. Nothing is bought unless you pass --buy.
"""
import os
import sys
import time
import uuid

import requests

API = "https://smsping.net/api/customer"


def call(method, path, *, params=None, body=None, auth=False):
    headers = {"Accept": "application/json"}
    if auth:
        headers["Authorization"] = "Bearer " + os.environ["SMSPING_API_KEY"]
    for _ in range(5):
        r = requests.request(method, API + path, params=params, json=body,
                             headers=headers, timeout=30)
        if r.status_code == 429:  # rate limited: wait Retry-After seconds, then resend the same request
            time.sleep(float(r.headers.get("Retry-After") or 5))
            continue
        payload = r.json()
        if not r.ok or payload.get("code") != 0:
            raise RuntimeError(f"HTTP {r.status_code} {payload.get('errorCode')}: {payload.get('message')}")
        return payload["data"]
    raise RuntimeError("still rate limited after 5 tries")


def cheapest_offer(country, service):
    data = call("GET", "/catalog/offers", params={"country": country, "service": service})
    in_stock = [o for o in data["offers"] if o["stock"] > 0]
    if not in_stock:
        sys.exit("No stock for this country/service right now.")
    return min(in_stock, key=lambda o: o["price"])


def wait_for_code(order_id, max_wait=600):
    deadline = time.time() + max_wait
    while time.time() < deadline:
        order = call("GET", f"/orders/{order_id}", auth=True)
        status = order["status"]
        if status == "received":
            return order
        if status in ("canceled", "timeout", "failed", "review", "finished"):
            sys.exit(f"Order ended with status={status}, refunded={order.get('refunded')}")
        time.sleep(5)  # the docs ask for at least 5 seconds between queries
    sys.exit("No SMS yet; cancel the order or check it again later.")


def main():
    country, service = "usa", "telegram"  # IDs come from GET /catalog
    offer = cheapest_offer(country, service)
    order_body = {
        "country": country,
        "service": service,
        "operator": offer["operator"],
        "expectedPrice": offer["price"],
        "idempotencyKey": uuid.uuid4().hex,  # keep it: reuse it if you retry this same purchase
    }
    print("Cheapest offer:", offer)
    if "--buy" not in sys.argv:
        print("Dry run. Would POST /orders with:", order_body)
        return

    order = call("POST", "/orders", body=order_body, auth=True)  # spends real balance
    if order["status"] not in ("pending", "waiting"):
        sys.exit(f"Not waiting for SMS: status={order['status']}")  # HTTP 200 alone is not success
    print("Number:", order["phone"])

    order = wait_for_code(order["id"])
    print("Code:", order["code"])
    call("POST", f"/orders/{order['id']}/finish", auth=True)


if __name__ == "__main__":
    main()

运行:

pip install requests
export SMSPING_API_KEY="你的密钥"     # 账户设置 > API 管理
python receive_sms.py                 # 演练:只显示报价和将要提交的订单
python receive_sms.py --buy           # 真正购买(扣真实余额)并等待验证码

注意事项

  • 密钥只能访问所属账户的数据;重置、停用密钥,或修改密码、邮箱后,旧密钥会失效。
  • 号码是一次性激活号码。购买前请确认目标服务接受你选择的号码类型。

常见问题

查价格需要 API 密钥吗?

不需要。/catalog 和 /catalog/offers 是公开的;订单、短信和余额接口需要 Bearer 密钥。

购买请求超时了,要换新的幂等键重试吗?

不要。用同一个 idempotencyKey 和同样的参数重试,并检查已有订单。只有新的、独立的购买才用新键。

多久可以查一次短信?

至少间隔 5 秒,同时不超过账户每分钟 60 次的限制。

准备开始?

登录平台生成 API 密钥,并在开发者文档里核对当前接口、字段和限额。购买前,请确认所选号码类型被目标服务接受。

打开开发者文档