供货端接单 API
版本 v1.0 · 更新于 2026-10-29 · 接口前缀 /api/v1/open/v1
1. 这是什么
用你自己的网站或程序直接接平台的订单,不必登录工作台:
- 拉取「可接订单」列表;
- 领取订单(claim),拿到这一单的支付链接;
- 完成后提交结果(complete),平台自动结算到你账户余额。
谁能用
| 角色 | 能否申请 API Key | 说明 |
|---|---|---|
| 老板(is_boss) | 可以 | 最多 5 个 Key |
| 独立供货商(无上级) | 可以 | 最多 5 个 Key |
| 工人(挂在老板名下) | 不可以 | 接口会返回 WORKER_NOT_ALLOWED;工人请继续用工作台 |
2. 快速开始
- 登录供货端工作台 → 左侧「API 接口」→ 创建密钥。
完整密钥只显示这一次,关闭后无法再次查看,请立刻保存。 - 开启接单:
POST /accepting,请求体{"enabled": true}。
没开启接单时,可接订单接口返回409 SUPPLIER_NOT_ACCEPTING,心跳也不会生效。 - 每 10–30 秒轮询
GET /orders/available,发现订单就POST /orders/{order_no}/claim, 领取成功后立刻GET /orders/{order_no}/detail取支付链接。
2.1 创建密钥时容易踩的三个坑
| 现象 | 原因 | 怎么处理 |
|---|---|---|
| 点了「创建密钥」没反应 | 名称没填 | 名称必填,最多 50 字 |
400 INVALID_IP_SPEC | IP 白名单里有非法项 | 只支持 1.2.3.4 或 1.2.3.0/24,多个用英文逗号分隔;不支持域名,也不支持 1.2.3.4-1.2.3.9 这类区间 |
400 API_KEY_LIMIT | 未撤销的密钥已经有 5 个 | 先到「API 接口」页撤销不用的,再创建新的 |
3. 认证
每个请求都要带密钥,两种写法任选其一:
X-API-Key: ssk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer ssk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
4. 基础约定
| 项目 | 约定 |
|---|---|
| Base URL | https://jmlscan.cloud-ip.cc/api/v1/open/v1 |
| 协议 | 只支持 HTTPS。HTTP 会被 301 跳转。 |
| 请求体 | Content-Type: application/json,UTF-8。写接口即使无参数也要发 {}。 |
| 金额 | 一律是字符串,如 "0.5"、"32.4"。请用十进制类型解析,不要用浮点累加。 |
| 时间 | RFC3339,UTC,如 2026-10-29T05:23:36Z。 |
| 分页 | ?page=1&page_size=20,page_size 上限 100,默认 20。 |
| 响应信封 | 成功 {"code":"OK","data":{…},"message":"success"};失败 {"code":"错误码","message":"说明"} |
| 邮箱 | 出于隐私,邮箱一律掩码,如 wr***r@i***m。请勿依赖完整邮箱做业务判断。 |
5. 接口清单
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /account | 账户概览:余额、接单开关、进行中订单数、单价 |
| POST | /accepting | 开启 / 关闭接单,请求体 {"enabled":true} |
| POST | /heartbeat | 心跳保活(建议 30–60 秒一次)。仅在开启接单时有效 |
| POST | /idle-ack | 确认「挂机提醒」,避免被自动下线 |
| GET | /orders/available | 可接订单列表 |
| GET | /orders/mine | 我已领取的订单 |
| GET | /orders/{order_no}/detail | 单个订单详情(领取后可取支付链接) |
| POST | /orders/{order_no}/claim | 领取订单 |
| POST | /orders/{order_no}/start | 开始处理(领到单后马上调) |
| POST | /orders/{order_no}/complete | 提交完成,请求体 {"result":"..."} |
| POST | /orders/{order_no}/fail | 标记失败,请求体 {"reason":"..."} |
| POST | /orders/{order_no}/cancel | 放弃已领取但未开始的订单 |
| GET | /ledger | 收益流水 |
| GET | /notice | 平台公告 |
提现、工人管理、团队订单等接口不对外开放,请在工作台操作。调用这些路径会返回 404 NOT_FOUND。
GET /account
{
"code": "OK",
"data": {
"accepting_orders": true,
"active_orders": 1,
"balance": "32.4",
"frozen_balance": "0",
"total_earned": "185.1",
"total_withdrawn": "152.7",
"pending_withdrawal_amount": "0",
"supplier_unit_price": "0.5",
"is_boss": true,
"is_worker": false,
"claim_ban_minutes": 0,
"claim_blocked_until": null,
"countdown_seconds": 480
},
"message": "success"
}
GET /orders/available
{
"code": "OK",
"data": {
"items": [
{
"order_no": "O20261029ZN5RTLNNC6YSHSX6",
"status": "pending",
"at_email": "pr***s@i***m",
"link": "https://payments.stripe.com/••••••••",
"link_locked": true,
"unit_price": "0.5",
"supplier_amount": "0.5",
"charge_amount": "0.5",
"fulfillment_mode": "supplier",
"source": "guest",
"created_at": "2026-10-29T05:26:24Z",
"claimed_at": null,
"started_at": null
}
],
"pagination": { "page": 1, "page_size": 20, "total": 12, "total_pages": 1 }
},
"message": "success"
}
link_locked: true 表示还不能用;
必须先把这一单 claim 到自己名下,再调 GET /orders/{order_no}/detail 取真实链接。6. 接单流程与状态机
pending ──claim──▶ claimed ──start──▶ processing ──complete──▶ completed
│ │
│ └──fail──▶ failed
└──cancel──▶ cancelled
| 状态 | 含义 | 你的下一步 |
|---|---|---|
pending | 未领取,在公共池里 | 可以 claim |
claimed | 已被你领取,尚未开始 | 立刻 start |
processing | 处理中,开始计时 | 完成后 complete 或 fail |
completed | 已完成,通常已结算 | — |
failed | 已标记失败 | — |
cancelled | 已取消(你放弃或平台取消) | — |
必须注意的四条规则
- claim 之前必须
accepting=true,否则409 SUPPLIER_NOT_ACCEPTING。 - claim 成功要立刻 start。只 claim 不 start 不计入处理时长,容易被判超时。
- 一个订单只能被一个人领到。并发 claim 时只有一个人成功,另一个会拿到订单状态相关的错误——按错误码处理即可,不要重试同一个单号。
- complete 的 result 要写清楚(例如支付成功凭据、截图地址或说明)。平台会据此判定是否结算,返回里带
settled。
complete 请求与响应
POST /orders/O20261029JOPJIGQPDEIC5ZBO/complete
{"result": "Payment done, screenshot: https://your-site.com/s/abc123.png"}
{
"code": "OK",
"data": {
"order": { "order_no": "O20261029JOPJIGQPDEIC5ZBO", "status": "completed", "result": "…" },
"settled": true
},
"message": "success"
}
settled=true 表示这一单的收益已入账。
7. 代码示例
curl
export KEY="ssk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export BASE="https://jmlscan.cloud-ip.cc/api/v1/open/v1"
# 1) 开启接单
curl -s -X POST "$BASE/accepting" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"enabled":true}'
# 2) 看可接订单
curl -s "$BASE/orders/available?page=1&page_size=10" -H "X-API-Key: $KEY"
# 3) 领取 + 开始
curl -s -X POST "$BASE/orders/O20261029ZN5RTLNNC6YSHSX6/claim" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" -d '{}'
curl -s -X POST "$BASE/orders/O20261029ZN5RTLNNC6YSHSX6/start" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" -d '{}'
Python (stdlib only)
import json, time, urllib.request, urllib.error
KEY = "ssk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE = "https://jmlscan.cloud-ip.cc/api/v1/open/v1"
def call(method, path, body=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("X-API-Key", KEY)
req.add_header("Content-Type", "application/json")
req.add_header("User-Agent", "my-supplier-bot/1.0") # 务必带上,别用默认 UA
try:
with urllib.request.urlopen(req, timeout=20) as r:
return json.loads(r.read().decode())
except urllib.error.HTTPError as e:
return json.loads(e.read().decode())
call("POST", "/accepting", {"enabled": True})
while True:
res = call("GET", "/orders/available?page=1&page_size=10")
for o in (res.get("data") or {}).get("items", []):
no = o["order_no"]
if call("POST", f"/orders/{no}/claim", {}).get("code") == "OK":
call("POST", f"/orders/{no}/start", {})
detail = call("GET", f"/orders/{no}/detail")
print("拿到订单", no, "支付链接", (detail.get("data") or {}).get("link"))
call("POST", "/heartbeat", {})
time.sleep(15)
PHP
<?php
$key = 'ssk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$base = 'https://jmlscan.cloud-ip.cc/api/v1/open/v1';
function api($method, $path, $body = null) {
global $key, $base;
$ch = curl_init($base . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $key, 'Content-Type: application/json'],
CURLOPT_USERAGENT => 'my-supplier-bot/1.0',
CURLOPT_TIMEOUT => 20,
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$raw = curl_exec($ch);
curl_close($ch);
return json_decode($raw, true);
}
api('POST', '/accepting', ['enabled' => true]);
$res = api('GET', '/orders/available?page=1&page_size=10');
foreach ($res['data']['items'] ?? [] as $o) {
if (api('POST', '/orders/' . $o['order_no'] . '/claim', [])['code'] === 'OK') {
api('POST', '/orders/' . $o['order_no'] . '/start', []);
}
}
Node.js (18+)
const KEY = "ssk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const BASE = "https://jmlscan.cloud-ip.cc/api/v1/open/v1";
async function api(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
});
return res.json();
}
(async () => {
await api("POST", "/accepting", { enabled: true });
const list = await api("GET", "/orders/available?page=1&page_size=10");
for (const o of list?.data?.items ?? []) {
const r = await api("POST", `/orders/${o.order_no}/claim`, {});
if (r.code === "OK") await api("POST", `/orders/${o.order_no}/start`, {});
}
})();
python-urllib、
python-requests、Go-http-client/1.1、curl/7.x、
Java/…、libwww-perl 等默认 UA 会被直接断开(连接被重置、没有响应体)。
本接口已对这些常见 UA 做了放行,但仍建议显式设置一个自己的 UA,避免以后策略调整时受影响。8. 错误码
网关层(本接口自身返回)
| HTTP | code | 含义与处理 |
|---|---|---|
| 401 | MISSING_API_KEY | 没带 Key。检查 X-API-Key 头 |
| 401 | INVALID_API_KEY | Key 无效或已撤销。到工作台确认 |
| 403 | IP_NOT_ALLOWED | 来源 IP 不在白名单里。改 Key 的白名单或换出口 IP |
| 403 | WORKER_NOT_ALLOWED | 工人账号不能调用。请老板申请 |
| 403 | ACCOUNT_DISABLED | 账号已停用 |
| 404 | NOT_FOUND | 路径不存在或不在开放范围 |
| 405 | METHOD_NOT_ALLOWED | 方法不对,例如对 claim 用了 GET |
| 429 | RATE_LIMITED | 调用过于频繁,见第 9 节。请退避重试 |
| 502 | UPSTREAM_UNAVAILABLE | 平台内部暂时不可用,指数退避重试 |
| 503 | DB_ERROR | 平台数据库繁忙,稍后重试 |
业务层(平台原样透传)
| HTTP | code | 含义与处理 |
|---|---|---|
| 409 | SUPPLIER_NOT_ACCEPTING | 没开启接单。先调 POST /accepting {"enabled":true} |
| 404 | ORDER_NOT_FOUND | 单号不存在或不属于你 |
| 409 | SUPPLIER_CLAIM_BLOCKED | 领取被临时封禁(抢单过于频繁等)。看 /account 的 claim_blocked_until,到点再试 |
| 409 | ORDER_NOT_OPEN_YET | 该订单还没到可接时间,稍后再试 |
遇到未列出的错误码时,请以响应里的 message 为准,并保留 code 便于排查。
9. 限流
- 每来源 IP:300 次 / 分钟(边缘层,超过返回 429)
- 每个 Key:600 次 / 分钟
建议的轮询节奏:/orders/available 10–30 秒一次,/heartbeat 30–60 秒一次。
正常接单完全够用。收到 429 时请按 1s → 2s → 4s 退避,不要立刻重试。
10. 常见问题
为什么 /orders/available 一直空的?
先确认 GET /account 里 accepting_orders 是 true;再确认 heartbeat 在正常调用。
平台池子本身也会出现暂时无单的情况,属正常现象。
为什么拿到的是掩码邮箱和掩码链接?
邮箱全天候掩码;支付链接只在领取之后才可读(link_locked 变 false)。
Key 泄露了怎么办?
立刻到工作台「API 接口」撤销该 Key,再建一个新的。撤销是立即生效的,进行中的调用会马上失败。
能不能一个 Key 给多个网站用?
可以,但建议按站点分开建(最多 5 个),这样某一个泄露时只需撤销那一个,也便于按 Key 看调用量。
余额和提现呢?
API 可以查余额和收益流水(/account、/ledger),提现不开放 API,请在工作台操作。