锦鲤掌柜 · 卡密开放接口(给 AI 写脚本用)
把这份文档整段交给 AI,说明要做什么(补货、读售出记录、回收),它就能写出脚本。
一览:6 个接口(增删改查)
| 用途 | 接口 | |
|---|---|---|
| 增 | 新建卡种(普通卡 / 循环卡) | POST /v1/stock/kinds |
| 增 | 补货:推送卡密 | POST /v1/stock/push |
| 删 | 撤回未售的卡密 | POST /v1/stock/revoke |
| 改 | 标记售出记录为已回收 | POST /v1/stock/sales/mark |
| 查 | 库存、卡种、加密公钥、撤回结果 | GET /v1/stock/status |
| 查 | 售出记录 | GET /v1/stock/sales |
卡密加密后才上传,服务器看不到内容,所以没有「改内容」:推错了先撤回再补货。只补货用「查」和「补货」两个就够;还没有卡种时先「新建卡种」。
基本
| 项 | 说明 |
|---|---|
| 地址 | https://yzg-relay.aiyuan.dpdns.org |
| 鉴权 | 请求头 Authorization: Bearer jlk_...。令牌在官网「我的账户 → 开发者令牌」或插件「账户 → 开发者」生成,只显示一次 |
| 店铺 | 令牌绑定一家店铺,所有接口只作用于这家店;多家店各用各的令牌 |
| 格式 | JSON,UTF-8;时间均为毫秒时间戳 |
| 限流 | 每个令牌每分钟 60 次;超出返回 429 rate_limited,带 retry_after(秒) |
| 出错 | { "ok": false, "error": "代码", "msg": "中文说明" } |
加密必须
卡密在本机加密后才上传,服务器只存密文,明文会被拒收(bad_ct)。不想自己实现,直接用 push.py 或 push.mjs。
- 取公钥:
GET /v1/stock/status返回的pub(P-256 未压缩公钥,65 字节,base64url) - 临时密钥:每条卡密生成一对新的 P-256 临时密钥
- 共享密钥:ECDH(临时私钥,
pub) - 派生密钥:HKDF-SHA256,salt = 临时公钥 65 字节,info =
jinli-stock-v1,输出 32 字节 - 加密:AES-256-GCM,随机 12 字节 iv,明文为卡密 UTF-8
- 拼接:
v1.<临时公钥>.<iv>.<密文含 tag>,三段均为 base64url
查库存与卡种
GET
/v1/stock/status(可选 ?batch=<补货返回的 batch>)
{ "ok": true, "online": true, "device": "Windows · Chrome",
"pub": "BCpW...",
"kinds": [
{ "id": "k1", "name": "腾讯视频·年卡", "group": "视频会员", "type_name": "普通卡", "unsold": 3, "sold": 10, "warn": 5, "pending": 0,
"format": { "kind": "normal", "content": "text", "cols": ["账号", "密码"], "multi": true, "sep": "dash", "join": "----", "example": "值1----值2", "dup": false } }
],
"revokes": [ { "label": "13800002222", "at": 1760850000000, "done": true, "result": "removed" } ],
"batch": { "batch": "b_x", "pushed": 3, "received": 3, "done": true } }
online:店铺在岗的插件是否在线(在线才会取货)pub:加密公钥(见下方「加密」);账号没开云同步时为 nullkinds:卡种目录。补货用id作kind_id;没有合适的卡种用下方「新建卡种」unsold/sold:插件里的未售 / 已售数(插件在线时上报);pending:云端还没被插件取走的条数type_name:普通卡(一张卖一次)/ 固定卡(一份内容每单都发)/ 循环卡(一批卡轮着发)format:每条卡密该怎么写。multi=false一条 = 一个值;multi=true一条 = 一张卡,按cols顺序用join连起来(join为空时写列名:值 列名:值,见example);pushable=false的卡种(固定卡、图片卡)不走推送revokes:最近 50 条撤回的结果:removed已移除 /sold已卖出未动 /missing插件里没有;done=false表示等插件上线处理batch:传了?batch=才有,done=true表示这批已被插件取走入库
增新建卡种
POST
/v1/stock/kinds{ "name": "Tims 咖啡券", "type": "normal", "cols": ["券码", "密码"], "join": "----", "group": "卡券", "warn": 5 }
name必填,≤30 字;本店已有同名卡种时不新建,直接返回那个(created=false)type:normal普通卡(一张卖一次,默认)/cycle循环卡(一批卡轮着发)。固定卡、图片卡只能在插件里建cols:列名,1–5 个,默认["卡密"];多列时每列都要有名字join:多列时一条卡密怎么连,"----"(默认)/","/" "/"\n"/""(写成列名:值 列名:值)group(分组,可选,≤20 字)、warn(库存预警线,默认 5)- 返回
{ created, kind },kind同「查」里kinds[]的一项。拿到kind.id立刻就能补货;插件在线时约 5 分钟内建好,卡密随后入库 - 改名、改格式、删除在插件里做
增补货
POST
/v1/stock/push{ "kind_id": "k1", "batch_note": "注册批次-20261019",
"items": [ { "ct": "v1....", "label": "13800001111" } ] }
items可以是密文字符串,也可以是{ ct, label }label(标识,可选):这张卡来自哪个账号,比如注册用的手机号。售出记录会带上它,回收、撤回都靠它。明文存储,别放密码或 key;只能字母数字和. : @ # / - _,≤64 位- 单批 ≤500 条;云端待取 ≤20000 条
- 返回
{ batch, accepted };插件在线时约 5 分钟内取走、解密入库,云端随即删除
删撤回未售
POST
/v1/stock/revoke{ "labels": ["13800002222"] }
- 云端还没取走的直接删除;已在插件里的,插件下次在线时移除未售的,已卖出的不动
- 结果在 GET /v1/stock/status 的 revokes 里
查售出记录
GET
/v1/stock/sales?status=sold&kind_id=&since=0&limit=200{ "ok": true, "sales": [
{ "label": "13800001111", "n": 1, "kind_id": "k1", "sold_at": 1760850000000, "expires_at": 1760936400000, "status": "sold", "note": "", "updated_at": 1760850001000 }
], "next_since": 1760850001000, "more": false }
status:sold已售(默认,不含退款)/refunded已退款 /recycled已回收 /alln:第几次卖出(固定卡一张会卖多次)expires_at:按插件里卡种的有效期算,没设为 null- 增量读取:把上次的
next_since作为since传回;more=true继续翻。退款会让记录从sold变refunded,增量能读到 - 不含买家信息和订单号
改标记已回收
POST
/v1/stock/sales/mark{ "labels": ["13800001111"], "note": "已删 key" }
- 也可写
"items": [ { "label": "13800001111", "n": 1 } ]只标某一次卖出 - 标记后插件显示「已回收」,不会再发这张卡;之后的退款不会覆盖回收状态
典型回收脚本
- 定时
GET /v1/stock/sales?status=all&since=<上次> status=refunded,或status=sold且expires_at已过 → 登录账号删除 keyPOST /v1/stock/sales/mark标记已回收- 保存
next_since
