跳到主要内容

方式五:API 接口对接

适合有自研订单系统、或前四种方式满足不了的定制需求。和平台授权 / ERP 授权的区别只有一条:这条路要写代码。 只是单量大、不想开发的话,先看方式二:批量导入;订单在平台或第三方 ERP 里,看方式三~四:自动进单

什么时候才值得走 API

你的订单在哪建议走哪条
Amazon、Shopify、乐天等销售平台平台授权同步,不用开发
在用的第三方 ERPERP 授权同步,不用开发
只是量大,人工录不过来批量导入,不用开发
自研订单系统,或要定制字段、定制触发时机API 接口对接

对接前要拿到什么

东西从哪来
appKey / appSecretOMS 右上角头像 → API信息/userCenter/apiKeys
接口文档https://apidoc-oms.xlwms.com/
接口基础地址https://api.xlwms.com/openapi/

这个页面是一张表:App Key / App Secret / 备注 / 创建时间 / 创建人,密钥默认打码显示。右上角还有个开发验签工具按钮,直达官方验签页面。

在哪拿 appKey 和 appSecret
注意 appSecret 是签名密钥,只能放在你自己的服务端,不要写进前端代码或 App

请求怎么签名

每个请求都要带签名 authcode,算法是 HmacSHA256:

  1. 把业务参数 data 里的字段按**字典序(不区分大小写)**排序,再序列化成 JSON
  2. appKey + 排序后的 data + reqTime 的顺序拼成一个字符串,中间没有任何分隔符
  3. appSecret 为密钥对该字符串做 HmacSHA256,结果转成十六进制字符串,就是 authcode

传参方式也要注意:authcode 走 URL 查询参数,appKey / data / reqTime 走 POST 请求体。

参数位置说明
authcodeURL query上面算出来的签名
appKeyPOST bodyOMS 后台给的 key
reqTimePOST body10 位 UNIX 秒级时间戳
dataPOST body业务参数,创建接口是数组
重要 reqTime 超过 5 分钟的请求会被服务端拒绝。 别用缓存下来的签名重放,也别让服务器时钟漂太多。

签名对不上就用官方验签工具

文档站提供了在线验签工具,把你的 appKeydatareqTime 贴进去比对,能立刻看出是排序、序列化还是拼接出了问题。

签名示例代码

按文档实现的最小可运行版本,联调前请先用官方验签工具核对一次

js
// Node.js 18+
import crypto from 'node:crypto'

function sign({ appKey, appSecret, data, reqTime }) {
  // 1. data 内字段按字典序(不区分大小写)排序后序列化
  const sortedData = JSON.stringify(sortKeys(data))
  // 2. 三段直接拼接,没有分隔符
  const raw = `${appKey}${sortedData}${reqTime}`
  // 3. HmacSHA256 后转十六进制
  return crypto.createHmac('sha256', appSecret).update(raw).digest('hex')
}

function sortKeys(value) {
  if (Array.isArray(value)) return value.map(sortKeys)
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.keys(value)
        .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
        .map((k) => [k, sortKeys(value[k])])
    )
  }
  return value
}

const appKey = process.env.OMS_APP_KEY
const appSecret = process.env.OMS_APP_SECRET
const reqTime = String(Math.floor(Date.now() / 1000))
const data = [
  {
    whCode: 'TEST',
    thirdOrderNo: 'SO-20260903-0001',
    subOrderType: 1,
    logisticsChannel: 'T_YAMATO_BIN',
    receiver: '佐藤 健太',
    telephone: '09012345678',
    countryRegionCode: 'JP',
    provinceName: '東京都',
    cityName: '渋谷区',
    postCode: '150-0002',
    addressOne: '渋谷 3-15-7',
    isSubmit: 1,
    itemList: [{ sku: 'APP-TSHIRT-006', quantity: 1 }]
  }
]

const authcode = sign({ appKey, appSecret, data, reqTime })
const res = await fetch(
  `https://api.xlwms.com/openapi/v1/outboundOrder/create?authcode=${authcode}`,
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ appKey, reqTime, data })
  }
)
console.log(await res.json())
python
# Python 3
import hashlib, hmac, json, os, time, requests

def sort_keys(value):
    if isinstance(value, list):
        return [sort_keys(v) for v in value]
    if isinstance(value, dict):
        return {k: sort_keys(value[k]) for k in sorted(value, key=str.lower)}
    return value

def sign(app_key, app_secret, data, req_time):
    sorted_data = json.dumps(sort_keys(data), ensure_ascii=False, separators=(',', ':'))
    raw = f'{app_key}{sorted_data}{req_time}'
    return hmac.new(app_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()

app_key, app_secret = os.environ['OMS_APP_KEY'], os.environ['OMS_APP_SECRET']
req_time = str(int(time.time()))
data = [{
    'whCode': 'TEST',
    'thirdOrderNo': 'SO-20260903-0001',
    'subOrderType': 1,
    'logisticsChannel': 'T_YAMATO_BIN',
    'receiver': '佐藤 健太',
    'telephone': '09012345678',
    'countryRegionCode': 'JP',
    'provinceName': '東京都',
    'cityName': '渋谷区',
    'postCode': '150-0002',
    'addressOne': '渋谷 3-15-7',
    'isSubmit': 1,
    'itemList': [{'sku': 'APP-TSHIRT-006', 'quantity': 1}],
}]

authcode = sign(app_key, app_secret, data, req_time)
r = requests.post(
    'https://api.xlwms.com/openapi/v1/outboundOrder/create',
    params={'authcode': authcode},
    json={'appKey': app_key, 'reqTime': req_time, 'data': data},
)
print(r.json())

创建一件代发出库单

POST /v1/outboundOrder/create单次最多 100 单

主要字段(完整字段以官方文档为准):

字段必填对应界面上的说明
whCode✳︎仓库仓库代码,最长 30
thirdOrderNo✳︎——外部单号,全局唯一,最长 100
subOrderType✳︎——1 = 按产品,2 = 按箱
logisticsChannel✳︎物流渠道渠道代码,如 T_YAMATO_BIN
referOrderNo参考单号你自己系统的编号
platformOrderNo平台单号平台上的原始订单号
isSubmit——0 = 存草稿(默认),1 = 直接提交
remark备注接口限 255,但承运商侧只接受 32 个全角
receiver✳︎收件人
telephone电话接口非必填,但黑猫 / 佐川必须填
countryRegionCode✳︎国家/地区日本固定 JP
provinceName✳︎省/州日本填都道府县,如 東京都
provinceCode✳︎省/州美国、加拿大、澳大利亚必填
cityName✳︎城市名称日本填市区町村
postCode✳︎邮编
addressOne✳︎地址-1接口限 255,但承运商侧只接受 16 个全角
addressTwo地址-2同上,16 个全角
district区/县发日本留空
itemList[].sku✳︎SKU
itemList[].quantity✳︎出库数量
同方式一 字段的业务规则和后台建单完全一致——日本地址怎么拆、哪些渠道要求电话、字数上限,都看方式一。接口层面的长度限制比承运商宽松,按接口填不报错,到承运商那里照样出问题注意 isSubmit 决定单据落在哪:填 1 直接提交,之后的走向仍按渠道分叉——黑猫直接进仓库处理中,佐川停在草稿的「待提交」。见第 6 步

建完之后还有这些接口

用途接口
分页查询出库单列表getParcelOutboundOrders
查询出库单详情(回读状态、跟踪号)getOutboundOrder
批量取消出库单(异步batchCancelOutboundOrder
查询出库单聚合状态(确认取消结果)selectBizStatus
更新跟踪号或面单(用自有面单时)updateTrackNoAndLabel
出库单加留言和附件replyBigOutboundMessage

三个必须避开的坑

重要 HTTP 500 不等于创建失败。 官方文档写明网络异常时不能直接当作失败——盲目重试会重复建单。正确做法是用查询详情接口thirdOrderNo 核对,确认没建成再重试。注意 code: 200 只代表这次请求成功,不代表每一单都建成了。逐单结果看返回的 data[].successdata[].msg注意 thirdOrderNo 全局唯一,拿它当幂等键——重复提交同一个外部单号不会重复建单,这是你重试逻辑的安全网。

备货中转(ToB)走另一组接口

本页讲的都是一件代发(小包出库单)。备货中转出库(ToB)是另一组接口:createBigOutboundOrder(按产品)和 createBoxStockOutboundOrder(按箱),查询和取消接口也是独立的一套。

接口文档以官方为准

本页只讲对接一件代发出库单的主干流程和容易踩的坑。完整字段、错误码、以及其他业务的接口,看官方文档:https://apidoc-oms.xlwms.com/

配合领星OMS操作文档食用更佳