接入指南
1. 开始接入前
版本:1.0.1
文档状态:正式版。实际接入时,请使用商户提供的接口域名、前台用户 ID、API Key 和商品权限。
接入信息包括:
商户的 OpenAPI HTTPS 域名。
采购会员在该商户前台的用户 ID。
绑定到该前台用户 ID 的
API Key。商户实际开放的商品和能力。
接口对接只需要保存三个参数:商户域名、前台用户 ID 和该用户的 API Key。
订单返回的 allowedActions 表示当前还能执行哪些操作。列表里没有的操作不要调用。
2. 基础约定
基础路径格式:
https://{merchant-domain}/openapi/v1通用规则:
编码:UTF-8。
请求与响应:
application/json。时间:使用 10 位 Unix 秒级时间戳,例如
1785216000。金额:统一使用人民币元,最多保留两位小数,例如
100.00表示 100 元,0.01表示 1 分。所有编号都应按照字段表标注的 JSON 类型原样保存和传递。
字段名:默认使用
clientOrderRef这样的 lowerCamelCase 格式;卡密固定使用card_list、card_no、card_password。分页:
page从 1 开始,pageSize默认 20、最大 100。所有请求必须使用 HTTPS。
2.1 编号和返回格式
本文出现的字段就是第三方接入时使用的字段。
accountRef、catalogRef、productRef、variantRef是整数编号;platformOrderRef、refundRef、platformCaseRef是字符串编号。请原样保存和传递,不要自行添加前缀或转换类型。clientOrderRef由第三方生成,在当前采购账户内不能重复用于另一笔订单,同时也是网络超时后的重试编号。字段名、允许填写的值和数据格式以本文及 OpenAPI YAML 为准。
统一响应:
{
"code": 0,
"message": "success",
"data": {},
"requestId": "req_01K0EXAMPLE"
}列表数据:
{
"list": [],
"total": 0,
"page": 1,
"pageSize": 20
}处理响应时要同时检查 HTTP 状态和 code。遇到问题请保存 requestId,方便商户查询日志。
3. 身份验证
商户域名放在请求地址中,前台用户 ID 和 API Key 放在请求头中:
GET /openapi/v1/account HTTP/1.1
Host: merchant.example.com
X-Kys-User-Id: 12345
X-Kys-Key: AK_0123456789abcdef0123456789abcdef规则:
X-Kys-User-Id必须填写商户提供的采购会员用户 ID。X-Kys-Key必须是该用户 ID 在“账户管理 > 接口管理”中启用的 API Key,格式为AK_加 32 位小写十六进制随机串。商户域名必须使用商户提供的 OpenAPI 主站域名;前台用户 ID 和 API Key 必须属于该商户账户。域名不正确时返回 404,身份参数不匹配时返回 401。
API Key 重置后,旧 Key 立即失效。
不需要 API Secret、时间戳、nonce 或请求签名。
用户 ID 和 API Key 不能放在 URL 查询参数或请求体中。
所有请求必须使用 HTTPS,API Key 不得写入日志或错误信息。
可以直接运行的多语言示例见第 13 章“身份验证完整示例”。
4. 接口清单
第三方可以调用 15 个接口。基础下单接入只需要分类、商品列表、商品详情、商品下单模板、创建订单、订单详情和 Webhook;其他接口按业务需要接入。商品、订单、卡密和售后发生变化时,平台会主动发送 4 类 Webhook:product.changed、order.changed、order.credentials.ready、aftersale.message.created。退款结果包含在 order.changed 中。
方法 | 路径 | 用途 | 接入要求 |
|---|---|---|---|
GET |
| 查询账户 | 按需 |
GET |
| 查询可发现商品涉及的分类 | 基础 |
GET |
| 查询可采购商品及当前订阅状态 | 基础 |
GET |
| 查询商品详情 | 基础 |
GET |
| 查询商品下单模板 | 基础 |
POST |
| 批量变更商品订阅 | 需要提前接收商品变化时使用 |
POST |
| 创建订单并使用余额支付 | 基础 |
GET |
| 查询订单列表 | 按需 |
GET |
| 查询订单详情 | 基础兜底 |
POST |
| 提交验证码 | 订单要求验证码时使用 |
POST |
| 申请取消 | 订单允许取消时使用 |
POST |
| 申请退款 | 订单允许退款时使用 |
POST |
| 创建售后 | 按需 |
GET |
| 查询售后详情 | 按需 |
POST |
| 追加售后消息 | 按需 |
以下章节中的路径均省略 /openapi/v1 前缀。
每个接口的用途、请求方式、请求地址、全部请求字段、全部返回字段和该接口专属规则都集中写在对应接口章节内。身份验证、金额与时间格式、请求频率限制和通用错误响应属于所有接口共同遵守的规则,只在公共章节说明一次。
6. 分类、商品和订阅
标准接入顺序:
调用
GET /categories获取当前账户可见分类。选择分类后调用
GET /goods/catalog?catalogRef=...分页查询可采购商品;列表直接返回当前订阅状态和到期时间。创建本地商品时调用
GET /goods/{productRef}获取详情和规格。对需要销售的规格调用一次
GET /goods/tpl,保存返回的下单模板并生成本地下单表单。单规格商品只传productRef,多规格商品同时传variantRef。需要在第一笔订单前接收商品变化时,调用
POST /goods/subscriptions订阅商品;不订阅也可以查询模板和创建订单。成功创建订单后,平台会自动订阅或续期对应商品。后续可以使用
GET /goods/catalog?catalogRef=...&subscriptionState=subscribed查询已订阅商品,并接收这些商品的product.changed。
9. Webhook
Webhook 就是平台主动向第三方回调地址发送的 HTTP 请求。Webhook 默认启用,不设置事件开关。第三方可以在商户前台个人中心“账户管理 > 接口管理”中保存默认回调地址,也可以在创建订单时填写该订单自己的 orderCallbackUrl;OpenAPI 不能查询或修改默认地址。
订单填写 orderCallbackUrl 后,订单、卡密、退款、对应商品变化以及关联售后消息优先发送到该固定地址。订单未填写地址时,每条消息生成时读取当时的默认回调地址。同一条消息只选择一个地址并固定下来,之后修改默认地址不会改变已经生成的消息。消息生成时两个地址都没有就不发送该条 Webhook,订单仍然正常创建,第三方使用查询接口获取当前结果。
9.5 确认与重试
Webhook 没有额外签名。第三方收到消息后按以下步骤处理:
检查 JSON 格式和必填字段。
保存
eventId。同一个eventId再次收到时,不要重复发卡、记录退款或追加售后消息。卡密消息必须先安全保存,再交给后续发货流程。商品消息用于同步变化;需要确认商品当前状态时调用商品查询接口。
数据保存成功后返回 HTTP 200,响应正文使用纯文本
OK;OK、ok等大小写形式都可以。
正确响应示例:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
OK只有 HTTP 状态为 200,并且响应正文去除首尾空白后忽略大小写等于 OK,才算成功。接收方必须在 3 秒内保存消息并返回 OK,耗时业务应在返回后异步处理。其他状态、空正文、JSON、其他文本、超时或连接失败都视为失败;发送方不跟随 3xx。
每条消息最多发送 3 次:第一次立即发送;没有收到 OK 时,在第一次发送后的第 15 秒和第 30 秒各重试一次。三次使用同一个 eventId、回调地址和请求正文。第三次仍失败后停止发送,也不提供发送记录查询。订单和退款可以通过 GET /orders/{clientOrderRef} 查询,商品可以重新查询商品列表或详情,售后可以查询售后详情。第三方必须用 eventId 防止重复处理。
由于 Webhook 没有签名,第三方不能只凭一条回调确认发送者身份。回调地址必须使用 HTTPS;涉及退款入账等不能撤销的资金操作时,请再调用需要身份验证的查询接口核对。
10. 错误处理和重试
错误示例:
{
"code": 409101,
"message": "下单模板已更新,请重新查询商品下单模板",
"data": null,
"requestId": "req_01K0EXAMPLE"
}10.1 重试原则
收到
429、部分5xx或网络超时时可以重试,但每次重试前应延长等待时间。POST /orders/create必须复用原clientOrderRef和原下单内容,不得生成新号;同内容重试返回原订单,不会重复扣款。POST /orders/{clientOrderRef}/refunds必须复用原clientRefundRef,不得为同一退款意图生成新号。参数错误、身份验证失败或没有权限时,必须先修正问题再重试。
收到
Retry-After时必须遵守。不要每秒查询。优先等待 Webhook;没有收到消息或需要核对时,再按订单号、商品号或售后号低频查询。
10.2 请求频率限制
每个采购账户执行以下限制:
接口 | 调用额度 |
|---|---|
查询账户、查询分类 | 60 次/分钟 |
查询商品列表、商品详情、商品下单模板 | 120 次/分钟 |
修改商品订阅 | 30 次/分钟;每次最多处理 100 个商品 |
创建订单 | 120 次/分钟;瞬时最多 10 次 |
查询订单列表、订单详情 | 120 次/分钟 |
提交验证码 | 10 次/分钟;同一订单 30 秒最多 1 次 |
申请取消订单、申请退款 | 30 次/分钟;同一订单 10 秒最多 1 次 |
创建售后、追加售后消息 | 30 次/分钟 |
查询售后详情 | 120 次/分钟 |
一个采购账户的全部接口合计不能超过 600 次/分钟,瞬时最多 30 次。主动发送给第三方的 Webhook 不占这些调用次数。
请求过于频繁时返回 HTTP 429。响应中的 Retry-After 表示至少需要等待多少秒,同时还会返回:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
同一请求同时检查总额度、接口额度和单个订单冷却时间,响应以当前最严格的一项为准。收到 Retry-After 后必须等待,不能立即循环重试。
限流服务暂时不可用时返回 HTTP 503 和 Retry-After: 1。创建订单重试必须继续使用原 clientOrderRef,退款重试必须继续使用原 clientRefundRef。
完整错误码见第 15 章“完整错误码”。
11. 敏感信息处理
第三方不得记录:
完整 API Key。
包含
X-Kys-Key的请求头和调试信息。卡密明文不得写入普通日志;业务必须保存时应加密存储并限制访问。
充值账号、验证码和图片证据的完整内容。
接口响应只包含本文列出的第三方字段。
12. 接口升级时的处理
/openapi/v1会增加新的可选字段,第三方程序不要因为出现未知字段而报错。删除字段、改变字段类型或改变原有含义时,会发布新的主版本。
收到程序不认识的状态值时,不要直接崩溃或当成成功;请保留原值并按未知状态处理。
新增可选字段会更新
OPENAPI_CONTRACT_REVISION;删除字段、改变字段类型或改变原有含义时会发布新的主版本。
13. 身份验证完整示例
13.1 接入需要的三个参数
第三方对接只需要保存以下三个参数:
参数 | 示例 | 说明 |
|---|---|---|
商户域名 |
| 商户提供的 OpenAPI HTTPS 域名 |
前台用户 ID |
| 采购会员在该商户前台的用户 ID |
API Key |
| 该用户在“账户管理 > 接口管理”中启用的 API Key |
商户域名决定访问哪个商户,前台用户 ID 决定访问哪个采购账户,API Key 用于确认调用身份。三项必须属于同一个商户账户。
不需要 API Secret、时间戳、nonce 或请求签名。
13.2 每次请求如何携带
商户域名放在请求地址中,前台用户 ID 和 API Key 放在请求头中:
GET /openapi/v1/account HTTP/1.1
Host: merchant.example.com
Accept: application/json
X-Kys-User-Id: 12345
X-Kys-Key: AK_0123456789abcdef0123456789abcdef请求头字段:
请求头 | 类型 | 必填 | 说明 |
|---|---|---|---|
| string | 是 | 采购会员在当前商户前台的用户 ID,按商户提供的原值传递 |
| string | 是 | 与该前台用户 ID 绑定的 API Key |
用户 ID 和 API Key 不得放在 URL 查询参数或 JSON 请求体中。
13.3 cURL 示例
查询账户:
curl 'https://merchant.example.com/openapi/v1/account' \
-H 'Accept: application/json' \
-H 'X-Kys-User-Id: 12345' \
-H 'X-Kys-Key: AK_0123456789abcdef0123456789abcdef'创建订单:
curl 'https://merchant.example.com/openapi/v1/orders/create' \
-X POST \
-H 'Content-Type: application/json' \
-H 'X-Kys-User-Id: 12345' \
-H 'X-Kys-Key: AK_0123456789abcdef0123456789abcdef' \
--data-raw '{"clientOrderRef":"PO-20260729-0001","productRef":1001,"units":1}'13.4 PHP 示例
<?php
$baseUrl = 'https://merchant.example.com/openapi/v1';
$userId = '12345';
$apiKey = 'AK_0123456789abcdef0123456789abcdef';
$curl = curl_init($baseUrl . '/account');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'X-Kys-User-Id: ' . $userId,
'X-Kys-Key: ' . $apiKey,
],
]);
$response = curl_exec($curl);
if ($response === false) {
throw new RuntimeException('请求失败');
}
$httpStatus = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
if ($httpStatus !== 200) {
throw new RuntimeException('接口返回 HTTP ' . $httpStatus);
}
$data = json_decode($response, true, flags: JSON_THROW_ON_ERROR);13.5 Node.js 示例
const baseUrl = 'https://merchant.example.com/openapi/v1';
const userId = '12345';
const apiKey = 'AK_0123456789abcdef0123456789abcdef';
const response = await fetch(`${baseUrl}/account`, {
headers: {
Accept: 'application/json',
'X-Kys-User-Id': userId,
'X-Kys-Key': apiKey,
},
});
const result = await response.json();
if (!response.ok) {
throw new Error(`接口返回 HTTP ${response.status},requestId=${result.requestId ?? ''}`);
}13.6 验证失败
请求已经通过当前商户的 OpenAPI 主站域名进入接口后,以下情况统一返回 HTTP 401 和错误码 INVALID_CREDENTIAL:
没有提交前台用户 ID 或 API Key。
前台用户 ID 不属于当前商户。
API Key 不属于该前台用户 ID。
API Key 已经重置或撤销。
响应不会说明具体是哪一个身份参数错误。请同时核对前台用户 ID 和 API Key。
使用分站域名或其他未启用 OpenAPI 的域名访问 /openapi 时,请求不会进入 OpenAPI 身份验证,直接返回 HTTP 404。此时应改用商户提供的 OpenAPI 主站域名。
身份验证通过但会员账户已经暂停时,GET /account 仍会成功并返回 accessState: "suspended";其他接口返回 HTTP 403 和 CREDENTIAL_SUSPENDED。
13.7 安全要求
所有接口必须使用 HTTPS。
API Key 只能保存在第三方平台的后台配置中,不能放进浏览器、前端代码、URL、日志或错误信息。
API Key 重置后旧 Key 立即失效,第三方需要及时更新配置。
商户设置了 IP 白名单时,请求来源还必须在白名单中。
平台主动发送的 Webhook 不携带上述身份验证请求头。Webhook 接收成功返回 HTTP 200 和纯文本
OK,正文大小写均可。
14. Webhook 接收代码示例
四类 Webhook 的触发条件、完整字段表、请求示例和重试规则统一以第 9 章对应事件章节为准。本章只提供接收端代码示例,不重复定义事件字段。
14.1 PHP 接收示例
<?php
function finishWebhook(int $statusCode, string $body = ''): never
{
http_response_code($statusCode);
header('Content-Type: text/plain; charset=utf-8');
echo $body;
exit;
}
$rawBody = file_get_contents('php://input');
if (!is_string($rawBody)) {
finishWebhook(400);
}
try {
$event = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
finishWebhook(400);
}
if (!is_array($event) || !is_string($event['eventId'] ?? null)) {
finishWebhook(400);
}
// 使用存储系统的唯一约束保存 eventId;重复事件也直接返回 OK。
// 保存成功后异步处理业务,HTTP 请求内不调用后续业务接口。
finishWebhook(200, 'OK');接收方必须补充:
eventId唯一约束。请求体大小限制。
JSON schema 校验。
请求超时和并发限制。
14.2 Node.js 接收示例
import express from 'express';
const app = express();
app.use(express.json({ limit: '256kb' }));
app.post('/kys/webhook', async (req, res) => {
const event = req.body;
if (!event || typeof event.eventId !== 'string') {
return res.sendStatus(400);
}
// 以唯一约束保存 eventId,保存后异步处理业务。
// 重复 eventId 也返回相同的 OK,避免平台继续重试。
return res.status(200).type('text/plain').send('OK');
});15. 完整错误码
15.1 状态
错误码以当前 OPENAPI_CONTRACT_REVISION 为准。
统一错误响应:
{
"code": 409101,
"message": "下单模板已更新,请重新查询商品下单模板",
"data": null,
"requestId": "req_01K0EXAMPLE"
}规则:
HTTP 状态表达协议结果,
code表达稳定业务原因。message只包含接入方可处理的业务说明。接入方判断
code,不要依赖中文文案。联系商户排查时提供
requestId,不得提供完整 API Key。
15.2 通用错误
HTTP | code | 标识 | 含义 | 接入方处理 |
|---|---|---|---|---|
400 | 400001 |
| 请求参数不符合契约 | 修正参数后重试 |
400 | 400002 |
| 公开资源引用无效、类型不符或已失效 | 重新拉取资源,禁止自行解析引用 |
401 | 401001 |
| 前台用户 ID 或 API Key 缺失、无效或不匹配 | 核对当前商户下的前台用户 ID 和 API Key 后重试 |
403 | 403005 |
| 会员账户已暂停;只有 | 联系商户管理员恢复账户 |
403 | 403002 |
| 来源 IP 不在白名单 | 核对出口 IP 和商户配置 |
404 | 404001 |
| 资源不存在或对当前账户不可见 | 重新同步资源 |
404 | 404002 |
| 当前账户下不存在对应的 OpenAPI 订单 | 核对第三方订单号或平台订单号 |
404 | 404003 |
| 当前账户下不存在对应的售后 | 核对平台售后编号 |
409 | 409001 |
| 同一客户端引用再次提交了不同业务内容 | 使用原内容重试;确实是新业务时生成新引用 |
429 | 429001 |
| 请求频率超限 | 遵守 |
500 | 500001 |
| 系统处理失败 | 保存 |
503 | 503001 |
| 服务暂时不可用 | 逐步延长等待时间;订单请求复用原 |
15.3 商品和订阅
HTTP | code | 标识 | 含义 | 接入方处理 |
|---|---|---|---|---|
404 | 404001 |
| 商品、规格或分类不存在,或者对当前账户不可见 | 重新查询分类、商品目录或商品详情 |
15.4 购买模板和订单
HTTP | code | 标识 | 含义 | 接入方处理 |
|---|---|---|---|---|
409 | 409101 |
| 下单模板已经变化或当前暂不可用 | 重新查询 |
409 | 409102 |
| 当前订单不能申请取消 | 查询订单详情并读取最新 |
409 | 409103 |
| 当前订单没有可退金额、不能申请退款或已有待处理退款申请 | 查询订单详情并等待当前退款申请处理完成 |
422 | 422100 |
| 订单暂时无法创建,且没有更具体的业务原因 | 根据 |
422 | 422101 |
| 商品类型、商品或规格当前不可购买 | 重新查询商品详情 |
422 | 422102 |
| 商品当前暂时无法供应 | 稍后查询商品详情后重试 |
422 | 422103 |
| 可用余额不足 | 充值后使用原 |
422 | 422110 |
| 购买数量不符合当前商品规则 | 按 |
422 | 422111 |
|
| 按 |
422 | 422112 |
|
| 提交持续可访问的公网 HTTPS 地址;不要提交文件、Base64、本地或内网地址 |
422 | 422114 |
| 已提交的订单回调地址不是允许的公网 HTTPS URL | 修正地址;也可以删除 |
422 | 422120 |
| 当前订单不能提交验证码 | 查询订单详情并读取最新 |
429 | 429110 |
| 验证码提交过于频繁 | 遵守 |
429 | 429111 |
| 取消申请提交过于频繁 | 遵守 |
429 | 429112 |
| 退款申请提交过于频繁 | 遵守 |
充值商品才会返回购买模板相关错误;卡密商品不要求提交 purchaseSchemaRevision 或 purchaseEntries。
创建订单时,clientOrderRef 在当前采购账户内唯一。相同编号和相同商品、规格、数量及充值资料会返回原订单;更换其中任一内容会返回 IDEMPOTENCY_CONFLICT。orderCallbackUrl 不用于判断内容是否相同,重试不能修改原订单第一次保存的回调地址。
15.5 售后
HTTP | code | 标识 | 含义 | 接入方处理 |
|---|---|---|---|---|
409 | 409104 |
| 当前订单不能创建售后,或者当前售后不能再追加消息 | 刷新订单或售后详情 |
400 | 400001 |
| 售后分类、说明、消息、图片数量或长度不符合要求 | 按接口字段说明修正请求 |
422 | 422112 |
| 售后图片地址不是允许的公网 HTTPS URL | 提交持续可访问的公网 HTTPS 图片地址 |
15.6 重试矩阵
场景 | 是否可自动重试 | 约束 |
|---|---|---|
网络超时、429、503 | 可以 | 逐步延长等待时间;订单复用原 |
500 | 有限次数 | 保存 |
400、401、403、404、409、422 | 默认不可以 | 先修正请求或刷新资源 |