Overview
| Base URL | https://wms.meirun.com/api/v1/... |
|---|---|
| Format | JSON (UTF-8) both ways. POST requests send Content-Type: application/json |
| Times | ISO 8601 in UTC, e.g. 2026-09-23T16:44:03.235Z |
| Units | Inches and pounds. Field names carry the unit: weightLb, lengthIn |
| Scope of data | A 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
| Scope | What it may do |
|---|---|
| Read | Every GET endpoint |
| Read and write | Plus every POST: create and cancel orders, create products and inbound orders, set the webhook |
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.
| Status | Meaning |
|---|---|
| 400 | Something in the request is wrong; error says what |
| 401 | No key, or a wrong or revoked one (one answer for all three) |
| 403 | A read key on a write endpoint, or acting for another seller |
| 404 | No such record (another seller's records answer 404 too) |
| 409 | Not allowed in the current state, e.g. already shipped, label already bought |
| 422 | Rows 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" }
| Outcome | Answer |
|---|---|
| Cancelled | 200 {"ok": true, "already": false} |
| Already cancelled | 200 {"ok": true, "already": true} |
| Already shipped | 409 reason: "shipped" |
| Label bought | 409 reason: "labelled" |
| Carton made | 409 reason: "boxed" |
| No such order | 404 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()
ref + updatedAt.
Order statuses
| status | Meaning |
|---|---|
awaitingTracking | Waiting for a tracking number |
toMove | Stock to be moved |
toPrint | Has a tracking number, label to print |
toPick | To pick |
toPack | To pack |
toShip | To ship |
shipped | Shipped |
cancelled | Cancelled |
exception | On 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.