Meirun WMS Open API

For a seller's own systems, ERP vendors and contract developers. Version v1, updated 2026-09-23. 中文

Overview

Base URLhttps://wms.meirun.com/api/v1/...
FormatJSON (UTF-8) both ways. POST requests send Content-Type: application/json
TimesISO 8601 in UTC, e.g. 2026-09-23T16:44:03.235Z
UnitsInches and pounds. Field names carry the unit: weightLb, lengthIn
Scope of dataA key sees and acts on exactly one seller's data -- the seller it was issued to. Naming another seller in a request changes nothing

Authentication and keys

The seller creates keys in the OMS: Settings → API Keys → New. A key is shown once, when it is created; a lost key is revoked and replaced.

One-click connect for an AI assistant. Claude, ChatGPT and other assistants that speak MCP add a connector with the address https://oms.meirun.com/mcp; the seller picks read-only or read-and-write on the OMS page that opens and presses Allow -- no key to copy. The connection is then listed under Settings → API Keys as "<assistant> (AI)", like any other key, and revoked there.

Send it with every request, either way:

X-Api-Key: wk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer wk_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxx
ScopeWhat it may do
ReadEvery GET endpoint
Read and writePlus every POST: create and cancel orders, create products and inbound orders, set the webhook
A system that only syncs status should get a read key. A read key calling a write endpoint gets 403 -- it never quietly succeeds.

Test mode

Add X-Test: 1 to any write. The request runs in full -- every check, every write -- and answers as it normally would, plus "test": true. Then all of it is rolled back and nothing is kept. Build and try an integration against the live system without a separate environment.

curl -X POST https://wms.meirun.com/api/v1/orders \
  -H "X-Api-Key: $KEY" -H "X-Test: 1" -H "Content-Type: application/json" \
  -d '{"ref":"TEST-1","lines":[{"sku":"LAMP-A","qty":1}]}'

Paging

List endpoints take page (from 1) and pageSize (up to 200), and answer with total, page and pageSize.

Errors

A non-2xx status, and {"error": "one sentence"}; some also carry a machine-readable reason.

StatusMeaning
400Something in the request is wrong; error says what
401No key, or a wrong or revoked one (one answer for all three)
403A read key on a write endpoint, or acting for another seller
404No such record (another seller's records answer 404 too)
409Not allowed in the current state, e.g. already shipped, label already bought
422Rows in a bulk import are wrong; nothing was imported, problems lists each row

GET/api/v1/whoami

Checks the key and changes nothing. The first call to write.

{ "warehouse": "meirun", "scope": "write",
  "seller": { "code": "1001", "name": "Hongyu Houseware" }, "key": "wk_15540e4e" }

GET/api/v1/warehouses

The warehouse's buildings. Use code on an inbound order.

{ "warehouses": [ { "code": "WAL", "name": "Walnut", "city": "Walnut", "state": "CA", "country": "US" } ] }

Products

GET/api/v1/products

Parameters: q (SKU or name), status (draft / pending / active / rejected / archived), paging.

POST/api/v1/products

Creates a product and submits it for the warehouse's review (status pending); "submit": false keeps a draft. Goods are received against a product once the warehouse approves it.

{
  "sku": "LAMP-A",               // required, 2-50 characters
  "name": "Brass Table Lamp",    // required
  "barcodes": ["0123456789012"],
  "declaredSpec": { "lengthIn": 12, "widthIn": 9, "heightIn": 4, "weightLb": 2.1 },
  "declare": { "en": "Table lamp", "cn": "...", "value": 15, "currency": "USD",
               "customsAttr": "general", "hsCode": "9405" }
}

GET/api/v1/stock

Stock by SKU. Parameters: sku (comma-separated for several; all when left out), paging.

{ "total": 1, "page": 1, "pageSize": 200, "stock": [ {
  "sku": "LAMP-A", "name": "Brass Table Lamp",
  "available": 38,      // on the shelves less what orders here have taken: the figure to push to a marketplace
  "onShelves": 40, "locked": 2, "inTransit": 100, "awaitingPutaway": 0, "damaged": 1,
  "warehouses": ["WAL"]
} ] }

POST/api/v1/orders

Creates an outbound order. Whole orders the shelves cover ship from stock; the rest go cross-border from the factory -- decided by the system.

{
  "ref": "3153-3369-0518-3280",   // your order number, unique; generated when left out
  "erpNo": "ERP-889012",          // optional, your system's own number
  "shipTo": { "name": "Jane Doe", "company": "", "line1": "12 Main St", "line2": "",
              "city": "Flagstaff", "state": "AZ", "postCode": "86004", "country": "US", "phone": "9285550100" },
  "lines": [ { "sku": "LAMP-A", "qty": 2 } ]
}

Answers 201 with the order as parcel.

POST/api/v1/orders/import

A batch at once, as CSV text. All or nothing: one bad row and nothing is imported, with every row's problem listed. "dryRun": true checks without importing.

Recognised columns (any case): order order/ref/order-id, sku, qty/quantity, recipient name, address address1, address2, city, state, zip, country, phone. Amazon's unshipped-orders report goes in as it is. Several rows with one order number are one order with several SKUs.

{ "text": "order,sku,qty,name,address1,city,state,zip,country\n1001,LAMP-A,2,Jane Doe,12 Main St,Flagstaff,AZ,86004,US", "dryRun": false }

GET/api/v1/orders

Parameters: q (order number), status, updatedSince (only orders changed after this time), paging. To stay in step by polling, send the newest updatedAt you have.

{ "total": 3, "page": 1, "pageSize": 200, "orders": [ {
  "ref": "3153-3369-0518-3280", "status": "shipped", "holdReason": null,
  "platform": "shopify", "platformOrderNo": "#3280", "erpNo": "ERP-889012",
  "shipTo": { ... }, "lines": [ { "sku": "LAMP-A", "qty": 2 } ],
  "weightLb": 4.2, "lengthIn": 12, "widthIn": 9, "heightIn": 4,
  "tracking": "794854001240", "carrier": "FEDEX",
  "shippedAt": "2026-09-22T21:10:00.000Z", "cancelledAt": null,
  "createdAt": "...", "updatedAt": "..."
} ] }

POST/api/v1/orders/cancel

Cancels an order -- only while nothing irreversible has happened to it. A bought label, a carton the factory has made, or a shipment is refused with the reason; ask the warehouse then.

{ "ref": "3153-3369-0518-3280" }
OutcomeAnswer
Cancelled200 {"ok": true, "already": false}
Already cancelled200 {"ok": true, "already": true}
Already shipped409 reason: "shipped"
Label bought409 reason: "labelled"
Carton made409 reason: "boxed"
No such order404 reason: "missing"

Inbound orders

GET/api/v1/inbound

Parameters: status, q, paging.

POST/api/v1/inbound

Tells the warehouse goods are coming. The products have to exist first (see Products).

{
  "warehouseCode": "WAL",          // may be left out when there is one building
  "tracking": "1Z999AA10123456784",
  "eta": "2026-10-05",
  "cartons": 12,
  "note": "",
  "lines": [ { "sku": "LAMP-A", "qty": 120 } ]
}

GET/api/v1/consignments

Cross-border cartons: measured at the factory, handed to the forwarder, at our door, gone out again. A several-carton order's cartons are REF-1, REF-2. Parameter q.

GET/api/v1/tracking

Where shipped orders are at the carrier. Parameters: q, stage, stalled=1 (stuck ones only).

Webhook: told when orders change

Instead of polling, give the key an https address. Once a minute, the orders that changed are POSTed there, up to 100 at a time.

POST/api/v1/webhook

{ "url": "https://erp.example.com/meirun/hook" }
→ { "ok": true, "url": "...", "secret": "whsec_..." }   // the secret is shown this once

{"url": ""} turns it off. GET/api/v1/webhook shows the address, the last delivery and the last failure.

What we send

POST https://erp.example.com/meirun/hook
Content-Type: application/json
X-WMS-Timestamp: 1758645843
X-WMS-Signature: sha256=5f1c...

{ "event": "orders.updated", "orders": [ { ...the same shape as GET /api/v1/orders... } ] }

Checking the signature

HMAC-SHA256 with the secret over timestamp + "." + raw body, compared with X-WMS-Signature. If it does not match, it did not come from us.

// Node.js
const crypto = require("crypto");
const expected = "sha256=" + crypto.createHmac("sha256", SECRET)
  .update(req.headers["x-wms-timestamp"] + "." + rawBody).digest("hex");
if (expected !== req.headers["x-wms-signature"]) return res.status(401).end();

# Python
import hmac, hashlib
expected = "sha256=" + hmac.new(SECRET.encode(), (ts + "." + raw_body).encode(), hashlib.sha256).hexdigest()
At least once, not exactly once. A delivery counts when you answer 2xx; on a timeout or an error the same orders come again the next minute. De-duplicate on ref + updatedAt.

Order statuses

statusMeaning
awaitingTrackingWaiting for a tracking number
toMoveStock to be moved
toPrintHas a tracking number, label to print
toPickTo pick
toPackTo pack
toShipTo ship
shippedShipped
cancelledCancelled
exceptionOn hold for a person. holdReason: platformCancelled (the marketplace cancelled it after the label was bought) / platformAddress (the marketplace changed the address after the label was bought)

Questions: raise a work order with the warehouse in the OMS, or contact your Meirun account manager.