API 文档

通过编程方式访问虚拟号码、订单和账户余额。

推荐

从官方 SDK 开始

新集成请使用 TypeScript/JavaScript 或 Python SDK。两个 SDK 默认使用公共 /v2 API,在安全重试中保留幂等键,提供类型化错误,并保持 OTP 生命周期一致。

01

安全创建

使用 product_id 为精确且稳定的价格档位槽创建订单,或使用 catalog_product_id、可选的 operator_idmin_price/max_price 以及幂等键来安全重试路由式付费调用。

catalog_product_idmax_priceIdempotency-Key
import { SmscodeClient, OtpTimeoutError } from "@smscode/sdk";

const client = new SmscodeClient({ token: process.env.SMSCODE_TOKEN! });

let orderId: number | undefined;

try {
  const created = await client.orders.create({
    catalog_product_id: Number(process.env.SMSCODE_CATALOG_PRODUCT_ID),
    max_price: "0.50",
    quantity: 1,
  });

  const order = created.orders[0]!;
  orderId = order.id;
  const first = await client.orders.waitForOtp(orderId, { timeoutMs: 120_000 });

  console.log(first.otpCode); // 在目标应用中提交此验证码。
  await client.orders.finish(orderId);
} catch (err) {
  if (err instanceof OtpTimeoutError && orderId !== undefined) {
    const current = await client.orders.get(orderId);
    if (current.can_cancel) await client.orders.cancel(orderId);
  }
  throw err;
}
import os

from smscode import OtpTimeoutError, SmscodeClient

with SmscodeClient(token=os.environ["SMSCODE_TOKEN"]) as client:
    created = client.orders.create(
        catalog_product_id=int(os.environ["SMSCODE_CATALOG_PRODUCT_ID"]),
        max_price="0.50",
        quantity=1,
    )

    order = created.orders[0]
    order_id = int(order["id"])

    try:
        first = client.orders.wait_for_otp(order_id, timeout_ms=120_000)
        print(first.otp_code)  # 在目标应用中提交此验证码。
        client.orders.finish(order_id)
    except OtpTimeoutError:
        current = client.orders.get(order_id)
        if current["can_cancel"]:
            client.orders.cancel(order_id)
        raise

使用 can_resendresend_available_at 判断重发时机。更底层的重发时间戳属于内部字段,不是公开响应字段。

概述

/v1 API 上的所有金额字段均为 IDR(印尼盾),以整数单位表示——例如 "price": 15000"balance": 500000 表示 Rp 15,000 和 Rp 500,000。如需同一账本的 USD 原生投影,请使用上方的版本切换开关切换到 v2 API。

认证

所有 API 请求均需 Bearer token。在控制台的账户设置中生成令牌,然后在每个请求中包含它:

Authorization:Bearer YOUR_API_TOKEN

缺少有效令牌的请求将收到 401 UNAUTHORIZED 响应。

基础 URL

以下所有端点路径均相对于:

https://api.smscode.gg/v1

响应格式

每个响应返回结构一致的 JSON。所有响应都包含 x-request-id 响应头,用于调试。

成功
{
  "success": true,
  "data": { ... }
}
错误
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

/v1 API 上的所有金额字段均为 IDR(印尼盾),以整数单位表示——例如 "price": 15000"balance": 500000 表示 Rp 15,000 和 Rp 500,000。如需同一账本的 USD 原生投影,请使用上方的版本切换开关切换到 v2 API。

GET/catalog/countries

返回所有可用国家列表。

参数

请求示例

curl -s https://api.smscode.gg/v1/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}
GET/catalog/services

返回可用服务(平台)列表。可按国家筛选。

查询参数

名称类型必填说明
country_idinteger筛选该国家可用的服务

请求示例

curl -s "https://api.smscode.gg/v1/catalog/services?country_id=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=7", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/services",
    params={"country_id": 7},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}
GET/catalog/operators

返回某个国家 + 服务下可选择的运营商。如果真实运营商和 Any 库存同时可用,响应会包含一行 operator_id 为 null 的 Any;如果没有运营商专属产品,列表为空。

查询参数

名称类型必填说明
country_idinteger国家 ID
platform_idinteger平台/服务 ID

请求示例

curl -s "https://api.smscode.gg/v1/catalog/operators?country_id=7&platform_id=3" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", platform_id: "3",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/operators?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/operators",
    params={"country_id": 7, "platform_id": 3},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "operator_id": null,
      "code": "any",
      "name": "Any",
      "local_name": null
    },
    {
      "operator_id": 433,
      "code": "axis",
      "name": "AXIS (XL Axiata)",
      "local_name": "AXIS (XL Axiata)"
    }
  ]
}
GET/catalog/products

返回可用产品的分页列表。可按国家、平台以及可选的运营商筛选。

查询参数

名称类型必填说明
country_idinteger按国家 ID 筛选
platform_idinteger按平台/服务 ID 筛选
operator_idinteger来自 /catalog/operators 的可选运营商 ID。Any 产品请省略。
sortstring排序方式:price_asc(默认)、price_desc、available_asc、available_desc、name_asc、name_desc
limitinteger每页结果数(1-10,000,默认 1,000)
pageinteger页码(最小 1,默认 1)

请求示例

curl -s "https://api.smscode.gg/v1/catalog/products?country_id=7&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/products",
    params={"country_id": 7, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
GET/catalog/exchange-rate

返回用于货币换算的当前 USD/IDR 汇率。

查询参数

名称类型必填说明
pairstring货币对(默认:USD/IDR)

请求示例

curl -s "https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET/balance

返回已认证用户的账户余额。

参数

请求示例

curl -s https://api.smscode.gg/v1/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
GET/orders

返回已认证用户的订单列表,按最近排序。支持按状态筛选和通过 offset 分页。

查询参数

名称类型必填说明
limitinteger最大结果数(1-100,默认 20)
offsetinteger跳过的结果数(默认 0)
statusstring按状态筛选:ACTIVE、OTP_RECEIVED、COMPLETED、CANCELED、EXPIRED(不区分大小写)

请求示例

curl -s "https://api.smscode.gg/v1/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v1/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "catalog_product_id": 87,
      "phone_number": "+6281234567890",
      "amount": 15000,
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null,
      "operator_id": null,
      "operator_name": null
    }
  ]
}
GET/orders/{id}

根据 ID 返回单个订单。仅返回已认证用户的订单。

路径参数

名称类型必填说明
idinteger订单 ID(路径参数)

请求示例

curl -s https://api.smscode.gg/v1/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "catalog_product_id": 87,
    "phone_number": "+6281234567890",
    "amount": 15000,
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null,
    "operator_id": null,
    "operator_name": null
  }
}
GET/orders/active

列出所有当前活跃的订单(ACTIVE + OTP_RECEIVED)。用于轮询 OTP 状态更新。

参数

请求示例

curl -s https://api.smscode.gg/v1/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": null,
      "otp_message": "Confirm your login: https://example.com/confirm",
      "sms_revision": 1,
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "sms_revision": 0,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}
POST/orders/create

创建新的虚拟号码订单。自动扣除余额。支持可选的 Idempotency-Key 请求头,防止网络重试时产生重复订单。

请求体

名称类型必填说明
product_idinteger用于直接下单的稳定精确档位槽产品 ID。请提供它或 catalog_product_id 二选一,不要同时提供。
catalog_product_idinteger路由式国家+平台 umbrella ID。服务器会选择当前匹配的档位。请提供它或 product_id。
operator_idinteger来自 /catalog/operators 的可选运营商 ID。仅与 catalog_product_id 一起有效;Any 请省略。
min_priceinteger可选价格下限。IDR 整数。仅与 catalog_product_id 一起有效。
max_priceinteger可选价格上限。IDR 整数。仅与 catalog_product_id 一起有效。
prefer_providerstring可选的供应商代码,在报价相同时优先选用。
policystring可选的路由策略,仅在使用 catalog_product_id 时有效。取值:cheapest(默认)选择价格最低的健康报价;best_success 优先按近期发送成功率对报价排序。best_success 以每个供应商在最近 30 个完整自然日内收到 OTP 的订单占比来评分,按 10% 一档划分,且只有当该供应商在此区间内至少有 20 笔订单时才计入——低于该门槛或没有历史记录的供应商视为中性,因此新报价不会被埋没(需主动选用;该信号从中性起步)。若同时设置了 prefer_provider,则优先供应商仍排在最前。
quantityinteger数量(1-100,默认 1)

传递 Idempotency-Key 请求头,可安全重试而不会创建重复订单。Key 可包含字母、数字、连字符和下划线(A-Z a-z 0-9 _ -),最多 128 个字符;无效的 key 会返回 422 VALIDATION_ERROR。使用相同的 key 和相同的请求体重试将重放原始结果(包括部分成功时的 failed_count)。已到达供应商但失败的重试会被记录,重试时返回相同的错误 — 请使用新的 key 重新尝试。无副作用的失败(余额不足、无可用报价)会释放该 key,因此你可以充值后用相同的 key 重试。用不同的请求体重复使用某个 key 会返回 422 IDEMPOTENCY_KEY_REUSED,而该 key 仍在处理中的请求会返回 409 REQUEST_IN_PROGRESS。create 响应中的 failed_reason 字段始终为 null — 它仅在订单轮询/列表时填充。

请求示例

curl -s -X POST https://api.smscode.gg/v1/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"product_id":142,"quantity":1}'

# Or route by catalog_product_id — the server picks a current tier.
# product_id is the stable exact tier-slot id. catalog_product_id is the
# country+platform umbrella for routed ordering. Optional min_price/max_price
# bound the tier; operator_id scopes to a carrier from /catalog/operators.
# Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v1/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-124" \
  -d '{"catalog_product_id":87,"min_price":12000,"max_price":20000,"operator_id":433}'
const res = await fetch("https://api.smscode.gg/v1/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  }
}
POST/orders/cancel

取消活跃订单。租用费用将退回账户余额。

请求体

名称类型必填说明
idinteger要取消的订单 ID

请求示例

curl -s -X POST https://api.smscode.gg/v1/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
POST/orders/finish

在收到 OTP 后将订单标记为已完成。这会立即释放号码,而非等待过期。

请求体

名称类型必填说明
idinteger要完成的订单 ID

请求示例

curl -s -X POST https://api.smscode.gg/v1/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
POST/orders/resend

请求平台向租用号码重新发送 SMS。并非所有平台都支持重发 — 请查看响应中的 resent 字段。

请求体

名称类型必填说明
idinteger要重发 SMS 的订单 ID

请求示例

curl -s -X POST https://api.smscode.gg/v1/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
POST/orders/reactivate

重新激活一个已完成的号码 — 无需租用新号码,即可重新订购同一号码以获取另一个验证码。只有号码支持重新激活的已完成订单才符合条件(检查订单上的 can_reactivate,或使用 reactivate-options 预览)。重新激活的子订单是一个新订单,返回格式与 create 相同;余额将自动扣除。

请求体

名称类型必填说明
idinteger要重新激活的已完成订单。
max_priceinteger可选成本上限。IDR 整数。如果当前成本超过该上限,重新激活将返回 422 VALIDATION_ERROR 而被拒绝。

与 create 一样,这是一次涉及资金的变更 — 请传递 Idempotency-Key 请求头以安全重试(create 与 reactivate 绝不会在同一个 key 上冲突)。用不同的请求体重复使用某个 key 会返回 422 IDEMPOTENCY_KEY_REUSED,而该 key 仍在处理中的请求会返回 409 REQUEST_IN_PROGRESS。无法重新激活的号码会返回 409 CONFLICT;余额过低会返回 409 INSUFFICIENT_BALANCE。

请求示例

curl -s -X POST https://api.smscode.gg/v1/orders/reactivate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-901" \
  -d '{"id":1001,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v1/orders/reactivate", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-901",
  },
  body: JSON.stringify({ id: 1001, max_price: 20000 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/reactivate",
    json={"id": 1001, "max_price": 20000},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-901",
    })
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1044,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T11:20:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  }
}
GET/orders/{id}/reactivate-options

预览此刻重新激活将收取的费用。只读 — 不消耗任何 Idempotency-Key,也不创建任何内容。以 IDR 整数返回成本。仅适用于号码支持重新激活的已完成订单。

路径参数

名称类型必填说明
idinteger要预览重新激活成本的订单 ID(路径参数)。

请求示例

curl -s https://api.smscode.gg/v1/orders/1001/reactivate-options \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/1001/reactivate-options", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/1001/reactivate-options",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "cost": 15000
  }
}
GET/webhook

返回您当前的 webhook 通知配置。

参数

请求示例

curl -s https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
PATCH/webhook

更新您的 webhook URL 和/或密钥。首次设置 URL 时会自动生成密钥。发送空字符串可清除。URL 必须使用 HTTPS。

请求体

名称类型必填说明
webhook_urlstring接收 webhook 事件的 HTTPS URL(空字符串表示清除)
webhook_secretstring用于 HMAC-SHA256 签名的共享密钥(首次设置时自动生成)

至少需要一个字段。

请求示例

curl -s -X PATCH https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v1/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
POST/webhook/test

向已配置的 webhook URL 发送测试事件。返回服务器响应的 HTTP 状态码。可用于在正式使用前验证端点是否正常工作。

参数

请求示例

curl -s -X POST https://api.smscode.gg/v1/webhook/test \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook/test", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

Webhook 通知

配置 webhook URL 以接收订单事件的实时推送通知,无需轮询。这是机器人脚本的推荐方式。

事件列表

事件触发条件
order.otp_received收到新短信;解析出的验证码可能为 null
order.completed订单已标记为完成(手动或到期)
order.expired订单在收到任何短信前过期(余额已退还)
order.canceled订单被用户取消(余额已退还)

每条新短信都会触发此事件。otp_message 存在时,otp_code 可能为 null。多条短信事件可能乱序到达;请使用 sms_revision 忽略较旧的聚合字段对。

载荷

Webhook POST 请求体
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": null,
    "otp_message": "Confirm your login: https://example.com/confirm",
    "sms_revision": 1,
    "product_id": 142,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

签名验证

每个 webhook 请求都包含 X-Webhook-Signature 请求头,其中包含使用您的 webhook_secret 作为密钥对请求体计算的 HMAC-SHA256 签名:

X-Webhook-Signature:sha256={HMAC-SHA256(body, webhook_secret)}

在您的服务器上验证此签名以确保请求的真实性。发送采用即发即忘模式,3 秒超时且不重试。

速率限制

API 请求按端点分组进行速率限制。超出限制将返回 429 Too Many Requests,并附带 Retry-After 响应头,指示需要等待的秒数。

端点分组限制时间窗口
目录(国家、服务、产品、汇率)5,000 次请求60 秒
余额600 次请求60 秒
订单查询(列表、详情、活跃)5,000 次请求60 秒
创建订单3,000 次请求60 秒
取消订单1,000 次请求60 秒
订单操作(完成、重发)1,000 次请求60 秒
Webhook 配置(查询、更新)600 次请求60 秒
Webhook 测试10 次请求60 秒

错误代码

错误响应在 error.code 中包含以下代码之一:

代码HTTP说明
UNAUTHORIZED401缺少或无效的 API 令牌
FORBIDDEN403访问被拒绝
NOT_FOUND404资源未找到(订单、汇率等)
CONFLICT409重复请求或资源冲突
INSUFFICIENT_BALANCE409余额不足,无法创建订单
VALIDATION_ERROR422请求参数验证失败
RATE_LIMIT_EXCEEDED429请求过于频繁(请查看 Retry-After 响应头)
INTERNAL_ERROR500服务器内部错误
PROVIDER_ERROR422上游 SMS 提供商拒绝了请求。订单创建失败时,错误可能携带 detailscause_counts(旧版 product_id 订单 — 按原因分类的统计)或 attemptscatalog_product_id 订单 — 每次尝试的结果),取值为 okno_numbersinsufficient_balanceprice_rejectedprovider_unavailableprovider_account_balanceprovider_error
NO_OFFER_AVAILABLE422没有符合所请求产品与策略(价格上限、可用性)的有效报价。
CANCEL_TOO_EARLY409订单创建时间过短,无法取消 — 请等待 2 分钟
REQUEST_IN_PROGRESS409使用该幂等 key 的创建请求仍在处理中
IDEMPOTENCY_KEY_REUSED422该幂等 key 已用于不同的请求体
SERVICE_UNAVAILABLE503服务暂时不可用(维护中)

概述

/v2 API 上的所有金额字段均为 USD,以金额对象返回——{ "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }amount 是十进制字符串canonical_amount 是精确的 IDR 账本值(用于对账)。所应用的 USD/IDR rate 在每个响应中通过 meta.fx 披露一次。v2 是在与 v1 相同的 IDR 账本之上的渲染时 USD 投影——它从不存储或结算 USD。

从 v1 迁移到 v2

认证

所有 API 请求均需 Bearer token。在控制台的账户设置中生成令牌,然后在每个请求中包含它:

Authorization:Bearer YOUR_API_TOKEN

缺少有效令牌的请求将收到 401 UNAUTHORIZED 响应。

基础 URL

以下所有端点路径均相对于:

https://api.smscode.gg/v2

响应格式

每个响应返回结构一致的 JSON。所有响应都包含 x-request-id 响应头,用于调试。

成功
{
  "success": true,
  "data": { ... }
}
错误
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

v2 错误:无可用汇率(503)

503 Service Unavailable · Retry-After: 60
{
  "success": false,
  "error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
}

/v2 API 上的所有金额字段均为 USD,以金额对象返回——{ "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }amount 是十进制字符串canonical_amount 是精确的 IDR 账本值(用于对账)。所应用的 USD/IDR rate 在每个响应中通过 meta.fx 披露一次。v2 是在与 v1 相同的 IDR 账本之上的渲染时 USD 投影——它从不存储或结算 USD。

GET/catalog/countries

返回所有可用国家列表。

参数

请求示例

curl -s https://api.smscode.gg/v2/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}

与 v1 相同——仅基础路径改变(/v1/v2)。

GET/catalog/services

返回可用服务(平台)列表。可按国家筛选。

查询参数

名称类型必填说明
country_idinteger筛选该国家可用的服务

请求示例

curl -s "https://api.smscode.gg/v2/catalog/services?country_id=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=7", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/services",
    params={"country_id": 7},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}

与 v1 相同——仅基础路径改变(/v1/v2)。

GET/catalog/operators

返回某个国家 + 服务下可选择的运营商。如果真实运营商和 Any 库存同时可用,响应会包含一行 operator_id 为 null 的 Any;如果没有运营商专属产品,列表为空。

查询参数

名称类型必填说明
country_idinteger国家 ID
platform_idinteger平台/服务 ID

请求示例

curl -s "https://api.smscode.gg/v2/catalog/operators?country_id=7&platform_id=3" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", platform_id: "3",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/operators?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/operators",
    params={"country_id": 7, "platform_id": 3},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "operator_id": null,
      "code": "any",
      "name": "Any",
      "local_name": null
    },
    {
      "operator_id": 433,
      "code": "axis",
      "name": "AXIS (XL Axiata)",
      "local_name": "AXIS (XL Axiata)"
    }
  ]
}

与 v1 相同——仅基础路径改变(/v1/v2)。

GET/catalog/products

返回可用产品的分页列表。可按国家、平台以及可选的运营商筛选。

查询参数

名称类型必填说明
country_idinteger按国家 ID 筛选
platform_idinteger按平台/服务 ID 筛选
operator_idinteger来自 /catalog/operators 的可选运营商 ID。Any 产品请省略。
sortstring排序方式:price_asc(默认)、price_desc、available_asc、available_desc、name_asc、name_desc
limitinteger每页结果数(1-10,000,默认 1,000)
pageinteger页码(最小 1,默认 1)

请求示例

curl -s "https://api.smscode.gg/v2/catalog/products?country_id=7&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/products",
    params={"country_id": 7, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

GET/catalog/exchange-rate

返回用于货币换算的当前 USD/IDR 汇率。

参数

无——v2 始终返回 USD/IDR;v1 的 ?pair 参数被忽略。

请求示例

curl -s "https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
}

v2:返回 { pair, rate, rate_as_of }(无 base_currency/quote_currency,无 meta 包装——汇率本身就是数据)。?pair 被忽略——v2 始终返回 USD/IDR(v1 会遵循 ?pair)。如果不存在可用汇率,则返回 503 FX_RATE_UNAVAILABLE

GET/balance

返回已认证用户的账户余额。

参数

请求示例

curl -s https://api.smscode.gg/v2/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

GET/orders

返回已认证用户的订单列表,按最近排序。支持按状态筛选和通过 offset 分页。

查询参数

名称类型必填说明
limitinteger最大结果数(1-100,默认 20)
offsetinteger跳过的结果数(默认 0)
statusstring按状态筛选:ACTIVE、OTP_RECEIVED、COMPLETED、CANCELED、EXPIRED(不区分大小写)

请求示例

curl -s "https://api.smscode.gg/v2/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v2/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "catalog_product_id": 87,
      "phone_number": "+6281234567890",
      "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null,
      "operator_id": null,
      "operator_name": null
    }
  ],
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

GET/orders/{id}

根据 ID 返回单个订单。仅返回已认证用户的订单。

路径参数

名称类型必填说明
idinteger订单 ID(路径参数)

请求示例

curl -s https://api.smscode.gg/v2/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "catalog_product_id": 87,
    "phone_number": "+6281234567890",
    "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null,
    "operator_id": null,
    "operator_name": null
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

GET/orders/active

列出所有当前活跃的订单(ACTIVE + OTP_RECEIVED)。用于轮询 OTP 状态更新。

参数

请求示例

curl -s https://api.smscode.gg/v2/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": null,
      "otp_message": "Confirm your login: https://example.com/confirm",
      "sms_revision": 1,
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "sms_revision": 0,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}

v2:此 endpoint 涉及金额——它不返回 amount,也不返回 meta.fx(结构与 v1 相同,位于 /v2 下)。

POST/orders/create

创建新的虚拟号码订单。自动扣除余额。支持可选的 Idempotency-Key 请求头,防止网络重试时产生重复订单。

请求体

名称类型必填说明
product_idinteger用于直接下单的稳定精确档位槽产品 ID。请提供它或 catalog_product_id 二选一,不要同时提供。
catalog_product_idinteger路由式国家+平台 umbrella ID。服务器会选择当前匹配的档位。请提供它或 product_id。
operator_idinteger来自 /catalog/operators 的可选运营商 ID。仅与 catalog_product_id 一起有效;Any 请省略。
min_pricestring可选价格下限。USD 小数字符串(例如 "0.30")。仅与 catalog_product_id 一起有效。
max_pricestring可选价格上限。USD 小数字符串(例如 "0.50")。仅与 catalog_product_id 一起有效。
prefer_providerstring可选的供应商代码,在报价相同时优先选用。
policystring可选的路由策略,仅在使用 catalog_product_id 时有效。取值:cheapest(默认)选择价格最低的健康报价;best_success 优先按近期发送成功率对报价排序。best_success 以每个供应商在最近 30 个完整自然日内收到 OTP 的订单占比来评分,按 10% 一档划分,且只有当该供应商在此区间内至少有 20 笔订单时才计入——低于该门槛或没有历史记录的供应商视为中性,因此新报价不会被埋没(需主动选用;该信号从中性起步)。若同时设置了 prefer_provider,则优先供应商仍排在最前。
quantityinteger数量(1-100,默认 1)

传递 Idempotency-Key 请求头,可安全重试而不会创建重复订单。Key 可包含字母、数字、连字符和下划线(A-Z a-z 0-9 _ -),最多 128 个字符;无效的 key 会返回 422 VALIDATION_ERROR。使用相同的 key 和相同的请求体重试将重放原始结果(包括部分成功时的 failed_count)。已到达供应商但失败的重试会被记录,重试时返回相同的错误 — 请使用新的 key 重新尝试。无副作用的失败(余额不足、无可用报价)会释放该 key,因此你可以充值后用相同的 key 重试。用不同的请求体重复使用某个 key 会返回 422 IDEMPOTENCY_KEY_REUSED,而该 key 仍在处理中的请求会返回 409 REQUEST_IN_PROGRESS。create 响应中的 failed_reason 字段始终为 null — 它仅在订单轮询/列表时填充。

请求示例

curl -s -X POST https://api.smscode.gg/v2/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"catalog_product_id":87,"min_price":"0.30","max_price":"0.50","operator_id":433,"quantity":1}'
const res = await fetch("https://api.smscode.gg/v2/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({
    catalog_product_id: 87,
    min_price: "0.30",
    max_price: "0.50",
    operator_id: 433,
    quantity: 1,
  }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/create",
    json={
        "catalog_product_id": 87,
        "min_price": "0.30",
        "max_price": "0.50",
        "operator_id": 433,
        "quantity": 1,
    },
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

POST/orders/cancel

取消活跃订单。租用费用将退回账户余额。

请求体

名称类型必填说明
idinteger要取消的订单 ID

请求示例

curl -s -X POST https://api.smscode.gg/v2/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

POST/orders/finish

在收到 OTP 后将订单标记为已完成。这会立即释放号码,而非等待过期。

请求体

名称类型必填说明
idinteger要完成的订单 ID

请求示例

curl -s -X POST https://api.smscode.gg/v2/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}

与 v1 相同——仅基础路径改变(/v1/v2)。

POST/orders/resend

请求平台向租用号码重新发送 SMS。并非所有平台都支持重发 — 请查看响应中的 resent 字段。

请求体

名称类型必填说明
idinteger要重发 SMS 的订单 ID

请求示例

curl -s -X POST https://api.smscode.gg/v2/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}

与 v1 相同——仅基础路径改变(/v1/v2)。

POST/orders/reactivate

重新激活一个已完成的号码 — 无需租用新号码,即可重新订购同一号码以获取另一个验证码。只有号码支持重新激活的已完成订单才符合条件(检查订单上的 can_reactivate,或使用 reactivate-options 预览)。重新激活的子订单是一个新订单,返回格式与 create 相同;余额将自动扣除。

请求体

名称类型必填说明
idinteger要重新激活的已完成订单。
max_pricestring可选成本上限。USD 小数字符串(例如 "0.50")。如果当前成本超过该上限,重新激活将返回 422 VALIDATION_ERROR 而被拒绝。

与 create 一样,这是一次涉及资金的变更 — 请传递 Idempotency-Key 请求头以安全重试(create 与 reactivate 绝不会在同一个 key 上冲突)。用不同的请求体重复使用某个 key 会返回 422 IDEMPOTENCY_KEY_REUSED,而该 key 仍在处理中的请求会返回 409 REQUEST_IN_PROGRESS。无法重新激活的号码会返回 409 CONFLICT;余额过低会返回 409 INSUFFICIENT_BALANCE。

请求示例

curl -s -X POST https://api.smscode.gg/v2/orders/reactivate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-901" \
  -d '{"id":1001,"max_price":"0.50"}'
const res = await fetch("https://api.smscode.gg/v2/orders/reactivate", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-901",
  },
  body: JSON.stringify({ id: 1001, max_price: "0.50" }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/reactivate",
    json={"id": 1001, "max_price": "0.50"},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-901",
    })
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1044,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T11:20:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

GET/orders/{id}/reactivate-options

预览此刻重新激活将收取的费用。只读 — 不消耗任何 Idempotency-Key,也不创建任何内容。以带 FX 收据的 USD 金额对象返回成本。仅适用于号码支持重新激活的已完成订单。

路径参数

名称类型必填说明
idinteger要预览重新激活成本的订单 ID(路径参数)。

请求示例

curl -s https://api.smscode.gg/v2/orders/1001/reactivate-options \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/1001/reactivate-options", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/1001/reactivate-options",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "cost": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2:金额字段为 USD 金额对象,且响应携带单个 meta.fx { pair, rate, rate_as_of }rate 是每 1 USD 对应的整数 IDR,因此 USD = canonical_amount / rate。总额使用 2 位小数;单项价格/退款使用 4 位。严格为正的金额绝不会舍入为 0.00rate_as_of 是该汇率的 RFC3339 时间戳(+00:00 形式),若未记录时间戳则为 null

仅 v2:如果不存在可用的 USD/IDR 汇率,金额类 endpoint 将返回 503 FX_RATE_UNAVAILABLE 并带有 Retry-After 响应头,而不是金额响应体。v1 绝不会返回此情况。

GET/webhook

返回您当前的 webhook 通知配置。

参数

请求示例

curl -s https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

与 v1 相同——仅基础路径改变(/v1/v2)。

PATCH/webhook

更新您的 webhook URL 和/或密钥。首次设置 URL 时会自动生成密钥。发送空字符串可清除。URL 必须使用 HTTPS。

请求体

名称类型必填说明
webhook_urlstring接收 webhook 事件的 HTTPS URL(空字符串表示清除)
webhook_secretstring用于 HMAC-SHA256 签名的共享密钥(首次设置时自动生成)

至少需要一个字段。

请求示例

curl -s -X PATCH https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v2/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

与 v1 相同——仅基础路径改变(/v1/v2)。

POST/webhook/test

向已配置的 webhook URL 发送测试事件。返回服务器响应的 HTTP 状态码。可用于在正式使用前验证端点是否正常工作。

参数

请求示例

curl -s -X POST https://api.smscode.gg/v2/webhook/test \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook/test", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

响应示例

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

与 v1 相同——仅基础路径改变(/v1/v2)。

Webhook 通知

配置 webhook URL 以接收订单事件的实时推送通知,无需轮询。这是机器人脚本的推荐方式。

事件列表

事件触发条件
order.otp_received收到新短信;解析出的验证码可能为 null
order.completed订单已标记为完成(手动或到期)
order.expired订单在收到任何短信前过期(余额已退还)
order.canceled订单被用户取消(余额已退还)

每条新短信都会触发此事件。otp_message 存在时,otp_code 可能为 null。多条短信事件可能乱序到达;请使用 sms_revision 忽略较旧的聚合字段对。

载荷

Webhook POST 请求体
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": null,
    "otp_message": "Confirm your login: https://example.com/confirm",
    "sms_revision": 1,
    "product_id": 142,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

签名验证

每个 webhook 请求都包含 X-Webhook-Signature 请求头,其中包含使用您的 webhook_secret 作为密钥对请求体计算的 HMAC-SHA256 签名:

X-Webhook-Signature:sha256={HMAC-SHA256(body, webhook_secret)}

在您的服务器上验证此签名以确保请求的真实性。发送采用即发即忘模式,3 秒超时且不重试。

速率限制

API 请求按端点分组进行速率限制。超出限制将返回 429 Too Many Requests,并附带 Retry-After 响应头,指示需要等待的秒数。

端点分组限制时间窗口
目录(国家、服务、产品、汇率)5,000 次请求60 秒
余额600 次请求60 秒
订单查询(列表、详情、活跃)5,000 次请求60 秒
创建订单3,000 次请求60 秒
取消订单1,000 次请求60 秒
订单操作(完成、重发)1,000 次请求60 秒
Webhook 配置(查询、更新)600 次请求60 秒
Webhook 测试10 次请求60 秒

错误代码

错误响应在 error.code 中包含以下代码之一:

代码HTTP说明
UNAUTHORIZED401缺少或无效的 API 令牌
FORBIDDEN403访问被拒绝
NOT_FOUND404资源未找到(订单、汇率等)
CONFLICT409重复请求或资源冲突
INSUFFICIENT_BALANCE409余额不足,无法创建订单
VALIDATION_ERROR422请求参数验证失败
RATE_LIMIT_EXCEEDED429请求过于频繁(请查看 Retry-After 响应头)
INTERNAL_ERROR500服务器内部错误
PROVIDER_ERROR422上游 SMS 提供商拒绝了请求。订单创建失败时,错误可能携带 detailscause_counts(旧版 product_id 订单 — 按原因分类的统计)或 attemptscatalog_product_id 订单 — 每次尝试的结果),取值为 okno_numbersinsufficient_balanceprice_rejectedprovider_unavailableprovider_account_balanceprovider_error
NO_OFFER_AVAILABLE422没有符合所请求产品与策略(价格上限、可用性)的有效报价。
CANCEL_TOO_EARLY409订单创建时间过短,无法取消 — 请等待 2 分钟
REQUEST_IN_PROGRESS409使用该幂等 key 的创建请求仍在处理中
IDEMPOTENCY_KEY_REUSED422该幂等 key 已用于不同的请求体
SERVICE_UNAVAILABLE503服务暂时不可用(维护中)
FX_RATE_UNAVAILABLE503USD/IDR 汇率不可用(v2 金额类 endpoint)——返回 503 并带有 Retry-After 响应头。
v1 → v2

从 v1 迁移到 v2

v1 提供 IDR;v2 提供 USD。两个版本将永久共存——不会下线。每个集成只选择一个版本;请勿混用基础路径。除金额表示方式外,v2 与 v1 完全相同。

方面v1 · IDRv2 · USD
金额字段整数 IDR,例如 15000金额对象 { amount, currency, canonical_amount, canonical_currency }
meta.fx每个涉及金额的响应均必需
货币IDRUSD(硬编码)
FX_RATE_UNAVAILABLE无可用汇率时新增 503 + Retry-After
精度总额 2 位小数,价格/退款 4 位小数,正数向上取整
GET /catalog/exchange-rate{pair, base_currency, quote_currency, rate};遵循 ?pair{pair, rate, rate_as_of};忽略 ?pair(仅 USD/IDR)

并排示例

GET/balance
v1
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
v2
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}
GET/catalog/products
v1
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

product_id 是稳定的 SMSCode 档位槽 ID。如果你想订购这个精确档位,请保存它;它的价格和可用量可以在同一行内变化。catalog_product_id 是用于路由式下单的稳定国家+平台 umbrella;当你希望服务器选择当前匹配的档位时,可配合可选的 operator_id、min_price、max_price、prefer_provider 和 policy 使用。

POST/orders/cancel
v1
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
v2
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

迁移清单

  1. 将基础路径 /v1/v2
  2. 将金额字段解析为对象——把 amount 读取为十进制字符串;currency"USD"
  3. 对账时请使用 canonical_amount(精确 IDR);USD amount 是渲染时投影,且 ratemeta.fx 中披露一次。
  4. 处理新增的 FX_RATE_UNAVAILABLE(503)——在 Retry-After 后重试。v1 绝不会返回此情况。