Method 5: API integration
For teams with their own order system, or requirements the other four methods can't meet. The only difference from platform and ERP sync: this one needs code. High volume but no appetite for development? See Method 2: bulk import. Orders sitting in a marketplace or a third-party ERP? See Methods 3-4: automatic feeds.
When the API is actually worth it
| Where your orders live | What to use |
|---|---|
| Amazon, Shopify, Rakuten and similar marketplaces | Platform sync, no development |
| A third-party ERP you already run | ERP sync, no development |
| Just too many orders to key in by hand | Bulk import, no development |
| Your own order system, or custom fields and timing | API integration |
What you need before you start
| Thing | Where it comes from |
|---|---|
appKey / appSecret | OMS top right: avatar → API信息 (API information) (/userCenter/apiKeys) |
| API documentation | https://apidoc-oms.xlwms.com/ |
| Base URL | https://api.xlwms.com/openapi/ |
The page is a table of App Key / App Secret / note / created at / created by, with the keys masked by default. A signature tool button in the corner takes you straight to the official verifier.
appSecret is the signing key. Keep it server-side only — never in front-end code or a mobile app.Signing a request
Every request carries an authcode signature, computed with HmacSHA256:
- Sort the fields inside the business payload
datain dictionary order (case-insensitive) and serialise to JSON - Concatenate
appKey+ the sorteddata+reqTime, with no separator between them - HmacSHA256 that string using
appSecretas the key and hex-encode the result — that's yourauthcode
Where each parameter goes matters too: authcode travels as a URL query parameter, while appKey / data / reqTime go in the POST body.
| Parameter | Location | Notes |
|---|---|---|
authcode | URL query | The signature computed above |
appKey | POST body | Issued in the OMS console |
reqTime | POST body | 10-digit UNIX timestamp in seconds |
data | POST body | The business payload; an array for the create endpoint |
reqTime is more than 5 minutes old are rejected by the server. Don't replay cached signatures, and keep your server clock in sync.Signature not matching? Use the official verifier
The documentation site has an online signature tool. Paste in your appKey, data and reqTime and compare — it tells you immediately whether sorting, serialisation or concatenation is the culprit.
Signing example
A minimal working implementation of what the documentation describes. Check it against the official verifier before you start integration testing.
// 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 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())Creating a parcel outbound order
POST /v1/outboundOrder/create, up to 100 orders per request.
The main fields (the official docs are authoritative for the full list):
| Field | Required | Field on screen | Notes |
|---|---|---|---|
whCode | ✳︎ | Warehouse | Warehouse code, max 30 |
thirdOrderNo | ✳︎ | —— | External order number, globally unique, max 100 |
subOrderType | ✳︎ | —— | 1 = by product, 2 = by box |
logisticsChannel | ✳︎ | Logistics channel | Channel code, e.g. T_YAMATO_BIN |
referOrderNo | Reference Number | Your own system's id | |
platformOrderNo | Platform Order Number | The marketplace order number | |
isSubmit | —— | 0 = save as draft (default), 1 = submit straight away | |
remark | Note | The API allows 255, but the carrier only takes 32 full-width | |
receiver | ✳︎ | Recipient | |
telephone | telephone | Optional in the API, but mandatory for Yamato and Sagawa | |
countryRegionCode | ✳︎ | Country/Region | Always JP for Japan |
provinceName | ✳︎ | Province/State | The prefecture for Japan, e.g. 東京都 |
provinceCode | ✳︎ | Province/State | Required for the US, Canada and Australia |
cityName | ✳︎ | City Name | The ward / municipality for Japan |
postCode | ✳︎ | Postal code | |
addressOne | ✳︎ | Address1 | The API allows 255, but the carrier only takes 16 full-width |
addressTwo | Address2 | Same, 16 full-width | |
district | District / County | Leave empty for Japan | |
itemList[].sku | ✳︎ | SKU | |
itemList[].quantity | ✳︎ | Outgoing Quantity |
isSubmit decides where the order lands. With 1 it is submitted immediately, and from there the channel decides: Yamato goes straight to Awaiting, Sagawa stops in Drafts → To be submitted. See Step 6.The other endpoints you'll need
| Purpose | Endpoint |
|---|---|
| Paginated list of outbound orders | getParcelOutboundOrders |
| Order detail (read back status and tracking number) | getOutboundOrder |
| Batch cancel orders (asynchronous) | batchCancelOutboundOrder |
| Aggregated order status (confirm a cancellation) | selectBizStatus |
| Update tracking number or label (when using your own) | updateTrackNoAndLabel |
| Add a message or attachment to an order | replyBigOutboundMessage |
Three traps to avoid
Important HTTP 500 does not mean the order wasn't created. The official docs say a network error must not be treated as a failure — retrying blindly duplicates orders. Query the detail endpoint bythirdOrderNo first, and only retry once you've confirmed nothing was created.Note code: 200 only means the request succeeded, not that every order in it was created. Check data[].success and data[].msg per order.Note thirdOrderNo is globally unique — use it as your idempotency key. Re-sending the same external number won't create a duplicate, which is the safety net your retry logic needs.Freight outbound (ToB) uses different endpoints
Everything on this page is about parcel outbound. Freight Outbound (ToB) is a separate set: createBigOutboundOrder (by product) and createBoxStockOutboundOrder (by box), with their own query and cancel endpoints.
The official docs are authoritative
This page covers the main integration path for parcel outbound orders and the traps that catch people out. For the complete field list, error codes and the endpoints for other business flows, see https://apidoc-oms.xlwms.com/