卡密兑换 API
API v1返回兑换页
COMMERCIAL INTEGRATION

把兑换能力接入你自己的网页

使用已授权的卡密,你的网站后端即可调用本 API。凭证仅用于服务端鉴权,不会在兑换页面或接口响应中展示。

Base URLhttps://你的域名/api/v1
01

安全原则

接口只面向客户的服务端,不允许浏览器直接携带卡密访问。

不要在网页 JavaScript、HTML、浏览器 LocalStorage、URL 参数或截图中放入卡密。卡密等同于兑换凭证。前端只请求你的业务后端;你的业务后端再请求本 API。
服务端鉴权仅接受 Authorization: Bearer,不接受查询参数中的卡密。
会话隔离每个请求检查卡密与会话归属,不能读取其他卡密的会话。
最小暴露响应不返回原始卡密、资源库路径、供应商 Key、后台数据或内部 SMS Token。
无跨域直连API 不开放 CORS;浏览器跨域无法直接带卡密调用。
02

鉴权方式

已授权卡密即为这套 API 的 Bearer 凭证。

每个 API 请求均带同一张卡密。卡密只应保存在你的服务端环境变量、加密数据库字段或用户会话服务中;不要将其返回到浏览器。

HTTP Header
Authorization: Bearer KAMI-XXXX-XXXX-XXXX-XXXX
Content-Type: application/json
会话隔离:同一张卡密只能访问它创建的兑换会话与对应领取结果。
03

5 分钟接入

以 Node.js 18+ 的业务后端为例。

  1. 1
    保存凭证

    后端保存 KAMI_API_BASE 和用户的 KAMI_CARD_CODE。

  2. 2
    创建或恢复会话

    调用 POST /sessions;重复调用同一卡密会恢复未完成会话。

  3. 3
    按步骤推进

    以 current_step 为准:review/info 继续,confirm 明确确认,输入步骤传用户内容。

  4. 4
    领取结果

    JSON 走带鉴权下载;SMS 使用同一会话下的取码与换号接口。

server.mjs
const baseUrl = process.env.KAMI_API_BASE; // https://api.example.com/api/v1
const cardCode = process.env.KAMI_CARD_CODE; // 仅服务端保存

async function kami(path, options = {}) {
  const response = await fetch(`${baseUrl}${path}`, {
    ...options,
    headers: { authorization: `Bearer ${cardCode}`, 'content-type': 'application/json', ...(options.headers || {}) }
  });
  const body = await response.json();
  if (!response.ok || !body.ok) throw new Error(body.error?.message || 'API 请求失败');
  return body.data;
}

const { session } = await kami('/sessions', { method: 'POST' });
console.log(session.id, session.current_step);
04

兑换流程

不要假设所有卡密的流程都一样,始终读取接口返回的 current_step。

POST/sessions创建 / 恢复
→
POST/steps推进当前步骤
→
GET/sessions/:id读取状态
→
GET / POST结果接口下载或取码
推进步骤
// review / info / delivery:payload 可为空
await kami(`/sessions/${sessionId}/steps`, { method: 'POST', body: JSON.stringify({ payload: {} }) });

// confirm:必须由你的业务逻辑明确确认
await kami(`/sessions/${sessionId}/steps`, { method: 'POST', body: JSON.stringify({ payload: { confirmed: true } }) });

// text / textarea:key 由 current_step.key 返回
await kami(`/sessions/${sessionId}/steps`, { method: 'POST', body: JSON.stringify({ payload: { value: userInput } }) });
05

接口列表

所有路径都相对 Base URL,并且每个请求都需要 Bearer 卡密。

方法路径说明
POST/sessions创建或恢复兑换会话
GET/sessions/:sessionId查询会话、步骤和领取状态
POST/sessions/:sessionId/steps推进 review / confirm / input / delivery
GET/sessions/:sessionId/download下载 JSON ZIP(二进制响应)
GET/sessions/:sessionId/sms读取号码与验证码状态
POST/sessions/:sessionId/sms/fetch向上游查询一次验证码
POST/sessions/:sessionId/sms/swap未收码前申请换号

创建会话

cURL
curl --request POST 'https://你的域名/api/v1/sessions' \\
  --header 'Authorization: Bearer KAMI-XXXX-XXXX-XXXX-XXXX' \\
  --header 'Content-Type: application/json'

下载 JSON

下载接口直接返回 ZIP 文件流,不返回 JSON。服务端下载时必须继续附带 Authorization Header;不要把卡密写到下载链接。

Node.js 下载
const response = await fetch(`${baseUrl}/sessions/${sessionId}/download`, {
  headers: { authorization: `Bearer ${cardCode}` }
});
if (!response.ok) throw new Error('下载失败');
const zipBuffer = Buffer.from(await response.arrayBuffer());
// 按业务需要保存或转发 zipBuffer;不要把卡密写入下载链接。
06

SMS 取码与换号

SMS 也必须在服务端轮询;每个会话只操作自己的号码。

轮询间隔至少 4 秒。接口保护上游号码服务;过快请求可能返回 throttled: true 或 429。收到验证码后立即停止轮询。
轮询验证码
async function waitForSms(sessionId) {
  for (let attempt = 0; attempt < 30; attempt += 1) {
    const { sms, new_code } = await kami(`/sessions/${sessionId}/sms/fetch`, { method: 'POST', body: JSON.stringify({}) });
    if (new_code && sms.code) return sms.code;
    await new Promise((resolve) => setTimeout(resolve, 4000));
  }
  throw new Error('等待验证码超时,请展示重试或换号入口');
}

只有 sms.can_swap === true 时才允许换号。已收码、平台未开放取消窗口或号码池无可用号码时会拒绝;不要无条件重试换号。

07

请求限制

限制按 60 秒窗口计算。遇到 429 时,等待 Retry-After 或错误中的秒数后重试。

鉴权失败30 / IP / 分钟防止猜测卡密
创建会话20 / 卡密 / 分钟创建或恢复
状态查询120 / 卡密 / 分钟会话与号码状态
流程推进60 / 卡密 / 分钟步骤确认与领取
SMS 操作30 / 卡密 / 分钟取码与换号共用
文件下载30 / 卡密 / 分钟ZIP 下载
08

返回与错误格式

除下载接口外,所有响应都为 JSON。

成功响应
{
  "ok": true,
  "data": {
    "resumed": false,
    "session": {
      "id": "session_xxx",
      "status": "active",
      "current_step": { "type": "confirm", "title": "确认兑换" },
      "delivery": null
    }
  }
}
错误响应
{
  "ok": false,
  "error": {
    "code": "rate_limited",
    "message": "SMS 请求过于频繁,请稍后再试",
    "details": { "retry_after_seconds": 12 }
  }
}
invalid_card_credential卡密缺失、格式错误或不存在session_not_found会话不属于当前卡密,或不存在card_redeemed卡密已兑换且不能重复领取inventory_unavailable当前库存不足delivery_not_ready资源尚未领取完成operation_failed流程、上游号码或换号失败;查看 message 决定下一步
09

上线检查

以下项目完成后再向客户开放 API。

  • 使用 HTTPS 域名;反向代理正确设置 X-Forwarded-Proto,并开启 TRUST_PROXY=true。
  • 后端日志、监控和异常上报必须脱敏 Authorization。
  • 客户前端只能调用客户自己的后端,绝不直连本 API。
  • 网络超时后先 GET /sessions/:id 查询状态,不要盲目重复推进步骤或换号。
  • 正确处理 429、409、502,为用户提供可重试提示。
  • 发布前用测试卡完成 JSON 或 SMS 全流程联调。