OrangeShip 开放接口
v1 · 2026-09-29 https://orangeshipping.xyz/open/v1
目录
  1. 1 概述
  2. 2 环境与凭证
  3. 3 鉴权
  4. 4 接口参考
  5. 5 幂等与重试
  6. 6 订单变更拉取
  7. 7 数据字典
  8. 8 错误处理与限流
  9. 9 对接要求
  10. 10 版本管理
API Reference · v1

OrangeShip 开放接口:订单履约 API

渠道方通过本接口把待发货订单交给 OrangeShip 履约,并取回运单号与物流状态。

版本
v1(1.0-draft)
更新日期
2026-09-29
协议
HTTPS · JSON · UTF-8
鉴权
AppKey + HMAC-SHA256 签名

01概述

OrangeShip 是订单履约平台。渠道方(电商平台、订单管理系统、ERP 等)通过本接口把已确认的订单推送给 OrangeShip,由 OrangeShip 分配海外仓或承运商完成履约;渠道方再通过本接口取回运单号和物流状态,回写到自己的系统。

调用方向是单向的:只有渠道方调用 OrangeShip,OrangeShip 不回调渠道方。订单状态的变化通过「拉取订单变更」接口按游标增量获取。

1.1接口一览

接口方法与路径用途限流分组
连通性检查GET/ping校验凭证与签名,返回连接信息与服务器时间。查询
推送订单POST/orders批量推送订单,每次最多 200 单,按 order_id 幂等。写入
取消订单POST/orders/{order_id}/cancel退款、取消或改地址时取消订单。写入
拉取订单变更GET/orders/changes按游标增量取回状态、运单号的变化。取运单号的主通道。查询
查询订单GET/orders/{order_id}单笔订单快照,含商品行映射状态。查询
查询物流轨迹GET/orders/{order_id}/tracking单笔订单的物流事件,买家查看物流时按需调用。查询

1.2对接流程

  1. 获取凭证

    商家在 OrangeShip 后台「渠道接入」创建连接,把 AppKey 和 AppSecret 交给渠道方(§2.2)。

  2. 实现签名

    按 §3.2 实现签名,先用 §3.4 的测试向量离线自测。

  3. 验证连通

    调用 GET /ping,返回 code: 0 即凭证与签名正确。

  4. 推送与取消

    订单锁定后调用 POST /orders;退款、取消或改地址时调用 POST /orders/{order_id}/cancel。

  5. 取回运单号

    定时调用 GET /orders/changes,按游标拉取状态与运单号并回写(§6)。

  6. 按需查询

    买家查看物流时调用 GET /orders/{order_id}/tracking。

1.3渠道专用字段

部分字段只适用于特定渠道类型,在字段说明中以标签注明(如 PledgeBox)。其余字段,以及鉴权、幂等、订单变更、订单状态、错误码与限流,对所有渠道相同。

02环境与凭证

2.1环境

环境Base URL说明
生产https://orangeshipping.xyz/open/v1正式订单。凭证由商家在 OrangeShip 后台生成。
联调同生产OrangeShip 为渠道方开通独立的联调商家与连接,凭证由 OrangeShip 直接提供。联调连接的订单不会发往真实仓库。

本文所有路径都相对于 Base URL。例如「推送订单」的完整地址是 POST https://orangeshipping.xyz/open/v1/orders。

2.2凭证

凭证格式说明
AppKey32 位十六进制连接的公开标识,放在请求头 X-App-Key。
AppSecret48 位字符签名密钥,不随请求发送。只在商家创建连接时显示一次,不可找回;遗失需新建连接。
  • 一个连接对应渠道方的一个店铺或项目。
  • 订单归属哪个商家、哪个连接由 AppKey 决定,请求中不需要传商家或企业标识。
  • order_id 在连接内唯一;不同连接可以出现相同的 order_id。
  • 轮换凭证:商家新建连接并切换到新凭证,再停用旧连接。停用后旧连接拒绝推单和取消,但仍可读:请继续用旧凭证拉取其订单变更,直到在途订单全部取回(订单不会在连接之间迁移)。

2.3通用约定

项目约定
传输仅 HTTPS。
编码JSON,UTF-8。带 body 的请求必须带 Content-Type: application/json。
时间ISO 8601 日期时间,必须带时区,推荐 UTC(2026-09-18T02:10:00Z),也可带偏移(2026-09-18T10:10:00+08:00)。不带时区的值会被拒绝。响应中的时间一律为 UTC 并带毫秒,如 2026-09-20T08:00:05.000Z。
金额小数字符串,如 "129.00",不接受 JSON 数字。格式 ^-?\d{1,10}(\.\d{1,6})?$;按 2 位小数存储,多出的小数位四舍五入。
标识一律为字符串。oms_order_id 是 OrangeShip 内部订单号(64 位整数的字符串形式),仅供参考,调用接口时始终使用 order_id。
字符串校验前先去掉首尾空白;空串(含只有空白的字符串)按未传处理。
响应信封所有响应为 { "code": 整数, "message": 字符串, "data": 对象或 null },另带 requestId(字符串或 null)。code = 0 表示成功,其它见 §8.2。
可空字段标为可空的字段总会出现,值为 null,不会省略。
未知字段请求中的未知字段会被忽略;客户端也必须忽略响应中的未知字段(§10)。

03鉴权

每个请求都用连接的 AppSecret 做 HMAC-SHA256 签名,并把签名放在请求头里。不需要登录,也没有 token 交换。

3.1请求头

请求头必填说明
X-App-Key是连接的 AppKey。
X-Timestamp是Unix 时间戳,单位秒,十进制整数字符串。与服务器时间相差超过 300 秒的请求被拒绝(41102)。
X-Signature是按 §3.2 计算的签名,小写十六进制。
Content-TypePOST 必填application/json。缺失时服务端读不到请求体,签名校验失败(41101)。

3.2签名算法

规范化串
method      = 大写的 HTTP 方法,如 POST、GET
path        = 请求路径,含 /open/v1 前缀,不含 query string,如 /open/v1/orders/changes
body_hash   = lowercase_hex( SHA-256( 请求体的原始字节 ) )
canonical   = X-Timestamp + "." + method + "." + path + "." + body_hash
X-Signature = lowercase_hex( HMAC-SHA256( key = AppSecret, message = canonical ) )

规则:

  1. 签名的是实际发送的字节。请求体只序列化一次,用这串字节计算 body_hash 并原样发送;签名后不要再次序列化。
  2. 无请求体(所有 GET)时按空串计算,其 SHA-256 为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。
  3. path 为绝对路径,以 / 开头并包含 /open/v1 前缀;不含 scheme、host、端口和 query string。路径中的 order_id 先做百分号编码再参与签名,与发出的请求行保持一致。
  4. X-Timestamp 原样参与签名,不补零、不带小数。
  5. 方法用大写,十六进制用小写。
  6. AppSecret 按 UTF-8 原始字节使用,不做 base64 解码。
  7. 四段之间用字符 . 连接,末尾没有换行。

3.3示例代码

Node.js 18+(无第三方依赖)
const { createHash, createHmac } = require('node:crypto');

// url:完整请求地址;body:将要发送的字符串(GET 传 '')
function signRequest({ appKey, appSecret, method, url, body = '' }) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const path = new URL(url).pathname;   // 如 /open/v1/orders,不含 query
  const bodyHash = createHash('sha256').update(body, 'utf8').digest('hex');
  const canonical = `${timestamp}.${method.toUpperCase()}.${path}.${bodyHash}`;
  const signature = createHmac('sha256', appSecret).update(canonical, 'utf8').digest('hex');
  return {
    'X-App-Key': appKey,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
    ...(body ? { 'Content-Type': 'application/json' } : {}),
  };
}

// 调用示例:推送订单
const url = 'https://orangeshipping.xyz/open/v1/orders';
const body = JSON.stringify(payload);          // 只序列化一次
const res = await fetch(url, { method: 'POST', body, headers: signRequest({ appKey, appSecret, method: 'POST', url, body }) });
Python 3.8+(标准库)
import hashlib, hmac, json, time, urllib.parse, urllib.request

def sign_request(app_key, app_secret, method, url, body=b""):
    timestamp = str(int(time.time()))
    path = urllib.parse.urlsplit(url).path      # 如 /open/v1/orders,不含 query
    body_hash = hashlib.sha256(body).hexdigest()
    canonical = f"{timestamp}.{method.upper()}.{path}.{body_hash}".encode("utf-8")
    signature = hmac.new(app_secret.encode("utf-8"), canonical, hashlib.sha256).hexdigest()
    headers = {"X-App-Key": app_key, "X-Timestamp": timestamp, "X-Signature": signature}
    if body:
        headers["Content-Type"] = "application/json"
    return headers

# 调用示例:推送订单
url = "https://orangeshipping.xyz/open/v1/orders"
body = json.dumps(payload, separators=(",", ":")).encode("utf-8")   # 只序列化一次
req = urllib.request.Request(url, data=body, method="POST",
                             headers=sign_request(app_key, app_secret, "POST", url, body))
with urllib.request.urlopen(req, timeout=60) as resp:
    result = json.loads(resp.read())

3.4测试向量

用以下固定输入验证签名实现,结果必须逐字一致。向量已分别用 Node.js 与 Python 独立计算。

AppSecret
demo-secret-do-not-use-in-production
X-Timestamp
1789000000

向量 A:POST /orders(带请求体)

MethodPOST
Path/open/v1/orders
原始请求体(精确字节,无换行){"batch_id":"wave-2-2026-09-18-001","orders":[]}
body_hash3884346b6e08c3ad2dfcec6473332f733a7e646cb6c5641b670419e2b993878d
canonical1789000000.POST./open/v1/orders.3884346b6e08c3ad2dfcec6473332f733a7e646cb6c5641b670419e2b993878d
X-Signaturedba4cb3f99da1ca98e32848cfa7a6b918f2f230fa432aebfc1fc1e00017f8fd5

向量 B:GET /orders/changes(空请求体;query string 不影响签名)

MethodGET
Path/open/v1/orders/changes
原始请求体(空)
body_hashe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
canonical1789000000.GET./open/v1/orders/changes.e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Signature86025f3bbf1b59a5c73dbb4742f2dd33e7e350fd2d474bc02f4ef30a30f94c7c

向量 C:POST /orders/{order_id}/cancel(请求体 {})

MethodPOST
Path/open/v1/orders/ord_8f3a1c/cancel
原始请求体{}
body_hash44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
canonical1789000000.POST./open/v1/orders/ord_8f3a1c/cancel.44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
X-Signatured2ef46faae2cf8f2ff84dd3a23385ab92b590d96653fd0836d8b048a60ca72ae

命令行复现向量 A(两条均输出 dba4cb3f99da1ca98e32848cfa7a6b918f2f230fa432aebfc1fc1e00017f8fd5):

shell · node
node -e "const c=require('crypto');const b='{\"batch_id\":\"wave-2-2026-09-18-001\",\"orders\":[]}';console.log(c.createHmac('sha256','demo-secret-do-not-use-in-production').update('1789000000.POST./open/v1/orders.'+c.createHash('sha256').update(b).digest('hex')).digest('hex'))"
shell · python3
python3 -c "import hashlib,hmac;b=b'{\"batch_id\":\"wave-2-2026-09-18-001\",\"orders\":[]}';print(hmac.new(b'demo-secret-do-not-use-in-production',('1789000000.POST./open/v1/orders.'+hashlib.sha256(b).hexdigest()).encode(),hashlib.sha256).hexdigest())"

3.5时间窗与重放

  • X-Timestamp 与服务器时间相差超过 300 秒(任一方向)返回 41102。请开启 NTP 时间同步;收到 41102 时,用 GET /ping 返回的 server_time 核对时钟后,换新时间戳重试。
  • 不使用 nonce。方法、路径、时间戳与请求体都参与签名,截获的签名只对同一个请求在 300 秒内有效;用于其它路径或方法会被拒绝(41101)。在时间窗内重放同一个请求是无害的:写接口幂等(§5),查询接口只读。
  • 三个鉴权请求头应视为机密:只走 HTTPS,代理与日志不要记录请求头。

04接口参考

所有接口都需要 §3.1 的鉴权请求头。鉴权失败、连接停用、限流等通用错误统一见 §8.2,以下各接口只列出特有的错误。

4.1连通性检查

GET/ping

校验凭证与签名,返回连接信息和服务器时间。适合做「测试连接」按钮。

请求参数

无。

请求示例

HTTP
GET /open/v1/ping HTTP/1.1
Host: orangeshipping.xyz
X-App-Key: 3f9c1d2e4b5a69788796a5b4c3d2e1f0
X-Timestamp: 1789000000
X-Signature: 0ffa6fd797ff65dc66f440be6a1911f99ac4d1bb357292621bd10bdf13a18cef

示例签名由 §3.4 的演示密钥对 1789000000.GET./open/v1/ping.e3b0c442…b855 计算得出。

响应

HTTPcode说明
2000凭证与签名有效。连接已停用时同样返回 200,status 为 disabled(本接口不返回 41103)。

响应字段(data)

字段类型说明
tenant_namestring | null连接所属商家的显示名;读取失败时为 null。
connection_namestring连接名称(商家配置)。
connection_codestring连接短码,与 OrangeShip 后台显示一致。建议与 connection_name 一起展示,供商家确认凭证填对了连接。
statusstringactive 或 disabled。disabled 表示连接已停用:可以查询,不能推单和取消;「测试连接」应将其视为失败并提示商家启用。
server_timestring服务器当前时间(UTC,带毫秒),用于核对时钟偏差。

响应示例

200 OK
{
  "code": 0,
  "message": "ok",
  "data": {
    "tenant_name": "Shenzhen Damai Technology",
    "connection_name": "Foldable Drone",
    "connection_code": "PB-7K3M9Q",
    "status": "active",
    "server_time": "2026-09-18T02:10:00.000Z"
  }
}

4.2推送订单

POST/orders

批量推送已锁定的订单。每次最多 200 单、请求体不超过 2 MB;每单独立处理,单个订单不合法不影响同批其它订单。按 order_id 幂等(§5)。

请求参数(body)

「必填」列:必填 缺失或不合规时该单进入 rejected;应填 缺失时仍接收,但可能在分发时转异常(如直发承运商要求电话);可选 可省略。长度与格式限制对所有出现的字段生效,超限时整单进入 rejected,原因形如 shipping.wave must be at most 64 characters。

字段类型必填说明
batch_idstring应填本批推送的标识,仅用于日志与排查,不参与幂等。≤ 64 字符。
ordersarray必填订单对象,1–200 个。
orders[].order_idstring必填订单级、不可变、连接内唯一。买家升级或追加商品产生的新订单必须使用新的 order_id。≤ 64 字符。
orders[].pbidPledgeBoxstring必填PledgeBox 展示 ID,用于对账并显示给仓库人员;多个订单可共用。≤ 64 字符。
orders[].platformPledgeBoxstring应填众筹平台,如 kickstarter、indiegogo。≤ 32 字符。
orders[].platform_backer_idPledgeBoxstring可选支持者在众筹平台上的标识。≤ 64 字符。
orders[].projectPledgeBoxobject应填{ "id", "name" }。连接配置了项目 ID 时,id 必须一致,否则整单拒收,原因 project.id does not match the project of this connection;任一方为空时不校验。id ≤ 64,name ≤ 256。
orders[].statusstring必填固定为 locked,表示订单已锁定、不会再修改;其它值拒收。
orders[].locked_atPledgeBoxstring应填锁定时间(§2.3 时间格式),作为订单在 OrangeShip 的创建时间。
orders[].updated_atstring必填订单最后修改时间(§2.3 时间格式)。重推时与已存值比较,更早的推送返回 stale。
orders[].currencystring应填ISO 4217 币种代码,按 ISO 列表校验,不区分大小写。
orders[].amountobject应填{ "total", "shipping", "tax" },金额字符串(§2.3)。
orders[].backer.emailPledgeBoxstring应填买家邮箱,加密存储,仅用于售后排查。≤ 256 字符。
orders[].shipping_addressobject必填收货地址,字段见下。
…shipping_address.namestring应填收件人。≤ 128 字符。
…shipping_address.phonestring应填收件人电话。直发承运商必须有电话,缺失的订单会在分发时转异常。≤ 64 字符。
…shipping_address.address1string应填≤ 256 字符。
…shipping_address.address2string | null可选≤ 256 字符。
…shipping_address.citystring应填≤ 128 字符。
…shipping_address.provincestring | null可选州 / 省代码或名称。≤ 128 字符。
…shipping_address.zipstring | null可选邮编。多数目的地的承运商要求邮编。≤ 32 字符。
…shipping_address.country_codestring必填ISO 3166-1 二位国家代码,按 ISO 列表校验,不区分大小写。
orders[].shippingobject可选{ "method", "wave", "region" },自由文本,用于展示与路由(wave / region 为发货波次与地区)。method ≤ 128,wave ≤ 64,region ≤ 64。
orders[].itemsarray必填商品行,至少 1 行。
…items[].line_idstring必填订单内唯一,重推时保持不变。≤ 64 字符。
…items[].typePledgeBoxstring应填reward / addon / bundle / gift(不校验取值)。≤ 16 字符。
…items[].skustring必填可发货的 SKU。任一行缺失时整单拒收。≤ 128 字符。
…items[].namestring应填商品名称。≤ 512 字符。
…items[].quantityinteger必填1–9999。
…items[].unit_pricestring应填单价,金额字符串(§2.3)。
orders[].notestring | null可选给仓库的备注。≤ 1000 字符。

请求示例

HTTP
POST /open/v1/orders HTTP/1.1
Host: orangeshipping.xyz
Content-Type: application/json
X-App-Key: 3f9c1d2e4b5a69788796a5b4c3d2e1f0
X-Timestamp: 1789000000
X-Signature: <按 §3.2 计算>

{
  "batch_id": "wave-2-2026-09-18-001",
  "orders": [
    {
      "order_id": "ord_8f3a1c",
      "pbid": "PB123456",
      "platform": "kickstarter",
      "platform_backer_id": "KS-98765",
      "project": { "id": "prj_001", "name": "Foldable Drone" },
      "status": "locked",
      "locked_at": "2026-09-18T02:10:00Z",
      "updated_at": "2026-09-18T02:10:00Z",
      "currency": "USD",
      "amount": { "total": "129.00", "shipping": "19.00", "tax": "0.00" },
      "backer": { "email": "backer@example.com" },
      "shipping_address": {
        "name": "Jane Doe", "phone": "+1 415 555 0100",
        "address1": "1 Market St", "address2": "Suite 300",
        "city": "San Francisco", "province": "CA", "zip": "94105", "country_code": "US"
      },
      "shipping": { "method": "Standard", "wave": "Wave 2", "region": "North America" },
      "items": [
        { "line_id": "L1", "type": "reward", "sku": "DRONE-BLK", "name": "Drone (Black)", "quantity": 1, "unit_price": "99.00" },
        { "line_id": "L2", "type": "addon",  "sku": "BATT-01",   "name": "Extra Battery", "quantity": 2, "unit_price": "15.00" }
      ],
      "note": "Leave at door"
    }
  ]
}

处理规则(按订单)

订单在 OrangeShip 的现状处理结果
不存在创建订单。created
已存在,尚未推送到仓库(pending,或未进仓的 exception)用新报文整单替换。updated
已存在,已推送到仓库(processing,或进仓后的 exception)只更新备注等不影响履约的字段;金额、地址、商品行保持推仓时的内容。地址或商品行与推仓时不同时,订单转为 exception(exception_code: order_changed,已是 exception 的保持不变)由运营核对。不会再次推送到仓库。touched
已存在,报文 updated_at 早于已存值忽略。stale
与此前某次推送的订单对象完全相同且已处理完返回上次的结果,不再处理(§5)。duplicate
本次请求内未处理完(临时错误,或大批次用完了 25 秒的同步处理时间)订单对象已保存,转为异步处理,通常一分钟内完成,结果在订单变更中可见。oms_order_id 为 null。不需要重试。queued
已存在,状态为 shipped / in_transit / delivered不更新。进入 rejected,原因 already_shipped
已存在,状态为 cancelled不更新、不恢复。替换订单须使用新的 order_id(§9)。进入 rejected,原因 already_cancelled
!
HTTP 200 不代表每单都被接收

只要请求本身合法,HTTP 状态就是 200,即使所有订单都被拒收。请逐条检查 data.rejected。

响应

HTTPcode说明
2000请求已处理,逐单结果见 accepted / rejected。
40040001请求体不是合法 JSON;orders 缺失、为空、不是数组或超过 200 个;batch_id 超过 64 字符。整批不做处理。
41341300请求体超过 2 MB,请拆批。

响应字段(data)

字段类型说明
accepted[]array被保存或匹配到的订单,每单一条。
accepted[].order_idstring回显 order_id。
accepted[].pbidPledgeBoxstring回显 pbid。
accepted[].oms_order_idstring | nullOrangeShip 内部订单号;结果为 queued 时为 null。
accepted[].resultstring处理结果,取值见 §7.3。
rejected[]array未被接收的订单,每单一条。拒收记录(订单号、原因,不含收件人信息)在商家后台保留 90 天,供双方对账。
rejected[].order_idstring | null回显;报文没有 order_id 时为 null。
rejected[].pbidPledgeBoxstring | null回显;缺失时为 null。
rejected[].reasonstring拒收原因。already_shipped、already_cancelled 为固定值,可用于程序判断;其余为校验信息(如 items[0].sku is required),措辞可能调整。

响应示例

200 OK
{
  "code": 0,
  "message": "ok",
  "data": {
    "accepted": [
      { "order_id": "ord_8f3a1c", "pbid": "PB123456", "oms_order_id": "1024", "result": "created" },
      { "order_id": "ord_8f3a1f", "pbid": "PB123459", "oms_order_id": null,   "result": "queued" }
    ],
    "rejected": [
      { "order_id": "ord_8f3a1d", "pbid": "PB123457", "reason": "items[0].sku is required" },
      { "order_id": "ord_8f3a1e", "pbid": "PB123458", "reason": "already_shipped" }
    ]
  }
}

4.3取消订单

POST/orders/{order_id}/cancel

因退款、取消或买家修改地址而取消订单。修改地址的正确做法是取消旧订单后用新的 order_id 重新推送(§9)。

请求参数

参数位置类型必填说明
order_idpathstring必填推单时使用的 order_id,百分号编码。
reasonbodystring可选取消原因,≤ 256 字符,展示给运营。建议取值:refunded、cancelled_by_customer、address_changed、fraud。无原因时请求体发 {}。

请求示例

HTTP
POST /open/v1/orders/ord_8f3a1c/cancel HTTP/1.1
Host: orangeshipping.xyz
Content-Type: application/json
X-App-Key: 3f9c1d2e4b5a69788796a5b4c3d2e1f0
X-Timestamp: 1789000000
X-Signature: <按 §3.2 计算>

{ "reason": "refunded" }

处理规则

订单现状处理结果
pending,或未进仓的 exception取消订单,订单变更中出现 cancelled。例外:正在推送到仓库途中的订单无法安全撤回,按下一行处理。cancelled
processing,或进仓后的 exception订单转为 exception(exception_code: cancel_intercept),由运营到仓库拦截。拦截成功后变为 cancelled;包裹已发出则继续变为 shipped。exception
已在拦截中(此前已取消过,原因不同)不做改变,保留第一次的原因。noop
已 cancelled(原因与此前不同)不做改变。noop
与此前对该单的取消原因相同不做改变,也不重新判断(永久去重,§5)。duplicate
shipped / in_transit / delivered不取消。already_shipped
订单推送仍在排队(尚未建单)取消请求已保存,建单后立即执行,该订单不会发往仓库。queued
其它状态但本次请求内未能执行取消请求已保存,转为异步执行(通常一分钟内),结果在订单变更中可见。queued

响应

HTTPcode说明
2000已处理,结果见 result(包括 already_shipped,不作为错误返回)。
40040001请求体不是 JSON 对象,或 reason 不是字符串、超长。
40440401本连接内没有该订单,也没有排队中的推送(属于其它连接的订单同样返回 404)。

响应字段(data)

字段类型说明
order_idstring回显路径参数。
oms_order_idstring | nullOrangeShip 内部订单号;订单尚未建出时为 null。
resultstring处理结果,取值见 §7.4。

响应示例

200 OK
{ "code": 0, "message": "ok", "data": { "order_id": "ord_8f3a1c", "oms_order_id": "1024", "result": "cancelled" } }

4.4拉取订单变更

GET/orders/changes

按游标增量返回对外可见状态发生变化的订单,是取回运单号的主通道。每个订单只返回最新状态。拉取协议与客户端处理要求见 §6。

请求参数(query)

参数类型必填说明
cursorstring可选上一次响应的 next_cursor。仅首次调用可省略。
sincestring可选仅在不带 cursor 时生效:从该时间及之后的变更开始。格式为日期(2026-09-18,按 UTC 零点)或带时区的日期时间。省略时从该连接的第一笔订单开始。
limitinteger可选每页行数,1–200,默认 100。

请求示例

HTTP
GET /open/v1/orders/changes?cursor=eyJ0IjoiMjAyNi0wOS0yMFQwODowMDowNS4wMDBaIiwiaWQiOiIxMDI0In0&limit=100 HTTP/1.1
Host: orangeshipping.xyz
X-App-Key: 3f9c1d2e4b5a69788796a5b4c3d2e1f0
X-Timestamp: 1789000000
X-Signature: <按 §3.2 计算>

响应

HTTPcode说明
2000成功(orders 可能为空)。
40040001cursor 格式错误或越界、since 格式错误、limit 不在 1–200。错误的游标不会被自动重置。

响应字段(data)

字段类型说明
orders[]array变更行,按 changed_at 升序;同一页中每个订单最多一行。
orders[].order_idstring订单号。
orders[].pbidPledgeBoxstringPledgeBox 展示 ID。
orders[].statusstring订单状态,取值见 §7.1。
orders[].tracking_codestring | null运单号。状态为 shipped、in_transit 时一定有值;已出库但运单号未到的订单报 processing。
orders[].carrierstring | null承运商名称,如 UPS。
orders[].carrier_codestring | null履约系统的服务代码,仅供参考。
orders[].courier_codestring | null在渠道系统中标记发货时使用的承运商代码,见 §7.5。
orders[].shipped_atstring | null包裹交给承运商的时间。
orders[].delivered_atstring | null签收时间,与 delivered 状态一起出现。
orders[].exception_codestring | null异常代码,状态为 exception 时有值,取值见 §7.2。
orders[].exception_reasonstring | null异常说明,供展示给商家,措辞与语言可能变化,不要用于程序判断。
orders[].changed_atstring最近一次对外可见变化的时间。
next_cursorstring下次调用使用的游标,总会返回(包括空页)。
has_morebooleantrue 时立即用 next_cursor 继续拉取。
server_timestring服务器时间。晚于 server_time − 60 s 的变更暂不返回(§6.1)。

响应示例

200 OK
{
  "code": 0,
  "message": "ok",
  "data": {
    "orders": [
      {
        "order_id": "ord_8f3a1e", "pbid": "PB123458", "status": "exception",
        "tracking_code": null, "carrier": null, "carrier_code": null, "courier_code": null,
        "shipped_at": null, "delivered_at": null,
        "exception_code": "sku_unmapped", "exception_reason": "SKU mapping missing: DRONE-RED",
        "changed_at": "2026-09-19T10:00:00.000Z"
      },
      {
        "order_id": "ord_8f3a1c", "pbid": "PB123456", "status": "shipped",
        "tracking_code": "1Z999AA10123456784", "carrier": "UPS", "carrier_code": "UPS-EXP", "courier_code": "UPS",
        "shipped_at": "2026-09-20T08:00:00.000Z", "delivered_at": null,
        "exception_code": null, "exception_reason": null,
        "changed_at": "2026-09-20T08:00:05.000Z"
      }
    ],
    "next_cursor": "eyJ0IjoiMjAyNi0wOS0yMFQwODowMDowNS4wMDBaIiwiaWQiOiIxMDI0In0",
    "has_more": false,
    "server_time": "2026-09-21T03:10:00.000Z"
  }
}

4.5查询订单

GET/orders/{order_id}

返回单笔订单快照:包含订单变更行的全部字段,另加仓库参考号与商品行。不返回收件人个人信息(姓名、地址、电话、邮箱)。

请求参数

参数位置类型必填说明
order_idpathstring必填推单时使用的 order_id,百分号编码。

响应

HTTPcode说明
2000成功。
40440401本连接内没有该订单。

响应字段(data)

包含 §4.4 orders[] 的全部字段,另加:

字段类型说明
reference_numberstring | nullOrangeShip 参考号,印在仓库单据上。一般与 pbid 相同;pbid 不符合仓库或物流系统的单号规则时,为 OrangeShip 生成的编号。
external_numberstring | null推仓后,仓库或承运商系统中的单号。
items[].line_idstring商品行 ID。
items[].skustringSKU。
items[].quantityinteger数量。
items[].mapping_okbooleanSKU 是否已在 OrangeShip 映射到仓库货品。任一行为 false 的订单停在 exception(sku_unmapped),由商家补映射后自动继续,渠道方无需操作。
locked_atstring | null推单时的 locked_at;未传为 null。
updated_atstring最近一次对外可见变化的时间,与 changed_at 相同;不是推单时的 updated_at。

响应示例

200 OK
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_id": "ord_8f3a1c", "pbid": "PB123456", "status": "shipped",
    "tracking_code": "1Z999AA10123456784", "carrier": "UPS", "carrier_code": "UPS-EXP", "courier_code": "UPS",
    "shipped_at": "2026-09-20T08:00:00.000Z", "delivered_at": null,
    "exception_code": null, "exception_reason": null,
    "changed_at": "2026-09-20T08:00:05.000Z",
    "reference_number": "PB123456",
    "external_number": "WH2609200000456",
    "items": [
      { "line_id": "L1", "sku": "DRONE-BLK", "quantity": 1, "mapping_ok": true },
      { "line_id": "L2", "sku": "BATT-01",   "quantity": 2, "mapping_ok": true }
    ],
    "locked_at": "2026-09-18T02:10:00.000Z",
    "updated_at": "2026-09-20T08:00:05.000Z"
  }
}

4.6查询物流轨迹

GET/orders/{order_id}/tracking

返回单笔订单的物流事件。请在买家查看物流时按需调用,不要定时全量轮询;签收状态会通过订单变更返回。

海外仓发货的订单只有里程碑事件(shipped、delivered)。直发订单另有承运商的转运事件:对已发货、未签收的直发订单,调用时若距上次成功刷新超过 30 分钟,OrangeShip 会先向承运商刷新再返回(刷新最多等待数秒,失败时返回已有事件,5 分钟后才会再次尝试);刷新到签收会同时将订单置为 delivered。订单还没有任何事件时,按订单的发货、签收时间生成里程碑,source 为 oms。

请求参数

参数位置类型必填说明
order_idpathstring必填推单时使用的 order_id,百分号编码。

响应

HTTPcode说明
2000成功。未发货的订单同样返回 200,tracking_code 为 null,events 为空数组。
40440401本连接内没有该订单。

响应字段(data)

字段类型说明
order_id 等—order_id、pbid、tracking_code、carrier、carrier_code、courier_code、shipped_at、delivered_at 同 §4.4。
statusstring订单状态,推导方式同订单变更(§7.1)。
events[]array物流事件,按 occurred_at 从新到旧;重复通知已去重。
events[].statusstring归一化状态:shipped、in_transit、delivered、cancelled 或 exception。
events[].raw_statusstring | null履约系统的原始状态码,不要解释其含义。
events[].descriptionstring | null事件描述(承运商语言,通常为英文)。
events[].locationstring | null事件地点。
events[].occurred_atstring事件时间;来源未提供时为 OrangeShip 收到的时间。
events[].sourcestring事件来源,取值见 §7.6。
refreshed_atstring | null最近一次向承运商刷新的时间;从未刷新过(包括海外仓订单)为 null。

响应示例

200 OK
{
  "code": 0,
  "message": "ok",
  "data": {
    "order_id": "ord_8f3a1c", "pbid": "PB123456",
    "status": "in_transit",
    "tracking_code": "1Z999AA10123456784", "carrier": "UPS", "carrier_code": "UPS-EXP", "courier_code": "UPS",
    "shipped_at": "2026-09-20T08:00:00.000Z", "delivered_at": null,
    "events": [
      { "status": "in_transit", "raw_status": "NT", "description": "Departed facility", "location": "Shenzhen", "occurred_at": "2026-09-21T02:00:00.000Z", "source": "carrier" },
      { "status": "shipped",    "raw_status": "PU", "description": null, "location": null, "occurred_at": "2026-09-20T08:00:00.000Z", "source": "carrier" }
    ],
    "refreshed_at": "2026-09-21T03:10:00.000Z"
  }
}

05幂等与重试

操作幂等键重复请求的处理
POST /orders(订单)连接 + order_id同一 order_id 再次推送按 §4.2 的规则更新,不会产生重复订单。
POST /orders(报文)连接 + order_id + 订单对象的规范化 JSON(键递归排序,键顺序与空白不影响)完全相同的订单对象不重复处理:已处理完的返回上次结果(已接收的为 duplicate,拒收的仍在 rejected);仍在异步处理的返回 queued。校验不通过的订单不会被保存,重推会再次校验。
POST /orders/{order_id}/cancel连接 + order_id + 去除首尾空白后的 reason(缺失、null、空白视为同一值)相同原因的取消不重复处理,永久返回 duplicate。
GET 接口—只读,可任意重试。

5.1重试策略

  • 超时、HTTP 5xx:用同一串请求体字节重试(重新生成时间戳与签名),指数退避,从 1 秒开始,最多 5 次。
  • HTTP 429:至少等待 1 秒后指数退避重试。
  • 41102:核对时钟后换新时间戳重试。
  • 40001、41101、41103、41104、41300:修正请求后再发,原样重试没有意义。
  • 客户端超时请设为 60 秒以上。推送订单通常在 30 秒内返回:请求内同步处理最多 25 秒,未处理到的订单返回 queued 后异步完成。超时后重试时,已保存的订单返回 duplicate 或 queued,其余正常处理。

5.2顺序保证

对已存在的订单,推送的 updated_at 与已存值比较:严格更早的推送返回 stale 并被忽略,乱序到达的旧报文不会覆盖新报文。updated_at 相等时正常处理。

06订单变更拉取

6.1规则

  • 触发条件。订单的 changed_at 只在对外可见的内容变化时更新:状态、运单号、承运商、发货时间、签收时间、异常信息,或新增物流事件。渠道方自己的重推若没有改变这些内容,不会出现在变更中;新建的订单会出现一次,状态为拉取时已到达的状态。
  • 排序与游标。结果按 (changed_at, 内部 id) 升序。游标记录最后一行的位置,下次返回其后的行。游标不透明,不要解析或自行构造;游标不会过期。
  • 安全滞后。changed_at 晚于 server_time − 60 秒 的变更暂不返回,以免并发提交的变更落在已推进的游标之前。因此变化最多约一分钟后可见。
  • 只返回最新状态。两次拉取之间发生多次变化的订单只返回一行,可能跳过中间状态(例如 processing 之后直接是 delivered)。同一订单可能再次出现(例如商家修正承运商映射后重新下发),请按 order_id 更新而不是新增。
  • 已取走标记。返回过的、带运单号的已发货订单,会在响应发出时被记为「已取走」,除非订单再次变化,不会重复返回。请持久化并处理完整页后再拉取下一页。

6.2拉取频率

每个连接每 15–60 分钟拉取一次;has_more 为 true 时立即继续,直到为 false。

6.3客户端处理

伪代码
function pull(conn):
    loop:
        resp = GET /orders/changes?limit=200 + (conn.cursor ? &cursor=conn.cursor : "")
        if resp 不是 HTTP 200 / code 0:
            按 §8.2 处理;return               # 保留原游标,不推进
        for row in resp.data.orders:
            apply(row)                          # 必须幂等
        conn.cursor = resp.data.next_cursor     # 整页处理完后再持久化(存数据库)
        if not resp.data.has_more: break

function apply(row):
    order = 按 row.order_id 查本地订单;不存在则记录并跳过
    if row.changed_at <= order.last_changed_at: return    # 已处理过
    switch row.status:
        shipped / in_transit / delivered:
            if row.tracking_code 与本地不同:
                标记已发货(tracking_code、courier_code);通知买家   # 仅首次拿到运单号时通知
            if row.status == delivered: 标记已签收
        exception:  提示商家(按 exception_code 分类,exception_reason 用于展示)
        cancelled:  标记已取消
        pending / processing: 标记履约中(可选)
    order.last_changed_at = row.changed_at
  • 收到错误响应时不推进游标;处理完整页后再保存 next_cursor,空页也要保存。
  • 一个连接一个游标。
  • 订单首次出现时可能是任意状态,不要假设从 pending 开始。
  • 游标丢失时,不带 cursor 和 since 重新从头拉取;数据量大时可用 since 指定一个明显早于丢失时刻的时间(留出几分钟时钟偏差余量)。重新收到的行按幂等规则处理。

07数据字典

7.1订单状态 status

取值含义可能的下一状态
pending已接收,等待推送到仓库(SKU 映射与分仓处理中)。processing、exception、cancelled
processing已推送到仓库或承运商,等待拣货、打包、出面单;或已出库但运单号尚未返回。shipped、exception
shipped已交付承运商,运单号已分配。in_transit、delivered
in_transit最近一条物流事件是承运商的转运扫描,尚未签收。海外仓订单没有此状态。delivered
delivered已签收。终态。—
cancelled发货前已取消(取消接口或运营操作)。终态。—
exception需要处理,原因见 exception_code。pending、processing、shipped、cancelled

同一状态可能多次出现(例如商家修正映射期间 exception → pending → exception),每次有新的 changed_at。

7.2异常代码 exception_code

取值含义渠道方处理
sku_unmapped部分 SKU 尚未映射到仓库货品。无需操作,商家补映射后自动继续。
unfulfillable_line有商品行仓库无法发货。联系商家。
filing_required商品需先在仓库建档。无需操作,由商家处理。
cancel_intercept已进仓的订单被取消,运营拦截中。无需操作,之后变为 cancelled,或已发出时变为 shipped。
order_changed已进仓的订单被重推修改了地址或商品,运营核对中。无需操作;改地址请按 §9 取消后用新 order_id 推送。
route_conflict订单内商品的发货地不一致,需人工决定。无需操作,由商家处理。
address_incomplete地址信息不满足仓库或承运商要求。修正地址:取消后用新 order_id 重新推送。
push_failed仓库或承运商拒绝了订单。无需操作,商家要求时配合。
other其它原因。把 exception_reason 展示给商家。

v1 内可能新增异常代码,客户端遇到未知值时按 other 处理。

7.3推单结果

位置取值含义
accepted[].resultcreated新建订单。
updated未进仓的订单已整单替换。
touched已进仓的订单,仅更新备注等字段。
stale报文比已存的旧,已忽略。
duplicate与已处理的报文相同,返回上次结果。
queued已保存,异步处理中。
rejected[].reasonalready_shipped订单已发货,不再更新。
already_cancelled订单已取消,不再更新。
其它文本校验失败原因,如 items[0].sku is required、status must be locked。以 order could not be stored 开头表示报文无法保存,需修正后重推。

7.4取消结果 result

取值含义
cancelled已取消。
exception订单已进仓,转人工拦截。
already_shipped已发货,无法取消。
noop无需变更(已取消或已在拦截中)。
duplicate与此前的取消请求相同。
queued已保存,异步执行。

status、推单结果与取消结果在 v1 内为封闭集合,新增取值会提前通知(§10)。

7.5承运商字段

字段来源用途
carrierOrangeShip 中的承运商名称,如 UPS、YunExpress。展示。
carrier_code履约系统的服务或渠道代码。参考,提交工单时有用。
courier_code按商家维护的连接级映射表转换:先按 carrier 查,再按 carrier_code 查;都没有映射时等于 carrier(承运商名称未知时为 null)。在渠道系统中标记发货时填写的承运商代码。

如果渠道系统要求承运商代码来自固定列表,请提供列表,商家会在 OrangeShip 后台维护映射。

7.6物流事件来源 events[].source

取值含义
warehouse海外仓系统上报。
carrier承运商系统上报。
omsOrangeShip 根据订单的发货、签收时间生成的里程碑。

遇到未知值时按 carrier 处理。

08错误处理与限流

8.1错误响应

示例
{ "code": 41101, "message": "signature invalid", "data": null, "requestId": "c1f4…" }

请按 code 判断,不要依赖 message 文本。联系技术支持时请提供 requestId。

8.2错误码

codeHTTP名称说明处理
0200OK成功。推单中单个订单的问题在 data.rejected 中返回。—
40001400InvalidParam请求格式或参数不合法。修正请求,不要原样重试。
40401404NotFound本连接内没有该订单。核对 order_id。
41101401SignatureInvalid签名不匹配(POST 缺少 Content-Type: application/json 也会导致)。按 §8.4 排查。
41102401TimestampExpiredX-Timestamp 缺失、不是整数或偏差超过 300 秒。同步时钟后换新时间戳重试。
41103403ConnectionDisabled连接已被商家停用。只对推单、取消返回,查询接口仍可用。停止推单与取消,继续拉取变更直到在途订单取回;联系商家启用或更换凭证。
41104401AppKeyUnknownX-App-Key 缺失或不存在。检查凭证。
41300413PayloadTooLarge请求体超过 2 MB。拆批后重发。
42901429RateLimited超过连接限流,或来源 IP 鉴权失败过多(§8.3)。退避后重试;如同时鉴权失败,先修正签名。
50000500Internal服务端错误。退避重试(写接口幂等);持续出现请联系技术支持。

超过 5 MB 的请求会被网关直接拒绝(HTTP 413,无 JSON 响应体);HTTP 502 / 503 / 504 也可能没有 JSON 响应体,可退避重试。

8.3限流

分组接口限额
写入POST /orders、POST /orders/{order_id}/cancel每个连接每分钟 600 次
查询GET /ping、GET /orders/changes、GET /orders/{order_id}、GET /orders/{order_id}/tracking每个连接每分钟 120 次
鉴权失败全部同一来源 IP 每分钟鉴权失败超过 60 次后,该分钟内再失败的请求返回 429;签名正确的请求不受影响

限流在签名校验通过后按连接计数,不按来源 IP,多个连接共用出口 IP 时互不影响。其它限制:请求体 ≤ 2 MB,每次推送 ≤ 200 单,每页变更 ≤ 200 行。

8.4签名失败排查

  1. 用 §3.4 的测试向量离线验证签名函数。
  2. 确认 path 以 /open/v1 开头且不含 query string。
  3. 确认签名的是实际发送的字节,HTTP 库没有重新序列化请求体。
  4. 确认方法大写、十六进制小写、X-Timestamp 为秒。
  5. 路径中的 order_id 已百分号编码后再签名。
  6. POST 请求带有 Content-Type: application/json。

09对接要求

渠道方的实现须满足以下要求。

  1. 只推送已锁定、不会再修改的订单。推送即进入履约。
  2. 修改地址 = 取消旧订单 + 新 order_id 推送。不要用同一个 order_id 推送改过地址的订单:已进仓的订单地址不会更新,已取消的 order_id 会被拒收。
  3. order_id 订单级且不可变。升级、追加商品、拆单产生的新发货都是新订单。
  4. 每个商品行都是可发货的 SKU。组合商品推送前拆成最小可发货 SKU;尺码、颜色等选项编入 SKU 本身(如 TEE-BLK-L)。同一个 SKU 在商家的所有店铺 / 项目中必须代表同一件实物:OrangeShip 按商家维度把 SKU 映射到仓库货品。
  5. 退款和取消都调用取消接口,且越早越好。订单进入 processing 后需人工拦截,shipped 后无法拦截。
  6. 按 §6 拉取订单变更,每 15–60 分钟一次;拿到运单号后在渠道系统标记发货并通知买家。
  7. 物流轨迹按需查询,不要定时全量轮询。
  8. 一单一包裹。v1 不支持多包裹,仓库拆包时只返回第一个运单号。

10版本管理

  • 版本号在路径中(/open/v1)。不兼容的变更会使用新的路径(/open/v2),v2 发布后 v1 至少继续运行 6 个月。
  • v1 内只做兼容的新增:新增可选请求字段、新增响应字段、新增接口、新增枚举取值(至少提前两周通知)。客户端必须忽略未知字段。
  • v1 内不会重命名或删除字段、修改字段类型、修改签名算法、破坏游标兼容,也不会收紧已接受输入的校验。
  • 变更会在上线前通知各渠道方的对接人。

10.1变更记录

日期变更
2026-09-29Base URL 改为 https://orangeshipping.xyz/open/v1(原地址仍可访问,建议迁移);测试向量随路径与演示密钥更新。文档按接口参考格式重排,接口行为不变。
2026-09-28冻结前收紧输入校验:时间必须带时区、国家与币种按 ISO 列表校验、数量 1–9999、空串按未传处理;新增 exception_code;物流事件 source 改为系统类别;连接停用后仍可查询;限流改为签名通过后按连接计数。
2026-09-19v1 草案首次发布。