purser文档

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)。写入规则和同步接口相同。

导入几十万条历史订单时,用「历史导入」把多个批次串起来看一个总进度:

  1. POST /backfills(可带 { "label": "…" })拿到 id;
  2. 每个 /bulk 请求带上 "backfillId": "<id>";
  3. 全部发完后 POST /backfills/{id}/close;
  4. 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),同一店铺、同一 IP60 次/分钟

大量历史数据请用 /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。

本页内容