REST API 与 Node SDK
用密钥从你的服务器把顾客、商品、订单、物流和历史工单推给 purser,以及 Node SDK 的用法。
AI 要回答「我的订单到哪了」「这件还有货吗」,需要你的订单、商品和物流数据。自研商城通过 REST API 把这些数据推给 purser:你的系统里有变化就推一次,第一次接入时用批量接口把历史数据导进来。
基本信息
| 地址 | https://askpurser.com/api/v1 |
| 鉴权 | Authorization: Bearer sk_… |
| 格式 | JSON,Content-Type: application/json |
| 接口参考 | https://askpurser.com/api/v1(可交互的参考页),OpenAPI 文档在 https://askpurser.com/api/v1/spec.json |
接口参考页和 OpenAPI 文档是由接口定义直接生成的,字段和本页不一致时以它为准。
两种密钥
密钥(sk_…) | 公开键(pk_…) | |
|---|---|---|
| 用在哪 | 你的服务器调 REST API | 网页里的嵌入代码、App 客服页 |
| 能做什么 | 读写这个工作区的数据推送接口 | 只能开启聊天会话 |
| 能不能公开 | 不能,只放在服务器上 | 可以,本来就写在网页里 |
| 在哪里拿 | 「接入」→「3. 推送订单数据」→「生成密钥」 | 「接入」→「2. 把客服窗口装到你的网站」的嵌入代码里 |
- 生成密钥只有店主和管理员能做。密钥只显示这一次,purser 只保存它的哈希,生成后马上复制到服务器的环境变量里。
- 每个密钥属于一个工作区,调用时不用、也不能再传工作区 ID:数据写进哪个工作区只由密钥决定。
- 以前生成的
sk_live_…、pk_sand_…格式的密钥照常可用。 - 「接入 → 3. 推送订单数据」下面列出工作区的所有密钥(只有店主和管理员能看到)。密钥泄露或不再用时点「撤销」:立即失效,之后用它的请求都回
401,不能恢复。客服窗口嵌入代码里正在用的那个公开键标着「窗口在用」,不能撤销。 - 登录态的身份签名密钥是另一个东西,见 身份令牌。
检查密钥是否可用:
curl https://askpurser.com/api/v1/ping \
-H "Authorization: Bearer $PURSER_SECRET_KEY"
# {"orgId":"…"} 这个密钥属于哪个工作区数据推送接口
| 方法和路径 | 作用 | 每次最多 |
|---|---|---|
GET /ping | 检查密钥,返回它所属的工作区 | — |
POST /customers | 写入顾客 | 25 个 |
POST /products | 写入商品(含变体) | 25 个 |
POST /orders | 写入订单(含商品行) | 25 个 |
POST /shipments | 写入物流(含轨迹) | 25 个 |
POST /tickets | 写入以前客服系统里的历史工单(含整段对话) | 50 个工单、共 2,000 条消息 |
POST /customers/bulk 等 | 批量写入,异步处理(顾客、商品、订单、物流各一个) | 1,000 个 |
GET /batches/{id} | 查询一个批量写入的进度 | — |
POST /backfills | 开始一次历史导入 | — |
GET /backfills/{id} | 查询历史导入进度 | — |
POST /backfills/{id}/close | 声明历史导入的数据已经全部发完 | — |
POST /actions/{id}/status | 回报一笔退款的结果,见 退款接口 | — |
另有一个 POST /widget/sessions,用公开键(请求头 x-purser-key)调用,是聊天窗口自己开会话用的,一般不需要你调。
写入规则
- 请求体是
{ "data": [ … ] }。 - 按
id写入:id是你系统里的 ID,不存在就新建,存在就更新。 updatedAt必填,是你的系统最后一次修改这个对象的时间。比已存的版本旧或相同的写入会被忽略,结果里标stale,所以乱序到达的推送不会把新数据覆盖成旧的。- 时间字段接受带时区的 ISO-8601 字符串(如
2026-09-26T08:00:00Z)或毫秒时间戳。 - 金额是整数的最小货币单位:
1999表示 $19.99,日元的1999就是 ¥1999。币种用 ISO 4217 大写代码,如USD、JPY。 - 每个对象都可以带
custom:你自己的字段,原样保存,默认不给 AI 看。 - 顾客的
tags只替换上一次通过 API 写入的标签,客服在控制台里打的标签不受影响;不传tags就不动,传[]会删掉 API 写的标签。 - 顾客可以带
totalSpent+totalSpentCurrency、ordersCount(你系统里的累计消费和订单数),顾客分层会优先用它们;不传的话 purser 按收到的订单计算。
订单状态:pending、processing、partially_shipped、shipped、delivered、cancelled、refunded、returned。
物流状态:label_created、in_transit、out_for_delivery、delivered、delayed、exception、lost、returned_to_sender。
完整字段见 接口参考。
示例:推送一个订单
curl -X POST https://askpurser.com/api/v1/orders \
-H "Authorization: Bearer $PURSER_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"data":[{
"id": "ord_2001",
"updatedAt": "2026-09-26T08:00:00Z",
"number": "2001",
"customerId": "cus_mia",
"email": "mia@example.com",
"status": "processing",
"currency": "USD",
"total": 1599,
"placedAt": "2026-09-25T10:12:00Z",
"items": [{ "id": "li_1", "title": "Canvas tote", "quantity": 1, "unitPrice": 1599 }]
}]}'{ "results": [{ "id": "ord_2001", "status": "created" }] }每个对象的结果是 created、updated 或 stale 之一。
订单里的 customerId 要和 身份令牌 里的 sub 一致,已登录的顾客在聊天里问订单时,AI 才能找到他的订单。
幂等键
写入接口都支持 Idempotency-Key 请求头,规则和 Stripe 一样:
- 同一个键、同样的请求体,24 小时内再发一次:直接返回第一次的结果,不会重复处理;
- 同一个键、不同的请求体:返回
409(CONFLICT),这通常说明你的代码有 bug。
网络超时后重试时带上同一个键,就不用担心重复写入。
批量写入与历史导入
同步接口每次最多 25 个对象。量大时用 /bulk 接口:每次最多 1,000 个,先校验,再由后台处理,立即返回 202 和 { "batchId": "…" };用 GET /batches/{id} 查进度(pending、processing、done、failed)。写入规则和同步接口相同。
导入几十万条历史订单时,用「历史导入」把多个批次串起来看一个总进度:
POST /backfills(可带{ "label": "…" })拿到id;- 每个
/bulk请求带上"backfillId": "<id>"; - 全部发完后
POST /backfills/{id}/close; GET /backfills/{id}看进度,所有收到的对象处理完后状态变成done。
错误
非 2xx 的响应体都是:
{ "code": "CONFLICT", "status": 409, "message": "…", "data": { } }| 状态码 | 含义 |
|---|---|
400 | 请求体不符合格式,data.issues 列出哪里不对 |
401 | 没带密钥、密钥不存在或已失效 |
404 | 这个工作区里没有这个对象 |
409 | 幂等键被用于不同的请求体;或历史导入不存在、已经结束 |
413 | 一次发得太多,拆开发,或者改用 /bulk |
429 | 请求太频繁。响应带 Retry-After(秒),等这么久再重试 |
频率上限
按分钟计,超出回 429 和 Retry-After: 60:
| 什么 | 上限 |
|---|---|
每个密钥(sk_…)的所有调用 | 600 次/分钟 |
其中 /bulk 接口 | 30 次/分钟 |
客服窗口开始会话(/widget/sessions),同一店铺、同一 IP | 60 次/分钟 |
大量历史数据请用 /bulk 和历史导入(见上文),不要逐条循环调用。Node SDK 遇到 429 会自动退避重试。
Node SDK:@purser-ai/node
还没有发布到 npm
@purser-ai/node 还没有发布,现在 npm install 装不到。发布之前请直接调 REST API(上面的 curl 就是完整的请求)。下面是它发布后的用法。
零依赖,Node 20 及以上(也能在 Bun、Deno、Cloudflare Workers 里用:只用到 fetch 和 WebCrypto)。请求和响应的类型由接口的 OpenAPI 文档生成。
import { Purser, signIdentityToken } from "@purser-ai/node";
const purser = new Purser({ secretKey: process.env.PURSER_SECRET_KEY });
await purser.ping(); // { orgId }
await purser.orders.upsert({
id: "ord_2001", updatedAt: Date.now(), number: "2001", customerId: "cus_mia",
status: "shipped", currency: "USD", total: 1599, placedAt: Date.now(),
});
// 历史数据:数组或任何(异步)迭代器,分页导出可以直接接上
const result = await purser.backfill("orders", fetchAllOrders(), { onProgress: console.log });| 成员 | 说明 |
|---|---|
new Purser({ secretKey, baseUrl? }) | secretKey 必须以 sk_ 开头;baseUrl 默认 https://askpurser.com |
ping() | 返回密钥所属的工作区 { orgId } |
customers / products / orders / shipments / tickets | 各有一个 upsert(对象或数组, { idempotencyKey? }) |
backfill(resource, source, opts?) | resource 是 customers、products、orders、shipments 之一;选项 label、concurrency(默认 4)、pollMs(默认 2000)、onProgress |
signIdentityToken(...) | 签身份令牌,见 身份令牌 |
actionsHandler(...)、verifyActionSignature(...) | 退款接口的服务端,见 退款接口 |
SDK 替你做了这些事:
- 合并请求:20 毫秒内对同一种对象的单个
upsert调用会合并成一个请求(每个调用仍然拿到自己的结果),适合一连串 webhook 同时到达的情况; - 自动分批:传数组时按每次 25 个(工单 50 个)拆开发;
- 幂等键:每次写入都带
Idempotency-Key;传了idempotencyKey时,拆开的每一批用<你的键>:<这一批在数组里的起始位置>; - 重试:网络错误、
429和5xx带随机抖动的退避重试,最多尝试 4 次;其他4xx直接抛错; - 历史导入:
backfill每 1,000 个一批走/bulk,发完自动 close,然后轮询到done(或出现失败批次)为止,返回最后的进度; - 错误:抛出
PurserError,带status、code和接口返回的body。