CDK-Scan 对外 API 对接文档

版本 v3.0(对外版) | 出具日期 2026-09-27 | Base URL http://jmlscan.cloud-ip.cc:8088 范围 本文档只覆盖对接方需要的接口(对外业务 / 公开只读 / 账号侧)。 供货商端、管理端等内部接口不在本文档内,也不对外提供。

验证状态:路由面(401/404 探针枚举)与关键错误报文已于 2026-09-27 在本实例重新实测; 其余条目沿用 2026-09-23 的实测存档,未改动。


本文档与旧版(/api-doc)的差异

#差异说明
1移除内部接口旧页面曾公开列出管理端内部接口(含物理删除类操作)。本页面为公网免鉴权访问,这些内容已全部移除,只保留对接方需要的对外接口。
2补全字段级说明旧页面只有"路径 + 一行描述",无请求头表、字段表、校验规则、状态枚举、错误码。本文档全部补齐。
3错误示例为真实报文下文所有错误响应均为本实例实测原文,非手写。
4明确对外接口只有 2 个探针实测:对接方业务接口仅 POST/GET /api/v1/client/orders,其余 client/* 路径均为 404。
5参数标注"以实时接口为准"单价等运行时可变的字段,请读 GET /api/v1/public/scan/config,不要硬编码。

0. 一页速览

项目内容
是否已有对外接口✅ 已有,无需新开发
Base URLhttp://jmlscan.cloud-ip.cc:8088
对接方式两条路,按需选一条(详见 §3.0)<br>A. CDK 直连 —— 只凭 CDK,不用注册 / 邀请码 / API Key<br>B. 账号 API Key —— 需注册(邀请码制)+ 建 Key,频率更高
方式 A 端点POST /api/v1/public/scan/orders 凭证 = 请求体 cdk_codes 频率 10 次/分
方式 B 端点POST /api/v1/client/orders(提交)、GET /api/v1/client/orders(查询) 凭证 = 请求头 X-API-Key 频率 120 次/分
开户方式(仅方式 B 需要)邀请码制:需联系站长获取 invite_code(见 §2.1)
🔴 AccessToken(AT)必填,每条链接对应一个 AT,见 §3.1
🔴 允许的支付链接仅 pay.nicepay.co.kr/* 与 payments.stripe.com/upi/instructions/*
单价0.8 / 条(实时值读 GET /api/v1/public/scan/config)
单批上限200 条
幂等强制 Idempotency-Key 请求头(8–80 字符)
频率限制对外接口 120 次/分钟(按来源 IP);全局兜底 300 次/分钟
订单超时回收claimed / processing 超 8 分钟未推进 → 自动取消并退费
回调❌ 不支持,需轮询
验证状态2026-09-23 全量实测通过,测试数据零残留

1. 基础约定

1.1 协议与地址

项值
对接地址http://jmlscan.cloud-ip.cc:8088 ← 本文档全部实测基于此实例
传输协议HTTP
数据格式JSON,UTF-8
Content-Typeapplication/json
请求体上限1 MiB(超出 → body 读取失败)

⚠️ `scan.fishmail.vip` 是另一套独立部署实例,不是本文档对接对象。 所有可变参数请运行时从 GET /api/v1/public/scan/config 读取,不要硬编码。

1.2 统一响应信封

成功

{ "code": "OK", "data": { }, "message": "success" }

失败(无 data 字段)

{ "code": "AT_REQUIRED", "message": "每个支付链接都必须提供对应的 AccessToken(AT)" }

判断依据:HTTP 状态码 + `code` 字段。code == "OK" 为成功,其余为业务错误。

1.3 时间与金额格式

类型格式示例
时间RFC3339 / UTC,秒级"2026-09-23T13:20:31Z"
时间(空值)null,或空字符串 ""(管理端部分字段)"completed_at": null
金额字符串十进制,最多 4 位小数"0.8"、"1.6"

⚠️ 金额是字符串,请用 Decimal / BigDecimal 解析,勿直接转浮点。

1.4 请求追踪

可选请求头 X-Request-ID(≤80 字符)。服务端原样回显;不传则生成 32 位十六进制串。

报障时请提供该值,可据此精确定位日志。

1.5 响应安全头(实测)

X-Request-ID: <32位hex>
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: same-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'

1.6 CORS

允许来源由服务端白名单控制(当前仅放行站长自用来源,第三方网页前端调用需先与站长确认加入白名单)。

允许的请求头:

Authorization, Content-Type, Idempotency-Key, X-API-Key, X-Request-ID

允许方法:GET, POST, OPTIONS。暴露响应头:X-Request-ID。预检返回 204。

💡 服务端到服务端调用不受 CORS 限制,推荐对接方走服务端调用。


2. 鉴权体系

凭证携带方式用途有效期
API KeyX-API-Key: csk_xxx对接方业务调用(提交/查询订单)长期,撤销即失效
Bearer TokenAuthorization: Bearer <token>账号管理(建 Key、查余额、兑换 CDK)30 天(expires_in ≈ 2591999 秒)

对接方业务代码只需 API Key;Bearer Token 仅开户/运维使用。

2.1 开户流程

Step 1|获取邀请码并注册

🔴 `invite_code` 为必填(v1.2 预告的"免邀请码"未上线)。

POST /api/v1/auth/register

字段类型必填说明
accountstring✅3–32 位,仅字母/数字/下划线
passwordstring✅6–72 字节
invite_codestring✅站长下发的邀请码
curl -X POST http://jmlscan.cloud-ip.cc:8088/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"account":"your_account","password":"YourPass123456","invite_code":"CDK-XXXXXXXX"}'

成功 201:

{
  "code": "OK",
  "data": {
    "access_token": "eyJhbGciOiJIUzI1NiIs...",
    "token_type": "Bearer",
    "expires_at": "2026-10-23T13:13:38Z",
    "expires_in": 2591999,
    "user": { "id": 414, "account": "your_account", "role": "user", "created_at": "2026-09-23T13:13:38Z" }
  },
  "message": "success"
}

相关错误

HTTPcode触发
400INVALID_REQUEST请求体不是合法 JSON
400INVALID_ACCOUNT账号格式不符
400INVALID_PASSWORD密码长度不符
400INVITE_CODE_REQUIRED未传邀请码(实测线上行为)
400INVALID_INVITE_CODE邀请码无效/过期
403REGISTRATION_CLOSED当前暂停注册(受 registration_open 开关控制)
409ACCOUNT_EXISTS账号已存在

⚠️ 区分两个开关:registration_open = true 表示"注册未暂停";它不代表"可以不传邀请码"。两者独立。

Step 2|创建 API Key

POST /api/v1/user/api-keys(Bearer)

curl -X POST http://jmlscan.cloud-ip.cc:8088/api/v1/user/api-keys \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"my-integration"}'

成功 201(`secret` 仅此一次返回,请立即保存):

{
  "code": "OK",
  "data": {
    "id": 22,
    "name": "my-integration",
    "prefix": "csk_OkiZ7JfxdN2c…",
    "secret": "csk_OkiZ7JfxdN2clWZ27bO4nICIwv5HkjRfQbqFKX7LCrQ",
    "last_used_at": "",
    "created_at": "2026-09-23T13:20:31Z"
  },
  "message": "success"
}

每个账号最多 5 个 key(max_api_keys = 5),超出 → 409 API_KEY_LIMIT。

撤回:POST /api/v1/user/api-keys/:id/revoke → {"revoked": true}

Step 3|兑换 CDK 充值

POST /api/v1/user/cdks/redeem(Bearer)

字段类型必填说明
codesstring[]✅1–200 个充值码
{
  "code": "OK",
  "data": {
    "redeemed_count": 2,
    "credited_amount": "1.6",
    "balance": "1.6",
    "results": [
      { "code": "CDK-XXXXXXXXXXXXXXXX", "status": "redeemed", "message": "兑换成功", "amount": "0.8" }
    ]
  }
}

错误:400 CDK_REQUIRED(空数组)、400 TOO_MANY_CDKS(>200)、409 WALLET_NOT_FOUND。

Step 4|查询余额

GET /api/v1/user/overview(Bearer)→ {"balance":"1.6","frozen_balance":"0","unit_price":"0.8","cdk_purchase_url":"https://..."}


3. 接口详情

3.0 先看这个:两种提交方式,怎么选

对接方有两条路,凭证不同、扣费方式不同:

方式 A:CDK 直连(最省事)方式 B:账号 API Key
端点POST /api/v1/public/scan/ordersPOST /api/v1/client/orders
凭证CDK 本身(放在请求体 cdk_codes 里)请求头 X-API-Key: csk_...
要不要注册账号❌ 不用✅ 要(且注册需邀请码,见 §2.1)
要不要邀请码❌ 不用✅ 要
扣费来源扣 CDK 的剩余次数扣 账号余额(余额由 CDK 兑换而来)
频率限制⚠️ 10 次/分钟(严)120 次/分钟
查订单GET /api/v1/public/scan/orders/:receipt(凭回执号,免登录)GET /api/v1/client/orders(按账号列出)
余额查询查 CDK 剩余次数:POST /api/v1/public/scan/cdks/queryGET /api/v1/user/overview
适合一次性/低量对接、不想开户长期、高频、需要账号级账务

✅ 只想"拿卡密直接提交",用方式 A 就够 —— 不需要注册、不需要邀请码、不需要 API Key。

⚠️ 注意方式 A 的频率只有 10 次/分钟(按来源 IP),批量提交会被 429 RATE_LIMITED。

高频对接请走方式 B。

方式 A:POST /api/v1/public/scan/orders(CDK 直连)

鉴权:无(不需要任何请求头凭证) 频率:10 次/分钟

请求头必填说明
Content-Type✅application/json
Idempotency-Key✅8–80 字符。同一批重试必须复用同一个值
X-API-Key / Authorization❌不需要
字段类型必填说明
cdk_codesstring[]✅1–200 个。这些就是本次提交的"钱"
linksstring[]✅1–max_batch(当前 200)条,须匹配白名单(同 §3.1.2)
atsstring[]✅必须与 links 等长、一一对应(规则同 §3.1.3)
curl -X POST http://jmlscan.cloud-ip.cc:8088/api/v1/public/scan/orders \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: my-order-20260927-0001' \
  -d '{
    "cdk_codes": ["CDK-XXXXX-XXXXX-XXXXX-XXXXX"],
    "links":     ["https://pay.nicepay.co.kr/v1/payment/abc123"],
    "ats":       ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
  }'

成功响应:结构与 §3.1.4 相同(batch_no / receipt / orders[] 等)。 差别:本入口扣的是 CDK 次数,响应里的 balance / frozen_balance 反映的是该 CDK 所属账号(无绑定账号时为 0);请以 `remaining_uses` 判断卡内剩余次数。

提交后请保存返回的 `receipt`(`R` 前缀) —— 后续凭它免登录查询订单状态。

错误码(均为本实例实测原文)

HTTPcode触发实测报文
400INVALID_REQUEST请求体不是对象/非法 JSON请求格式错误
400INVALID_PAYMENT_LINK存在不在白名单的链接存在无效的支付链接
400INVALID_LINK_COUNTlinks 空或超上限每次必须提交 1-200 条链接
400AT_REQUIRED未提供 ats每个支付链接都必须提供对应的 AccessToken(AT)
400AT_COUNT_MISMATCHats 与 links 数量不等AccessToken 行数必须与支付链接一一对应
400INVALID_IDEMPOTENCY_KEY幂等键缺失或长度非 8–80Idempotency-Key 长度必须为 8-80 个字符
400INVALID_CDK_COUNTcdk_codes 空或超 200每次必须提交 1-200 个卡密
400INVALID_CDK卡密不存在 / 格式错卡密不存在或格式错误
409CDK_UNAVAILABLE卡密已用尽 / 禁用 / 过期—
409CDK_USES_INSUFFICIENT卡内剩余次数 < 链接数—
409DUPLICATE_LINK链接已被提交过—
429RATE_LIMITED超 10 次/分钟请求过于频繁,请稍后再试

★ 实测确认的校验顺序(排查问题时可据此定位卡在哪一步):

请求体结构 → links 白名单 → links 数量 → ats 数量
          → Idempotency-Key 长度 → cdk_codes 数量 → CDK 有效性
          → (之后)CDK 剩余次数 / 链接占用 / 派单

💡 例:同时写错链接和漏传幂等键时,会先报 INVALID_PAYMENT_LINK,不会报幂等键问题。


3.1 提交支付链接(核心接口)

POST /api/v1/client/orders 鉴权:`X-API-Key` 频率:120 次/分钟

POST /api/v1/user/orders 为同逻辑的 Bearer 版本,契约完全一致。

3.1.1 请求

请求头必填说明
X-API-Key✅csk_...
Content-Type✅application/json
Idempotency-Key✅8–80 字符,同批重试必须复用同一值
字段类型必填说明
linksstring[]✅1–max_batch(当前 200)条,须匹配白名单
atsstring[]✅必须与 `links` 等长、一一对应
curl -X POST http://jmlscan.cloud-ip.cc:8088/api/v1/client/orders \
  -H "X-API-Key: csk_xxx" \
  -H "Idempotency-Key: order-20260923-0001" \
  -H 'Content-Type: application/json' \
  -d '{
    "links": ["https://pay.nicepay.co.kr/v1/payment/abc123"],
    "ats":   ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."]
  }'

3.1.2 🔴 支付链接白名单(实测)

服务端正则:

^(?:https?://(?:www\.)?pay\.nicepay\.co\.kr/.+|https?://payments\.stripe\.com/upi/instructions/.+)$
链接是否通过
https://pay.nicepay.co.kr/v1/payment/xxx✅
https://payments.stripe.com/upi/instructions/xxx✅
https://www.pay.nicepay.co.kr/...✅
https://checkout.stripe.com/c/pay/cs_live_xxx🚫 400 INVALID_PAYMENT_LINK
其它任意域名🚫 400 INVALID_PAYMENT_LINK

3.1.3 🔴 AccessToken(AT)提交规则

场景结果(实测)
完全不传 ats400 AT_REQUIRED
ats 数量 ≠ links 数量400 AT_COUNT_MISMATCH
每条链接配一个 AT(1:1)✅ 通过校验,进入余额/CDK 扣减
ats 中夹空字符串按缺失处理,整批拒绝

AT 是 ChatGPT 的 access_token(JWT,形如 eyJhbGciOi...)。一个链接对应一个 AT,不可批量复用。

AT 会在订单被工人「完成」时自动验活:确定失效(invalid/expired/missing)= 支付成功 = 可结算;仍存活 = 支付失败 = 不结算。

3.1.4 响应(成功 201)

{
  "code": "OK",
  "data": {
    "batch_no": "B20260923MONSTERXXXXXXXXX",
    "order_count": 1,
    "total_amount": "0.8",
    "balance": "0.8",
    "frozen_balance": "0.8",
    "idempotent": false,
    "receipt": "R20260923DCVVXKWRDR6GB3",
    "remaining_uses": 9,
    "orders": [
      {
        "order_no": "O20260923LGUUI2BG7L62D42Q",
        "batch_id": 301,
        "source": "client",
        "fulfillment_mode": "supplier",
        "customer_id": 414,
        "supplier_id": null,
        "link": "https://pay.nicepay.co.kr/v1/payment/abc123",
        "status": "pending",
        "unit_price": "0.8",
        "charge_amount": "0.8",
        "supplier_amount": "0.6",
        "result": "",
        "failure_reason": "",
        "has_at": true,
        "at_email": "",
        "at_status": "",
        "at_probe_status": "",
        "at_probe_error": "",
        "at_proxy_used": "",
        "at_probe_count": 0,
        "at_checked_at": null,
        "settled_on_dead_at": false,
        "claimed_at": null,
        "started_at": null,
        "completed_at": null,
        "created_at": "2026-09-23T13:25:00Z"
      }
    ]
  },
  "message": "success"
}

字段说明

字段说明
batch_no批次号(前缀 B)
receipt★ 回执号(前缀 `R`),用于后续免登录查询,请保存
balance / frozen_balance提交后余额 / 冻结额(仅提交响应有)
remaining_usesCDK 提交后的剩余次数
idempotenttrue 表示命中幂等,未新建订单
orders[].at_email从 AT 解析出的邮箱(初始为空,验活后才回填)
orders[].settled_on_dead_attrue = 因 AT 确认失效而结算

3.1.5 提交相关错误码(实测)

HTTPcode触发条件
400INVALID_REQUEST请求体非法 JSON
400INVALID_LINK_COUNTlinks 为空或超上限
400INVALID_PAYMENT_LINK存在不在白名单的链接
400AT_REQUIRED未提供 `ats`
400AT_COUNT_MISMATCHats 与 links 数量不等
400INVALID_IDEMPOTENCY_KEY幂等键缺失或长度非 8–80
400INVALID_CDK卡密不存在/格式错误
400DUPLICATE_CDK本次提交中卡密重复
409INSUFFICIENT_BALANCE余额(含 CDK 折算)不足
409DUPLICATE_LINK链接已提交过(含本批内重复)
409CDK_UNAVAILABLE卡密已用尽/禁用/过期
409CDK_USES_INSUFFICIENT卡密剩余次数 < 链接数
409BALANCE_INCONSISTENT冻结余额异常(需联系管理员)
429RATE_LIMITED超频(响应含 Retry-After)

3.2 查询我的订单

GET /api/v1/client/orders?page=1&page_size=20(X-API-Key)

查询参数默认上限
page1—
page_size20100(超出被截断)

响应:

{
  "code": "OK",
  "data": {
    "items": [ { /* 同 3.1.4 的 order 结构 */ } ],
    "pagination": { "page": 1, "page_size": 20, "total": 0, "total_pages": 0 }
  },
  "message": "success"
}

⚠️ 本接口无搜索/筛选参数,只有 page / page_size。需按订单号精查请用下方回执查询或在工作台操作。


3.3 凭回执号查询提交结果(免登录)

GET /api/v1/public/scan/orders/:receipt 无需鉴权 频率:120 次/分钟

:receipt 必须为提交时返回的 `R` 前缀回执号。

传入结果(实测)
R20260923...(正确回执)✅ 200,返回批次 + 订单列表
B20260923...(批次号)🚫 404 ORDER_NOT_FOUND
O20260923...(订单号)🚫 404 ORDER_NOT_FOUND
任意不存在值🚫 404 ORDER_NOT_FOUND

响应结构同 §3.1.4,但以下字段不返回(因其仅提交时计算):

字段提交响应回执查询
balance✅❌ 无
frozen_balance✅❌ 无
remaining_uses✅❌ 无
idempotent✅❌ 无
batch_no / receipt / orders[]✅✅

💡 这是 v1.2 文档未区分之处。回执查询只反映订单状态,不反映余额;查余额请用 GET /user/overview。


3.4 公开只读接口(无需鉴权)

3.4.1 提交配置

GET /api/v1/public/scan/config

{
  "code": "OK",
  "data": {
    "unit_price": "0.8",
    "supplier_unit_price": "0.6",
    "max_batch": 200,
    "available": true,
    "accepting_supplier_count": 18,
    "cdk_purchase_url": "https://wzyp.cn/shop/66666SMS",
    "announcement": "<中文公告>",
    "announcements": { "zh": "...", "en": "...", "ja": "...", "hi": "..." },
    "countdown_seconds": 480,
    "at_required": true
  },
  "message": "success"
}

🔴 `at_required: true` — 对接方应在启动时读取此字段做自检;若为 true 而本地仍未传 ats,所有提交将被 400 拒绝。

3.4.2 提交统计

GET /api/v1/public/scan/stats → {"total_orders":777,"completed_orders":434}

3.4.3 查询 CDK 余额(免登录)

POST /api/v1/public/scan/cdks/query 频率:30 次/分钟

字段类型必填
cdk_codesstring[]✅ 1–200 个
{
  "code": "OK",
  "data": {
    "cards": [
      { "code": "CDK-*****-*****-*****-NXOMT", "total_uses": 5, "remaining_uses": 3, "status": "active" }
    ],
    "total_remaining_uses": 3
  }
}

返回的 code 为掩码形式。status:active | redeemed | disabled。

错误:400 INVALID_CDK_COUNT(数量不在 1–200)、400 INVALID_CDK(卡密无效)。

3.4.4 合并 CDK(免登录)

POST /api/v1/public/scan/cdks/merge 频率:10 次/分钟

把多张小额卡合并为一张。要求 2–20 个 CDK,且各项属性一致。

字段类型必填
cdk_codesstring[]✅ 2–20 个

成功 201:

{
  "code": "OK",
  "data": {
    "cdk": "CDK-XXXXXXXXXXXXXXXXXXXX",
    "masked_code": "CDK-*****-*****-*****-ABCDE",
    "remaining_uses": 8,
    "merged_count": 3,
    "source_cdks": ["CDK-*****-...", "CDK-*****-...", "CDK-*****-..."]
  },
  "message": "success"
}

错误:400 INVALID_CDK_MERGE(数量不在 2–20)、409 CDK_MERGE_FAILED(属性不一致等)。

⚠️ 合并后源卡进入 disabled 状态(表示已被合并吸收,并非失效作废)。

3.4.5 探活

GET /health → {"code":"OK","data":{"status":"up"},"message":"success"}


3.5 工作台(/user/)接口(Bearer)

接口方法说明
/api/v1/user/overviewGET余额 / 单价 / CDK 购买地址
/api/v1/user/ordersPOST提交订单(与 `/client/orders` 同契约,需 {links, ats})
/api/v1/user/ordersGET我的订单(page / page_size ≤100)
/api/v1/user/order-batchesGET我的批次列表(含 batch_no / input_count / order_count / total_amount)
/api/v1/user/orders/:order_no/cancelPOST取消订单(pending/claimed/processing 可取消,自动退费)
/api/v1/user/ledgerGET余额流水
/api/v1/user/withdrawalsPOST / GET申请 / 查询提现
/api/v1/user/cdks/redeemPOSTCDK 充值
/api/v1/user/api-keysGET / POST列出 / 创建 API Key
/api/v1/user/api-keys/:id/revokePOST撤销 API Key
/api/v1/user/order-availabilityGET接单可用性

提现请求体(POST /user/withdrawals):{"amount":"1.6","method":"...","account":"..."}


4. 完整路由清单(实测枚举)

以下为 2026-09-23 对该实例逐条探测得到的完整路由面(404 者不在表中)。

4.1 公开(无鉴权)

GET    /health
GET    /api/v1/public/scan/config
GET    /api/v1/public/scan/stats
POST   /api/v1/public/scan/orders                      10 次/分
GET    /api/v1/public/scan/orders/:receipt            120 次/分
POST   /api/v1/public/scan/cdks/query                  30 次/分
POST   /api/v1/public/scan/cdks/merge                  10 次/分

4.2 认证

POST   /api/v1/auth/register                           20 次/分
POST   /api/v1/auth/login                              20 次/分
GET    /api/v1/auth/me
POST   /api/v1/auth/logout
POST   /api/v1/auth/change-credentials

4.3 用户(Bearer,role ∈ {user, admin})

GET    /api/v1/user/overview
GET    /api/v1/user/ledger
POST   /api/v1/user/cdks/redeem
GET    /api/v1/user/withdrawals
POST   /api/v1/user/withdrawals
GET    /api/v1/user/order-availability
GET    /api/v1/user/api-keys
POST   /api/v1/user/api-keys
POST   /api/v1/user/api-keys/:id/revoke
POST   /api/v1/user/orders
GET    /api/v1/user/orders
GET    /api/v1/user/order-batches
POST   /api/v1/user/orders/:order_no/cancel

4.4 对外业务(API Key)

POST   /api/v1/client/orders                          120 次/分
GET    /api/v1/client/orders                          120 次/分

4.5 静态页面

路径内容
/工作台(免登录提交页)
/scan/免登录提交页(多语言 + CDK 融合面板)
/user/工作台(与 / 同一文件)
/admin/管理后台
/supplier/供货商端
/pay/支付/CDK 管理页(含 CDK 融合入口)
/api-doc本 API 文档页

5. 订单状态机

pending ──claim──▶ claimed ──start──▶ processing ──complete(AT判死)──▶ completed
   │                  │                    │
   │                  │                    └──fail──▶ failed
   │                  │
   └────cancel────────┴────────cancel──────┴──────────────▶ cancelled
状态含义可否取消
pending待接单✅
claimed已被工人接单✅
processing工人处理中✅
completed已完成(AT 确认失效 + 已结算)❌
failed失败(AT 仍存活 / 工人标记失败)❌
cancelled已取消(自动退费)❌

超时回收:claimed / processing 超过 480 秒未推进 → 自动取消并退费(order_timeout_minutes = 8)。

5.1 AT 状态枚举(8 值)

值含义是否算「确定失效」可否结算
missing无 AT(本地判定)✅✅
invalidJWT 不可解析 / HTTP 401✅✅
expiredJWT exp 已过期(本地判定)✅✅
normalAT 正常(200/204 或越过认证层的 4xx)❌(=存活)🚫
reviewHTTP 403,人工复核,不认定死亡❌🚫
rate_limitedHTTP 429❌🚫
network超时/连接失败/未知❌🚫
validation_error程序解析异常❌🚫

只有 `missing` / `invalid` / `expired` 三者算「确定失效」,才触发结算。其余状态订单保持 processing 供重试,对应错误码 409 AT_INDETERMINATE。


6. 关键机制说明

6.1 幂等

  • Idempotency-Key 强制,8–80 字符
  • 同 customer_id + 同 key 重复提交 → 返回首次结果,idempotent: true,不重复建单、不重复扣费
  • 重试必须复用同一 key;换 key 视为新提交

重试建议:网络超时/5xx → 用同 key 重试(最多 3 次,指数退避 1s/2s/4s);收到 429 → 按 Retry-After 等待;收到 4xx 业务错误 → 不要重试,修正参数。

6.2 CDK 计费

  • CDK 按「次数」计费,1 条链接消耗 1 次
  • 提交时原子扣减;订单被取消 → 自动返还(cdk_usages.refunded_uses 增加)
  • 某张卡剩余次数不足时,会自动跨卡凑足(多卡联合扣减)
  • 卡状态:active(可用)|redeemed(已用尽)|disabled(已合并到其它卡,非作废)

6.3 AT 验活链路(含本期修复)

工人点「完成」
   ↓
事务外:读取订单快照
   ↓
AT 验活(双层)
   ├─ 本地层:JWT 解析 + exp 检查(不联网)→ missing / invalid / expired
   └─ 在线层:GET 探针端点
        ↓ 经站内本地中继 http://站内中继(Chrome TLS 指纹)
        ↓ 转发至 https://chatgpt.com/backend-api/me
        ├─ 401 → invalid(确定失效 → 可结算)
        ├─ 200/204 → normal(存活 → 不结算)
        ├─ 403 → review(不认定死亡)
        ├─ 429 → rate_limited
        └─ 超时/连接失败 → network
   ↓
判定:确定失效 → 进事务加行锁 → settleFrozen + CreditOrder → completed
     仍存活   → 409 AT_STILL_ALIVE,保持 processing
     不确定   → 409 AT_INDETERMINATE,保持 processing

本期变更:Cloudflare 自 2026-09-23 起对 chatgpt.com 启用 TLS/JA3 指纹挑战,Go 标准库 net/http 的 TLS 指纹不像浏览器 → 一律 403,导致所有 AT 验活失效(换 IP 无效,实测新老代理池与直连全部 403)。

修复方式:站内新增本地中继(curl_cffi 的 Chrome 指纹),AT_PROBE_ENDPOINT 指向 站内中继端点。

对对接方接口契约零影响 —— 但若验活大面积返回 review,请勿反复调用 complete,应联系站长排查中继状态。

6.4 结算口径(重要)

  • 归属账户 = `COALESCE(上级 parent_id, 自己)`(即老板账号承接其名下工人的收益)
  • 恒等式:balance + frozen_balance + total_withdrawn == total_earned
  • 一 CDK 多账号提交时,只结算 1 单,不追讨工人余额
  • 冻结余额在提交时产生;订单终结(completed / failed / cancelled)时解冻

7. 错误码全表

7.1 通用 / 鉴权

HTTPcode说明
400INVALID_REQUEST请求格式错误
401UNAUTHORIZED未提供凭证
401INVALID_TOKENToken 失效,请重新登录
401INVALID_API_KEYAPI Key 无效或已撤销
403FORBIDDEN无权访问该资源
403USER_DISABLED账号已被禁用
404NOT_FOUND资源不存在
429RATE_LIMITED请求过于频繁(含 Retry-After)
500INTERNAL_ERROR服务端内部错误

7.2 注册 / 登录

HTTPcode说明
400INVALID_ACCOUNT账号格式不符(3–32 位字母/数字/下划线)
400INVALID_PASSWORD密码长度不符
400INVITE_CODE_REQUIRED未传邀请码
400INVALID_INVITE_CODE邀请码无效或过期
400CURRENT_CREDENTIALS_INVALID当前用户名或旧密码错误
401INVALID_CREDENTIALS账号或密码错误
403REGISTRATION_CLOSED当前已暂停注册
409ACCOUNT_EXISTS账号已存在

7.3 提交订单

HTTPcode说明
400INVALID_LINK_COUNT链接数不在 1–max_batch
400INVALID_PAYMENT_LINK链接不在白名单
400AT_REQUIRED未提供 `ats`
400AT_COUNT_MISMATCHats 与 links 数量不等
400INVALID_IDEMPOTENCY_KEY幂等键缺失/长度错误
400INVALID_CDK卡密不存在或格式错误
400DUPLICATE_CDK本批中卡密重复
400CDK_REQUIRED未提供充值码
400TOO_MANY_CDKS充值码超 200
400INVALID_CDK_COUNT卡密数不在 1–200
400INVALID_SUPPLIER_AMOUNT供货商金额格式错误(管理端)
409INSUFFICIENT_BALANCE余额不足
409DUPLICATE_LINK链接已提交过
409CDK_UNAVAILABLE卡密已用尽/禁用/过期
409CDK_USES_INSUFFICIENT卡密剩余次数不足
409CDK_MERGE_FAILEDCDK 合并失败
409CDK_REFUND_UNAVAILABLE卡密次数返还失败
409SUPPLIER_NOT_ACCEPTING未开启接单
409BALANCE_INCONSISTENT冻结余额异常

7.4 订单查询 / 操作

HTTPcode说明
404ORDER_NOT_FOUND订单/回执不存在
404AT_NOT_FOUND该订单未保存 AT
409ORDER_NOT_AVAILABLE订单已被其他供货商接走
409INVALID_ORDER_STATUS当前状态不允许此操作
403FORBIDDEN无权操作该订单
409AT_STILL_ALIVEAT 仍存活,判定支付失败,不予结算
409AT_INDETERMINATEAT 状态无法确认,请稍后重试
400FAILURE_REASON_REQUIRED未填写失败原因

7.5 提现

HTTPcode说明
400INVALID_AMOUNT提现金额格式错误
400INVALID_WITHDRAWAL提现信息错误
409WALLET_NOT_FOUND余额账户不存在
409SUPPLIER_ACCOUNT_NOT_FOUND供货商资金账户不存在
409WITHDRAWAL_REVIEWED提现单已处理
404WITHDRAWAL_NOT_FOUND提现单不存在
422INSUFFICIENT_BALANCE可用余额不足(用户端)

8. 频率限制

范围限制
对外业务 /client/*120 次/分钟(按来源 IP)
全局兜底 /api/v1/*300 次/分钟
POST /public/scan/orders10 次/分钟
POST /public/scan/cdks/query30 次/分钟
POST /public/scan/cdks/merge10 次/分钟
GET /public/scan/orders/:receipt120 次/分钟
/auth/*20 次/分钟
AT 手动验活30 次/分钟

超限响应 429,附 Retry-After: <秒>。

⚠️ 服务端判定客户端 IP 时信任 TRUSTED_PROXIES 白名单;伪造 `X-Forwarded-For` 无效。


9. 联调 Checklist

  • [ ] 已向站长获取 invite_code
  • [ ] 注册成功并保存 access_token
  • [ ] 创建 API Key 并保存 secret(仅一次可见)
  • [ ] 兑换 CDK,确认 GET /user/overview 余额 > 0
  • [ ] 读取 GET /public/scan/config,确认 at_required 与 unit_price
  • [ ] 构造 {links, ats},两者长度一致
  • [ ] 链接确认命中白名单(nicepay 或 stripe upi)
  • [ ] 每次提交带唯一 Idempotency-Key(8–80 字符)
  • [ ] 重试必须复用同一 Idempotency-Key
  • [ ] 保存返回的 receipt,用于后续查询
  • [ ] 轮询间隔建议 ≥ 5 秒,避免触发限流

10. cURL 全流程

BASE=http://jmlscan.cloud-ip.cc:8088

# 1) 注册(invite_code 必填)
curl -s -X POST $BASE/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"account":"my_account","password":"MyPass123456","invite_code":"CDK-XXXXXXXX"}'

# 2) 创建 API Key
TOKEN=<上一步返回的 access_token>
curl -s -X POST $BASE/api/v1/user/api-keys \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"prod"}'

# 3) 兑换 CDK
curl -s -X POST $BASE/api/v1/user/cdks/redeem \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"codes":["CDK-XXXXXXXXXXXXXXXX"]}'

# 4) 提交(★ ats 必填且与 links 一一对应)
KEY=csk_xxxxxxxx
curl -s -X POST $BASE/api/v1/client/orders \
  -H "X-API-Key: $KEY" \
  -H "Idempotency-Key: mybatch-20260923-001" \
  -H 'Content-Type: application/json' \
  -d '{
    "links": ["https://pay.nicepay.co.kr/v1/payment/abc123"],
    "ats":   ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."]
  }'

# 5) 查询(按回执号,免鉴权)
curl -s $BASE/api/v1/public/scan/orders/R20260923XXXXXXXXXXXXXX

# 6) 查询(按账号,API Key)
curl -s "$BASE/api/v1/client/orders?page=1&page_size=20" -H "X-API-Key: $KEY"

11. Python 示例(标准库,无第三方依赖)

import json, time, urllib.request, urllib.error

BASE = "http://jmlscan.cloud-ip.cc:8088"

def call(method, path, body=None, token=None, api_key=None, headers=None):
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(BASE + path, data=data, method=method)
    req.add_header("Content-Type", "application/json")
    if token:
        req.add_header("Authorization", "Bearer " + token)
    if api_key:
        req.add_header("X-API-Key", api_key)
    for k, v in (headers or {}).items():
        req.add_header(k, v)
    try:
        with urllib.request.urlopen(req, timeout=30) as r:
            return r.status, json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        return e.code, json.loads(e.read().decode())

# 1) 提交(每条链接配一个 AT)
def submit(api_key, links, ats, idem_key):
    assert len(links) == len(ats), "links 与 ats 必须一一对应"
    status, data = call("POST", "/api/v1/client/orders",
                        {"links": links, "ats": ats},
                        api_key=api_key,
                        headers={"Idempotency-Key": idem_key})
    if status != 201:
        raise RuntimeError(f"提交失败 {status}: {data}")
    return data["data"]["receipt"]

# 2) 轮询(按回执号,免鉴权)
def poll(receipt, interval=5, timeout=480):
    deadline = time.time() + timeout
    while time.time() < deadline:
        status, data = call("GET", f"/api/v1/public/scan/orders/{receipt}")
        if status != 200:
            raise RuntimeError(f"查询失败 {status}: {data}")
        orders = data["data"]["orders"]
        if all(o["status"] in ("completed", "failed", "cancelled") for o in orders):
            return orders
        time.sleep(interval)
    raise TimeoutError("轮询超时")

if __name__ == "__main__":
    API_KEY = "csk_xxxxxxxx"
    links = ["https://pay.nicepay.co.kr/v1/payment/abc123"]
    ats   = ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...."]

    receipt = submit(API_KEY, links, ats, "mybatch-20260923-001")
    print("receipt:", receipt)
    for o in poll(receipt):
        print(o["order_no"], o["status"], "at_status=", o.get("at_status"))

12. 运行时参数(2026-09-27 实测)

以下值全部来自 `GET /api/v1/public/scan/config`,会随运营调整而变。

字段2026-09-27 实测说明
unit_price"0.8"每条链接的扣费(字符串,请用 Decimal 解析)
max_batch200单批链接上限
at_requiredtrueAT 强制:为 true 时必须传 ats
availabletrue当前是否可接单
accepting_supplier_count14在线接单人数
countdown_seconds480工人接单倒计时(秒)
cdk_purchase_urlhttps://wzyp.cn/shop/66666SMSCDK 购买地址
announcement(中文文本)站点公告
announcements{zh,en,ja,hi}多语言公告字典

🔴 以上任何一项都不要硬编码。 单价会调、接单人数会变、available 会翻转。

建议:每次提交前读一次 `/config`;available == false 时暂缓提交,避免无谓的 409。

账号级固定限制(不会变,可写死):

项值
每账号 API Key 上限5
Idempotency-Key 长度8–80 字符
单批链接 / 卡密上限200
CDK 合并数量2–20
订单超时回收8 分钟

13. 快速排障

现象原因与处理
400 AT_REQUIRED未传 ats。读 GET /public/scan/config 的 at_required 确认;补齐 AT
400 AT_COUNT_MISMATCHats 与 links 长度不等,逐条对齐
400 INVITE_CODE_REQUIRED注册未带邀请码;联系站长获取(此项并未放开)
400 INVALID_PAYMENT_LINK链接不在白名单;仅接受 pay.nicepay.co.kr/* 或 payments.stripe.com/upi/instructions/*
400 INVALID_IDEMPOTENCY_KEY幂等键缺失或长度不在 8–80
409 INSUFFICIENT_BALANCE余额/CDK 折算不足;兑换 CDK 或减少条数
409 AT_STILL_ALIVEAT 仍存活 = 支付未成功,属正常拒结算;确认该 AT 对应的支付是否真的失败
409 AT_INDETERMINATE验活网络异常/限流/403;稍后重试,勿频繁调用
404 ORDER_NOT_FOUND回执查询传了批次号/订单号;必须传 `R` 前缀回执号
400 INVALID_REQUEST(供货商)开关接单传了 accepting;应传 `enabled`
409 SUPPLIER_NOT_ACCEPTING未开启接单;先 POST /supplier/accepting {"enabled":true}
429 RATE_LIMITED超频;按 Retry-After 等待
探活请求被直接断开若用脚本探活,请带浏览器风格 User-Agent;服务端对异常 UA 会直接断开

14. 当前不支持的能力

能力状态
订单状态回调 / Webhook❌ 需轮询
订单搜索 / 高级筛选参数❌ 仅 page / page_size
HTTPS❌ 当前为 HTTP(如需请与站长确认)
批量取消订单❌ 单条取消
自助修改单价/超时❌ 管理端设置
免邀请码注册❌ 未上线(v1.2 预告作废)

附录 A:本文档的验证方法

为避免"凭记忆写文档",本文档所有条目经三重交叉验证:

1. 路由枚举:对该实例逐条发起真实请求(含不存在路径作对照), 以 HTTP 状态码区分「存在」(401/400/409/200)与「不存在」(404),得到 §4 的路由面。

探针必须带浏览器 UA —— 否则可能被网关直接断连,把全部路由误报成"不存在"。

2. 真实 E2E:注册账号 → 登录 → 建 Key → 提交 → 查询全链路跑通,逐项记录真实响应体。 本文档所有错误响应均为实际触发的报文原文,非手写推测。 3. 字段交叉核对:以线上实测行为为准,与服务端实现逐字段比对。 凡"实现与实测冲突",一律以实测为准并在此登记。

2026-09-27 复核结果(相对 09-23 版):

检查项结果
对外路由面✅ 无变化(对接方业务接口仍为 2 个)
公开只读接口✅ 全部可用
错误码✅ 关键错误报文重新抓取,与 09-23 一致
运行时参数⚠️ 与 09-23 不同(见 §10),再次印证不要硬编码

由此发现并纠正的历史错误:

项旧文档描述实测真相
注册邀请码"已改为可留空,勿传"必填,不传 400 INVITE_CODE_REQUIRED
支付链接白名单仅 nicepaynicepay + Stripe UPI 双前缀
supplier/accepting 字段名未记录`enabled`(非 accepting)
回执查询响应与提交响应同构不含 balance / frozen_balance / remaining_uses
对外 client 接口数量一度被写成 6~8 个实测只有 2 个,其余 client/* 均 404

*文档结束 | v3.0(对外版)| 2026-09-27 | Base URL http://jmlscan.cloud-ip.cc:8088*