REST API · 接入准备
REST API 快速开始
接口都是 HTTPS + JSON,不需要 SDK:任何能发 HTTP 请求、能算 HMAC-SHA256 的语言都能接。下面用大约 10 分钟,从拿到凭证走到发出第一个签名请求, 再以终端用户的身份调用接口。
- 在控制台拿到 appKey 与签名密钥
- 发出第一个请求:
GET /v1/access-clients/me - 搜索商品,并以终端用户的身份调用
- 看懂错误,排查签名
所有调用都放在你自己的服务端。签名密钥 app_secret 不能进小程序、App 或网页:包可以被解开,前端请经你的后端中转。
1. 拿到凭证
- 用手机号登录开发者控制台,第一次登录会自动开通体验应用(7 天、每天 1000 次调用,可跑通查商品与下单)。 正式上线在控制台申请正式接入,审核通过后会出现一个新的正式应用。
- 进入应用详情 →「凭证」:appKey 直接显示;签名密钥(app_secret)点「生成」后只显示这一次,立即保存。 丢了就重置,旧的立即失效。生成过密钥后,这个 appKey 的所有请求都必须签名。
- 把两者放进服务端的环境变量(如
GOWU_APP_KEY、GOWU_APP_SECRET),不要写进代码仓库。 - 应用详情里的「权限」列出这个 appKey 能调哪些接口;不在列表里的接口会返回
403,需要的话联系平台开通。
2. 发出第一个请求
下面是一个完整可运行的脚本:前半段是签名函数(可以原样抄进你的服务端),后半段调用 GET /v1/access-clients/me。 返回 200 并看到自己的 appKey 与开通的接口列表,就说明凭证和签名都对了。
签名的要点(完整规则见鉴权与签名):
- 每个请求带
x-app-key、x-timestamp(Unix 秒)、x-nonce(每次一个新的随机串)、x-sign-version: 2、x-signature。 - 签名串是 8 段用换行连接:OCP-SIGN-V2、时间戳、nonce、大写的方法、纯路径、规范化 query、请求体的 sha256、x-open-user。
- 请求体先序列化成字符串,同一个字符串既算哈希又发出去;不要让 HTTP 库再序列化一次。
1// quickstart.mjs —— Node.js 18+:GOWU_APP_KEY=… GOWU_APP_SECRET=… node quickstart.mjs2import crypto from "node:crypto";3 4// 只在你自己的服务端运行:app_secret 不能进小程序 / App / 网页5// path 可带 query(如 /v1/lm/orders?page=2),不含域名6function gowuHeaders({ appKey, appSecret, method, path, body = "", openUser = "" }) {7 const ts = String(Math.floor(Date.now() / 1000));8 const nonce = crypto.randomUUID();9 const bodyHash = crypto.createHash("sha256").update(body).digest("hex");10 // v2:nonce 与规范化 query 都进签名(解码后按 key/value 升序、各自 encodeURIComponent)11 const [pathname, search = ""] = path.split("?");12 const query = [...new URLSearchParams(search).entries()]13 .sort(([ak, av], [bk, bv]) => (ak < bk ? -1 : ak > bk ? 1 : av < bv ? -1 : av > bv ? 1 : 0))14 .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)15 .join("&");16 const canonical = ["OCP-SIGN-V2", ts, nonce, method.toUpperCase(), pathname, query, bodyHash, openUser].join("\n");17 const signature = crypto.createHmac("sha256", appSecret).update(canonical).digest("hex");18 return {19 "content-type": "application/json",20 "x-app-key": appKey,21 "x-timestamp": ts,22 "x-nonce": nonce,23 "x-sign-version": "2",24 "x-signature": signature,25 ...(openUser ? { "x-open-user": openUser } : {})26 };27}28 29const BASE = "https://api.gowu.net";30 31/** 发一个签名请求:body 先序列化成字符串,同一个字符串既算签名又发出去 */32async function gowu(method, path, { body, openUser } = {}) {33 const raw = body === undefined ? "" : JSON.stringify(body);34 const res = await fetch(BASE + path, {35 method,36 headers: gowuHeaders({37 appKey: process.env.GOWU_APP_KEY,38 appSecret: process.env.GOWU_APP_SECRET,39 method, path, body: raw, openUser40 }),41 body: raw || undefined42 });43 const data = await res.json();44 if (!res.ok) throw new Error(`${res.status} ${data.code}: ${data.message}`);45 return data;46}47 48// 第一个请求:确认 appKey、签名都对,并看到这个 appKey 开通了哪些接口49console.log(await gowu("GET", "/v1/access-clients/me"));1{2 "appKey": "string",3 "remark": "string",4 "createdAt": "string",5 "apiCallCount": 06}3. 搜索商品,并以终端用户的身份调用
查商品这类接口只要 appKey 与签名;下单、订单、售后、地址、额度这类「用户态」接口,还要用 x-open-user 说明这次是哪个用户在操作 (接口参考里标「用户态」的那些)。
x-open-user放你系统里的用户 ID,必须取自你服务端的登录态,不能直接用前端传上来的值;它也参与签名(第 8 段)。- 平台按「appKey + 用户 ID」隔离数据:同一个用户 ID 在不同 appKey 下是不同的人,第一次出现时自动建档。
- 员工福利 / 企业采购的用户还要先由应用负责人绑定到企业,才能用企业额度下单,见企业与员工;消费者购物直接用即可。
- GET 请求的 query 也参与签名:把 query 原样写进 path,签名函数会按规则规范化。
1// 接着 quickstart.mjs 里的 gowu() 往下写2 3// 1) 搜索商品(不需要终端用户)4// 员工福利 / 企业采购用 /v1/enterprise/products/search,消费者购物用 /v1/lm/products/search,参数相同5const found = await gowu("POST", "/v1/enterprise/products/search", {6 body: { version: 2, query: { text: "保温杯" }, limit: 5 }7});8// 返回 { hits: [{ product, ... }], total, nextCursor }9console.log(found.hits.map((h) => `${h.product.id} ${h.product.title} ¥${h.product.price}`));10 11// 2) 以某个用户的身份查他的订单:openUser 取自你服务端的登录态,参与签名12// query 直接写在 path 里,gowuHeaders 会按规范化规则把它算进签名13const orders = await gowu("GET", "/v1/enterprise/orders?page=1&pageSize=10", {14 openUser: req.session.userId15});4. 看懂错误,排查签名
出错时 HTTP 状态码表意,响应体是 { "code": "...", "message": "..." },message 会说明具体原因。全部错误码见状态码与错误。
| 现象 | 最常见的原因 |
|---|---|
401 签名不对 | 请求体算哈希用的字符串与实际发送的不一致(被 HTTP 库重新序列化、多了空格或换行);path 带了域名;query 没算进签名;x-open-user 签名时的值与请求头不一致;用了 app_secret 以外的密钥。 |
401 时间戳过期 | 服务器时钟偏差超过 ±300 秒(容器、虚拟机常见),开启 NTP 同步;时间戳是秒,不是毫秒。 |
401 nonce 重复 | 重试时复用了上一次的请求头。每次请求(包括重试)都要重新生成 nonce 与签名。 |
403 | 这个接口不在 appKey 的权限里(控制台「权限」可查);或体验应用已到期。员工福利 / 企业采购还可能是用户没绑定企业、不是管理员。 |
429 | 超出限流或体验应用当天的调用量,稍后重试,见限流。 |
排查签名时,把你算出的 8 段签名串逐段打印出来对照:绝大多数问题出在第 6 段(query)与第 7 段(请求体哈希)。
5. 下一步
- 按你的场景读接入指南:员工福利 / 企业采购从企业与员工开始,消费者购物从用户与地址开始。
- 查字段:接口参考里每个接口都有参数表、cURL / Node.js / Python 调用示例与返回示例。
- 调试工具:OpenAPI 规格按场景各一份,可导入 Postman、Apifox、Swagger UI,或生成客户端: 员工福利购物、企业采购、消费者购物。
- 订单、售后结果用 Webhook 推给你,在控制台「凭证」里配置回调地址。
- 给 AI 写代码用:llms.txt 是按场景分好的接口索引,llms-full.txt 含完整入参出参与示例。不想自己做签名,让 Agent 直接用 MCP 或 命令行 gowu。