接入指南

API文档最后更新: 2026-08-30

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_listcard_nocard_password

  • 分页:page 从 1 开始,pageSize 默认 20、最大 100。

  • 所有请求必须使用 HTTPS。

2.1 编号和返回格式

本文出现的字段就是第三方接入时使用的字段。

  • accountRefcatalogRefproductRefvariantRef 是整数编号;platformOrderRefrefundRefplatformCaseRef 是字符串编号。请原样保存和传递,不要自行添加前缀或转换类型。

  • 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.changedorder.changedorder.credentials.readyaftersale.message.created。退款结果包含在 order.changed 中。

方法

路径

用途

接入要求

GET

/account

查询账户

按需

GET

/categories

查询可发现商品涉及的分类

基础

GET

/goods/catalog

查询可采购商品及当前订阅状态

基础

GET

/goods/{productRef}

查询商品详情

基础

GET

/goods/tpl

查询商品下单模板

基础

POST

/goods/subscriptions

批量变更商品订阅

需要提前接收商品变化时使用

POST

/orders/create

创建订单并使用余额支付

基础

GET

/orders

查询订单列表

按需

GET

/orders/{clientOrderRef}

查询订单详情

基础兜底

POST

/orders/{clientOrderRef}/verification-code

提交验证码

订单要求验证码时使用

POST

/orders/{clientOrderRef}/cancel

申请取消

订单允许取消时使用

POST

/orders/{clientOrderRef}/refunds

申请退款

订单允许退款时使用

POST

/aftersales

创建售后

按需

GET

/aftersales/{platformCaseRef}

查询售后详情

按需

POST

/aftersales/{platformCaseRef}/messages

追加售后消息

按需

以下章节中的路径均省略 /openapi/v1 前缀。

每个接口的用途、请求方式、请求地址、全部请求字段、全部返回字段和该接口专属规则都集中写在对应接口章节内。身份验证、金额与时间格式、请求频率限制和通用错误响应属于所有接口共同遵守的规则,只在公共章节说明一次。

6. 分类、商品和订阅

标准接入顺序:

  1. 调用 GET /categories 获取当前账户可见分类。

  2. 选择分类后调用 GET /goods/catalog?catalogRef=... 分页查询可采购商品;列表直接返回当前订阅状态和到期时间。

  3. 创建本地商品时调用 GET /goods/{productRef} 获取详情和规格。

  4. 对需要销售的规格调用一次 GET /goods/tpl,保存返回的下单模板并生成本地下单表单。单规格商品只传 productRef,多规格商品同时传 variantRef

  5. 需要在第一笔订单前接收商品变化时,调用 POST /goods/subscriptions 订阅商品;不订阅也可以查询模板和创建订单。

  6. 成功创建订单后,平台会自动订阅或续期对应商品。后续可以使用 GET /goods/catalog?catalogRef=...&subscriptionState=subscribed 查询已订阅商品,并接收这些商品的 product.changed

9. Webhook

Webhook 就是平台主动向第三方回调地址发送的 HTTP 请求。Webhook 默认启用,不设置事件开关。第三方可以在商户前台个人中心“账户管理 > 接口管理”中保存默认回调地址,也可以在创建订单时填写该订单自己的 orderCallbackUrl;OpenAPI 不能查询或修改默认地址。

订单填写 orderCallbackUrl 后,订单、卡密、退款、对应商品变化以及关联售后消息优先发送到该固定地址。订单未填写地址时,每条消息生成时读取当时的默认回调地址。同一条消息只选择一个地址并固定下来,之后修改默认地址不会改变已经生成的消息。消息生成时两个地址都没有就不发送该条 Webhook,订单仍然正常创建,第三方使用查询接口获取当前结果。

9.5 确认与重试

Webhook 没有额外签名。第三方收到消息后按以下步骤处理:

  1. 检查 JSON 格式和必填字段。

  2. 保存 eventId。同一个 eventId 再次收到时,不要重复发卡、记录退款或追加售后消息。

  3. 卡密消息必须先安全保存,再交给后续发货流程。商品消息用于同步变化;需要确认商品当前状态时调用商品查询接口。

  4. 数据保存成功后返回 HTTP 200,响应正文使用纯文本 OKOKok 等大小写形式都可以。

正确响应示例:

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-Limit

  • X-RateLimit-Remaining

  • X-RateLimit-Reset

同一请求同时检查总额度、接口额度和单个订单冷却时间,响应以当前最严格的一项为准。收到 Retry-After 后必须等待,不能立即循环重试。

限流服务暂时不可用时返回 HTTP 503Retry-After: 1。创建订单重试必须继续使用原 clientOrderRef,退款重试必须继续使用原 clientRefundRef

完整错误码见第 15 章“完整错误码”。

11. 敏感信息处理

第三方不得记录:

  • 完整 API Key。

  • 包含 X-Kys-Key 的请求头和调试信息。

  • 卡密明文不得写入普通日志;业务必须保存时应加密存储并限制访问。

  • 充值账号、验证码和图片证据的完整内容。

接口响应只包含本文列出的第三方字段。

12. 接口升级时的处理

  • /openapi/v1 会增加新的可选字段,第三方程序不要因为出现未知字段而报错。

  • 删除字段、改变字段类型或改变原有含义时,会发布新的主版本。

  • 收到程序不认识的状态值时,不要直接崩溃或当成成功;请保留原值并按未知状态处理。

  • 新增可选字段会更新 OPENAPI_CONTRACT_REVISION;删除字段、改变字段类型或改变原有含义时会发布新的主版本。

13. 身份验证完整示例

13.1 接入需要的三个参数

第三方对接只需要保存以下三个参数:

参数

示例

说明

商户域名

merchant.example.com

商户提供的 OpenAPI HTTPS 域名

前台用户 ID

12345

采购会员在该商户前台的用户 ID

API Key

AK_0123456789abcdef0123456789abcdef

该用户在“账户管理 > 接口管理”中启用的 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

请求头字段:

请求头

类型

必填

说明

X-Kys-User-Id

string

采购会员在当前商户前台的用户 ID,按商户提供的原值传递

X-Kys-Key

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

INVALID_ARGUMENT

请求参数不符合契约

修正参数后重试

400

400002

INVALID_PUBLIC_REFERENCE

公开资源引用无效、类型不符或已失效

重新拉取资源,禁止自行解析引用

401

401001

INVALID_CREDENTIAL

前台用户 ID 或 API Key 缺失、无效或不匹配

核对当前商户下的前台用户 ID 和 API Key 后重试

403

403005

CREDENTIAL_SUSPENDED

会员账户已暂停;只有 GET /account 仍可返回 accessState=suspended

联系商户管理员恢复账户

403

403002

SOURCE_IP_DENIED

来源 IP 不在白名单

核对出口 IP 和商户配置

404

404001

RESOURCE_NOT_VISIBLE

资源不存在或对当前账户不可见

重新同步资源

404

404002

ORDER_NOT_FOUND

当前账户下不存在对应的 OpenAPI 订单

核对第三方订单号或平台订单号

404

404003

AFTERSALE_NOT_FOUND

当前账户下不存在对应的售后

核对平台售后编号

409

409001

IDEMPOTENCY_CONFLICT

同一客户端引用再次提交了不同业务内容

使用原内容重试;确实是新业务时生成新引用

429

429001

RATE_LIMITED

请求频率超限

遵守 Retry-After 并逐步延长等待时间

500

500001

INTERNAL_ERROR

系统处理失败

保存 requestId,延迟重试

503

503001

TEMPORARILY_UNAVAILABLE

服务暂时不可用

逐步延长等待时间;订单请求复用原 clientOrderRef

15.3 商品和订阅

HTTP

code

标识

含义

接入方处理

404

404001

RESOURCE_NOT_VISIBLE

商品、规格或分类不存在,或者对当前账户不可见

重新查询分类、商品目录或商品详情

15.4 购买模板和订单

HTTP

code

标识

含义

接入方处理

409

409101

PURCHASE_SCHEMA_CHANGED

下单模板已经变化或当前暂不可用

重新查询 /goods/tpl 并按最新模板提交

409

409102

CANCELLATION_NOT_ALLOWED

当前订单不能申请取消

查询订单详情并读取最新 allowedActions

409

409103

REFUND_NOT_ALLOWED

当前订单没有可退金额、不能申请退款或已有待处理退款申请

查询订单详情并等待当前退款申请处理完成

422

422100

ORDER_CREATE_REJECTED

订单暂时无法创建,且没有更具体的业务原因

根据 message 核对商品和订单资料

422

422101

PRODUCT_UNAVAILABLE

商品类型、商品或规格当前不可购买

重新查询商品详情

422

422102

INVENTORY_UNAVAILABLE

商品当前暂时无法供应

稍后查询商品详情后重试

422

422103

BALANCE_INSUFFICIENT

可用余额不足

充值后使用原 clientOrderRef 查询或按返回语义重试

422

422110

UNITS_NOT_ALLOWED

购买数量不符合当前商品规则

unitRule 修正

422

422111

PURCHASE_INPUT_INVALID

purchaseEntries 的数量、分组或字段编号不符合当前下单模板

entryModepurchaseFields 修正,每组直接使用 fieldRef 作为字段名

422

422112

IMAGE_URL_INVALID

imageUrl 不是长度不超过 2048 的公网 HTTPS 图片 URL

提交持续可访问的公网 HTTPS 地址;不要提交文件、Base64、本地或内网地址

422

422114

ORDER_CALLBACK_URL_INVALID

已提交的订单回调地址不是允许的公网 HTTPS URL

修正地址;也可以删除 orderCallbackUrl 后正常下单并使用订单查询兜底

422

422120

OTP_NOT_ALLOWED

当前订单不能提交验证码

查询订单详情并读取最新 allowedActions

429

429110

OTP_ATTEMPTS_LIMITED

验证码提交过于频繁

遵守 Retry-After

429

429111

CANCELLATION_ATTEMPTS_LIMITED

取消申请提交过于频繁

遵守 Retry-After

429

429112

REFUND_ATTEMPTS_LIMITED

退款申请提交过于频繁

遵守 Retry-After

充值商品才会返回购买模板相关错误;卡密商品不要求提交 purchaseSchemaRevisionpurchaseEntries

创建订单时,clientOrderRef 在当前采购账户内唯一。相同编号和相同商品、规格、数量及充值资料会返回原订单;更换其中任一内容会返回 IDEMPOTENCY_CONFLICTorderCallbackUrl 不用于判断内容是否相同,重试不能修改原订单第一次保存的回调地址。

15.5 售后

HTTP

code

标识

含义

接入方处理

409

409104

CASE_ACTION_NOT_ALLOWED

当前订单不能创建售后,或者当前售后不能再追加消息

刷新订单或售后详情

400

400001

INVALID_ARGUMENT

售后分类、说明、消息、图片数量或长度不符合要求

按接口字段说明修正请求

422

422112

IMAGE_URL_INVALID

售后图片地址不是允许的公网 HTTPS URL

提交持续可访问的公网 HTTPS 图片地址

15.6 重试矩阵

场景

是否可自动重试

约束

网络超时、429、503

可以

逐步延长等待时间;订单复用原 clientOrderRef,退款复用原 clientRefundRef,售后消息复用原 clientMessageRef

500

有限次数

保存 requestId,设置最大次数和熔断

400、401、403、404、409、422

默认不可以

先修正请求或刷新资源