purserDOCS
purserDOCS
HomePricing中文Sign in

Get started

purser docsGo live in 10 minutes

Channels

Website chatIn-app support pageEmailWhatsApp and Instagram

AI support

Knowledge baseHow the AI answersTest chat and releases

Inbox and team

InboxTeams and routingCustomers

More

RefundsProduct feedback

Account and billing

Plans and billingAccount and data

Developers

Identity tokens: telling purser who the customer isREST API and Node SDKRefund endpoint (Actions API)Connect your own AI assistant over MCP

REST API and Node SDK

Push customers, products, orders, shipments and past tickets to purser from your server with a secret key, and how the Node SDK does it for you.

To answer "where is my order?" or "is this in stock?", the AI needs your order, product and shipment data. A custom-built store sends that data to purser through the REST API: push whenever something changes in your system, and use the bulk endpoints to bring in your history when you first connect.

Basics

Base URLhttps://askpurser.com/api/v1
AuthenticationAuthorization: Bearer sk_…
FormatJSON, Content-Type: application/json
Referencehttps://askpurser.com/api/v1 (an interactive reference page); the OpenAPI document is at https://askpurser.com/api/v1/spec.json

The reference page and the OpenAPI document are generated from the API's own definitions; where they differ from this page, they are right.

Two kinds of key

Secret key (sk_…)Publishable key (pk_…)
Used byYour server, calling the REST APIThe embed code on your web pages, the in-app page
Can doCall the data endpoints for this workspaceOnly start chat sessions
Can it be public?No, keep it on your serverYes, it is in your web pages by design
Where to get itIntegration (接入) → "3. Push order data" (推送订单数据) → Create key (生成密钥)In the embed code under Integration (接入) → "2. Install the chat widget on your site" (把客服窗口装到你的网站)
  • Only owners and admins can create secret keys. The key is shown once; purser only stores its hash, so copy it into your server's environment right away.
  • Each key belongs to one workspace. You do not (and cannot) pass a workspace ID: the key alone decides which workspace the data goes to.
  • Older keys in the sk_live_… and pk_sand_… format keep working.
  • Setup (接入) → "3. Push order data" lists all of the workspace's keys (owners and admins only). When a key leaks or is no longer used, click Revoke (撤销): it stops working at once, every request with it gets 401, and it cannot be restored. The publishable key in your chat-window embed code is marked "in use by the window" (窗口在用) and cannot be revoked.
  • The identity secret used for signed-in customers is a different thing; see Identity tokens.

Check that a key works:

curl https://askpurser.com/api/v1/ping \
  -H "Authorization: Bearer $PURSER_SECRET_KEY"
# {"orgId":"…"}  the workspace this key belongs to

Data endpoints

Method and pathWhat it doesPer call
GET /pingCheck a key; returns the workspace it belongs to—
POST /customersUpsert customers25
POST /productsUpsert products (with variants)25
POST /ordersUpsert orders (with line items)25
POST /shipmentsUpsert shipments (with tracking events)25
POST /ticketsUpsert past tickets from a previous helpdesk (with their whole thread)50 tickets, 2,000 messages in total
POST /customers/bulk etc.Bulk upsert, processed asynchronously (one each for customers, products, orders, shipments)1,000
GET /batches/{id}Progress of one bulk upsert—
POST /backfillsStart a history import—
GET /backfills/{id}Progress of a history import—
POST /backfills/{id}/closeSay that everything for a history import has been sent—
POST /actions/{id}/statusReport how a refund ended, see Refund endpoint—

There is also POST /widget/sessions, called with the publishable key (header x-purser-key). The chat widget uses it to start a session; you normally never call it.

Upsert rules

  • The request body is { "data": [ … ] }.
  • Objects are upserted by id, which is your system's ID: created if new, updated otherwise.
  • updatedAt is required: when your system last changed the object. A write that is not newer than the stored copy is ignored and reported as stale, so pushes arriving out of order never overwrite new data with old.
  • Timestamps are ISO-8601 strings with a time zone (such as 2026-09-26T08:00:00Z) or epoch milliseconds.
  • Amounts are integers in minor units: 1999 means $19.99, and for yen 1999 means ¥1999. Currencies are ISO 4217 codes in capitals, such as USD and JPY.
  • Every object can carry custom: your own fields, stored as they are and not shown to the AI by default.
  • A customer's tags replace only the tags the API set before; tags your agents added in the console are kept. Leave tags out to change nothing; [] removes the API's tags.
  • A customer can carry totalSpent + totalSpentCurrency and ordersCount (lifetime spend and order count as your system knows them). Customer segments use them first; without them, purser counts the orders it has received.

Order statuses: pending, processing, partially_shipped, shipped, delivered, cancelled, refunded, returned.

Shipment statuses: label_created, in_transit, out_for_delivery, delivered, delayed, exception, lost, returned_to_sender.

Every field is listed in the API reference.

Example: push one order

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" }] }

Each object's result is one of created, updated or stale.

The order's customerId must match the sub in the identity token; that is how the AI finds a signed-in customer's orders when they ask in the chat.

Idempotency keys

The write endpoints accept an Idempotency-Key header, with the same rules as Stripe:

  • same key, same body, again within 24 hours: you get the first response back and nothing is processed twice;
  • same key, different body: 409 (CONFLICT), which usually means a bug in your code.

Send the same key when you retry after a network timeout and you never write twice.

Bulk upserts and history imports

The synchronous endpoints take at most 25 objects per call. For more, use the /bulk endpoints: up to 1,000 objects per call, validated immediately and processed in the background. They answer 202 with { "batchId": "…" } right away; follow progress with GET /batches/{id} (pending, processing, done, failed). The upsert rules are the same as for the synchronous endpoints.

To import hundreds of thousands of past orders, group many batches into one history import (backfill) with a single progress number:

  1. POST /backfills (optionally with { "label": "…" }) returns an id;
  2. send each /bulk request with "backfillId": "<id>";
  3. when you have sent everything, POST /backfills/{id}/close;
  4. follow GET /backfills/{id}; it becomes done once every object received has been processed.

Errors

Every non-2xx response has this body:

{ "code": "CONFLICT", "status": 409, "message": "…", "data": { } }
StatusMeaning
400The body does not match the schema; data.issues says where
401No key, an unknown key or a revoked key
404No such object in this key's workspace
409The idempotency key was used with a different body; or the backfill does not exist or is already closed
413Too many objects in one call; split it, or use /bulk
429Too many requests. The response carries Retry-After (seconds); wait that long and retry

Rate limits

Per minute; over the limit you get 429 with Retry-After: 60:

WhatLimit
All calls with one secret key (sk_…)600 a minute
Of those, /bulk routes30 a minute
Chat sessions started (/widget/sessions), same shop and IP60 a minute

For large amounts of history use /bulk and history imports (above) rather than looping one object at a time. The Node SDK backs off and retries on 429 by itself.

Node SDK: @purser-ai/node

Not on npm yet

@purser-ai/node has not been published, so npm install will not find it yet. Until it is, call the REST API directly (the curl above is a complete request). What follows is how it works once published.

Zero dependencies, Node 20 or later (it also runs on Bun, Deno and Cloudflare Workers: it only uses fetch and WebCrypto). Request and response types are generated from the API's OpenAPI document.

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(),
});

// History: an array or any (async) iterable, so a paginated export streams straight through
const result = await purser.backfill("orders", fetchAllOrders(), { onProgress: console.log });
MemberNotes
new Purser({ secretKey, baseUrl? })secretKey must start with sk_; baseUrl defaults to https://askpurser.com
ping()Returns the key's workspace, { orgId }
customers / products / orders / shipments / ticketsEach has upsert(objectOrArray, { idempotencyKey? })
backfill(resource, source, opts?)resource is one of customers, products, orders, shipments; options label, concurrency (default 4), pollMs (default 2000), onProgress
signIdentityToken(...)Sign an identity token, see Identity tokens
actionsHandler(...), verifyActionSignature(...)The server side of the refund endpoint, see Refund endpoint

What the SDK does for you:

  • Coalescing: single-object upsert calls for the same resource within 20 ms share one request (each call still gets its own result), which suits a burst of webhooks;
  • Batching: arrays are split into requests of 25 objects (50 for tickets);
  • Idempotency: every write carries an Idempotency-Key; if you pass idempotencyKey, each split batch uses <your key>:<offset>;
  • Retries: network errors, 429 and 5xx are retried with jittered backoff, at most 4 attempts in total; other 4xx errors are thrown at once;
  • History imports: backfill sends batches of 1,000 through /bulk, closes the backfill when the source is exhausted, then polls until it is done (or a batch has failed) and returns the final progress;
  • Errors: it throws PurserError with status, code and the API's body.

Identity tokens: telling purser who the customer is

Sign a short-lived, single-use token on your server with the identity secret so the chat widget recognises a signed-in customer.

Refund endpoint (Actions API)

A signed HTTPS endpoint your custom store implements so purser can ask for a refund amount and start a refund: request format, signature check, idempotency, retries and reporting the outcome.

On this page

BasicsTwo kinds of keyData endpointsUpsert rulesExample: push one orderIdempotency keysBulk upserts and history importsErrorsRate limitsNode SDK: @purser-ai/node