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 URL | http://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-Type | application/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 Key | X-API-Key: csk_xxx | 对接方业务调用(提交/查询订单) | 长期,撤销即失效 |
| Bearer Token | Authorization: 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
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
account | string | ✅ | 3–32 位,仅字母/数字/下划线 |
password | string | ✅ | 6–72 字节 |
invite_code | string | ✅ | 站长下发的邀请码 |
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"
}
相关错误
| HTTP | code | 触发 |
|---|---|---|
| 400 | INVALID_REQUEST | 请求体不是合法 JSON |
| 400 | INVALID_ACCOUNT | 账号格式不符 |
| 400 | INVALID_PASSWORD | 密码长度不符 |
| 400 | INVITE_CODE_REQUIRED | 未传邀请码(实测线上行为) |
| 400 | INVALID_INVITE_CODE | 邀请码无效/过期 |
| 403 | REGISTRATION_CLOSED | 当前暂停注册(受 registration_open 开关控制) |
| 409 | ACCOUNT_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)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
codes | string[] | ✅ | 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/orders | POST /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/query | GET /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_codes | string[] | ✅ | 1–200 个。这些就是本次提交的"钱" |
links | string[] | ✅ | 1–max_batch(当前 200)条,须匹配白名单(同 §3.1.2) |
ats | string[] | ✅ | 必须与 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` 前缀) —— 后续凭它免登录查询订单状态。
错误码(均为本实例实测原文)
| HTTP | code | 触发 | 实测报文 |
|---|---|---|---|
| 400 | INVALID_REQUEST | 请求体不是对象/非法 JSON | 请求格式错误 |
| 400 | INVALID_PAYMENT_LINK | 存在不在白名单的链接 | 存在无效的支付链接 |
| 400 | INVALID_LINK_COUNT | links 空或超上限 | 每次必须提交 1-200 条链接 |
| 400 | AT_REQUIRED | 未提供 ats | 每个支付链接都必须提供对应的 AccessToken(AT) |
| 400 | AT_COUNT_MISMATCH | ats 与 links 数量不等 | AccessToken 行数必须与支付链接一一对应 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失或长度非 8–80 | Idempotency-Key 长度必须为 8-80 个字符 |
| 400 | INVALID_CDK_COUNT | cdk_codes 空或超 200 | 每次必须提交 1-200 个卡密 |
| 400 | INVALID_CDK | 卡密不存在 / 格式错 | 卡密不存在或格式错误 |
| 409 | CDK_UNAVAILABLE | 卡密已用尽 / 禁用 / 过期 | — |
| 409 | CDK_USES_INSUFFICIENT | 卡内剩余次数 < 链接数 | — |
| 409 | DUPLICATE_LINK | 链接已被提交过 | — |
| 429 | RATE_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 字符,同批重试必须复用同一值 |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
links | string[] | ✅ | 1–max_batch(当前 200)条,须匹配白名单 |
ats | string[] | ✅ | 必须与 `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)提交规则
| 场景 | 结果(实测) |
|---|---|
完全不传 ats | 400 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_uses | CDK 提交后的剩余次数 |
idempotent | true 表示命中幂等,未新建订单 |
orders[].at_email | 从 AT 解析出的邮箱(初始为空,验活后才回填) |
orders[].settled_on_dead_at | true = 因 AT 确认失效而结算 |
3.1.5 提交相关错误码(实测)
| HTTP | code | 触发条件 |
|---|---|---|
| 400 | INVALID_REQUEST | 请求体非法 JSON |
| 400 | INVALID_LINK_COUNT | links 为空或超上限 |
| 400 | INVALID_PAYMENT_LINK | 存在不在白名单的链接 |
| 400 | AT_REQUIRED | 未提供 `ats` |
| 400 | AT_COUNT_MISMATCH | ats 与 links 数量不等 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失或长度非 8–80 |
| 400 | INVALID_CDK | 卡密不存在/格式错误 |
| 400 | DUPLICATE_CDK | 本次提交中卡密重复 |
| 409 | INSUFFICIENT_BALANCE | 余额(含 CDK 折算)不足 |
| 409 | DUPLICATE_LINK | 链接已提交过(含本批内重复) |
| 409 | CDK_UNAVAILABLE | 卡密已用尽/禁用/过期 |
| 409 | CDK_USES_INSUFFICIENT | 卡密剩余次数 < 链接数 |
| 409 | BALANCE_INCONSISTENT | 冻结余额异常(需联系管理员) |
| 429 | RATE_LIMITED | 超频(响应含 Retry-After) |
3.2 查询我的订单
GET /api/v1/client/orders?page=1&page_size=20(X-API-Key)
| 查询参数 | 默认 | 上限 |
|---|---|---|
page | 1 | — |
page_size | 20 | 100(超出被截断) |
响应:
{
"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_codes | string[] | ✅ 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_codes | string[] | ✅ 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/overview | GET | 余额 / 单价 / CDK 购买地址 |
/api/v1/user/orders | POST | 提交订单(与 `/client/orders` 同契约,需 {links, ats}) |
/api/v1/user/orders | GET | 我的订单(page / page_size ≤100) |
/api/v1/user/order-batches | GET | 我的批次列表(含 batch_no / input_count / order_count / total_amount) |
/api/v1/user/orders/:order_no/cancel | POST | 取消订单(pending/claimed/processing 可取消,自动退费) |
/api/v1/user/ledger | GET | 余额流水 |
/api/v1/user/withdrawals | POST / GET | 申请 / 查询提现 |
/api/v1/user/cdks/redeem | POST | CDK 充值 |
/api/v1/user/api-keys | GET / POST | 列出 / 创建 API Key |
/api/v1/user/api-keys/:id/revoke | POST | 撤销 API Key |
/api/v1/user/order-availability | GET | 接单可用性 |
提现请求体(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(本地判定) | ✅ | ✅ |
invalid | JWT 不可解析 / HTTP 401 | ✅ | ✅ |
expired | JWT exp 已过期(本地判定) | ✅ | ✅ |
normal | AT 正常(200/204 或越过认证层的 4xx) | ❌(=存活) | 🚫 |
review | HTTP 403,人工复核,不认定死亡 | ❌ | 🚫 |
rate_limited | HTTP 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 通用 / 鉴权
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_REQUEST | 请求格式错误 |
| 401 | UNAUTHORIZED | 未提供凭证 |
| 401 | INVALID_TOKEN | Token 失效,请重新登录 |
| 401 | INVALID_API_KEY | API Key 无效或已撤销 |
| 403 | FORBIDDEN | 无权访问该资源 |
| 403 | USER_DISABLED | 账号已被禁用 |
| 404 | NOT_FOUND | 资源不存在 |
| 429 | RATE_LIMITED | 请求过于频繁(含 Retry-After) |
| 500 | INTERNAL_ERROR | 服务端内部错误 |
7.2 注册 / 登录
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_ACCOUNT | 账号格式不符(3–32 位字母/数字/下划线) |
| 400 | INVALID_PASSWORD | 密码长度不符 |
| 400 | INVITE_CODE_REQUIRED | 未传邀请码 |
| 400 | INVALID_INVITE_CODE | 邀请码无效或过期 |
| 400 | CURRENT_CREDENTIALS_INVALID | 当前用户名或旧密码错误 |
| 401 | INVALID_CREDENTIALS | 账号或密码错误 |
| 403 | REGISTRATION_CLOSED | 当前已暂停注册 |
| 409 | ACCOUNT_EXISTS | 账号已存在 |
7.3 提交订单
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_LINK_COUNT | 链接数不在 1–max_batch |
| 400 | INVALID_PAYMENT_LINK | 链接不在白名单 |
| 400 | AT_REQUIRED | 未提供 `ats` |
| 400 | AT_COUNT_MISMATCH | ats 与 links 数量不等 |
| 400 | INVALID_IDEMPOTENCY_KEY | 幂等键缺失/长度错误 |
| 400 | INVALID_CDK | 卡密不存在或格式错误 |
| 400 | DUPLICATE_CDK | 本批中卡密重复 |
| 400 | CDK_REQUIRED | 未提供充值码 |
| 400 | TOO_MANY_CDKS | 充值码超 200 |
| 400 | INVALID_CDK_COUNT | 卡密数不在 1–200 |
| 400 | INVALID_SUPPLIER_AMOUNT | 供货商金额格式错误(管理端) |
| 409 | INSUFFICIENT_BALANCE | 余额不足 |
| 409 | DUPLICATE_LINK | 链接已提交过 |
| 409 | CDK_UNAVAILABLE | 卡密已用尽/禁用/过期 |
| 409 | CDK_USES_INSUFFICIENT | 卡密剩余次数不足 |
| 409 | CDK_MERGE_FAILED | CDK 合并失败 |
| 409 | CDK_REFUND_UNAVAILABLE | 卡密次数返还失败 |
| 409 | SUPPLIER_NOT_ACCEPTING | 未开启接单 |
| 409 | BALANCE_INCONSISTENT | 冻结余额异常 |
7.4 订单查询 / 操作
| HTTP | code | 说明 |
|---|---|---|
| 404 | ORDER_NOT_FOUND | 订单/回执不存在 |
| 404 | AT_NOT_FOUND | 该订单未保存 AT |
| 409 | ORDER_NOT_AVAILABLE | 订单已被其他供货商接走 |
| 409 | INVALID_ORDER_STATUS | 当前状态不允许此操作 |
| 403 | FORBIDDEN | 无权操作该订单 |
| 409 | AT_STILL_ALIVE | AT 仍存活,判定支付失败,不予结算 |
| 409 | AT_INDETERMINATE | AT 状态无法确认,请稍后重试 |
| 400 | FAILURE_REASON_REQUIRED | 未填写失败原因 |
7.5 提现
| HTTP | code | 说明 |
|---|---|---|
| 400 | INVALID_AMOUNT | 提现金额格式错误 |
| 400 | INVALID_WITHDRAWAL | 提现信息错误 |
| 409 | WALLET_NOT_FOUND | 余额账户不存在 |
| 409 | SUPPLIER_ACCOUNT_NOT_FOUND | 供货商资金账户不存在 |
| 409 | WITHDRAWAL_REVIEWED | 提现单已处理 |
| 404 | WITHDRAWAL_NOT_FOUND | 提现单不存在 |
| 422 | INSUFFICIENT_BALANCE | 可用余额不足(用户端) |
8. 频率限制
| 范围 | 限制 |
|---|---|
对外业务 /client/* | 120 次/分钟(按来源 IP) |
全局兜底 /api/v1/* | 300 次/分钟 |
POST /public/scan/orders | 10 次/分钟 |
POST /public/scan/cdks/query | 30 次/分钟 |
POST /public/scan/cdks/merge | 10 次/分钟 |
GET /public/scan/orders/:receipt | 120 次/分钟 |
/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_batch | 200 | 单批链接上限 |
at_required | true | AT 强制:为 true 时必须传 ats |
available | true | 当前是否可接单 |
accepting_supplier_count | 14 | 在线接单人数 |
countdown_seconds | 480 | 工人接单倒计时(秒) |
cdk_purchase_url | https://wzyp.cn/shop/66666SMS | CDK 购买地址 |
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_MISMATCH | ats 与 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_ALIVE | AT 仍存活 = 支付未成功,属正常拒结算;确认该 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 |
| 支付链接白名单 | 仅 nicepay | nicepay + 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*