仓信AI / Cangxin AI Open API v1 · Seller / ERP API
For your own system or ERP (Lingxing, Dianxiaomi, Mabang ...): create orders and inbound notices, query stock and tracking, and receive webhooks. Machine-readable spec: /api/v1/openapi.json (OpenAPI 3.1, import it into Postman / Apifox).
Base URL: https://cangxinai.com/api/v1
0. ERP integration (Lingxing) at a glance / ERP 对接要点(领星)
A test account is delivered as exactly three values: test domain, appKey and appSecret. Nothing else is needed to call the API.
测试账号只交付三项:测试域名、appKey、appSecret。调用接口不需要其他参数。
| Lingxing field / 领星字段 | Our value / 我方对应 | Header / 请求头 |
|---|---|---|
| API domain / API 域名 | Base URL of the environment, ending in /api/v1 (production, or the separate test environment for a test account) 接口地址,以 /api/v1 结尾(正式环境;测试账号则为独立测试环境的地址) |
- |
| appKey | The seller code (GET /me returns it as key.appkey). Identifies the seller, not a secret 卖家代码(GET /me 返回的 key.appkey),用于识别卖家,不是密钥 |
X-WMS-Key |
| appSecret | The API key (shown once when created; stored hashed). Sent as a Bearer token, or not sent at all when requests are signed with HMAC-SHA256 (optional) API 密钥(生成时只显示一次,服务器只存哈希)。作为 Bearer 令牌发送;或可选用 HMAC-SHA256 请求签名,此时密钥本身不发送 |
Authorization: Bearer <appSecret> | X-WMS-Signature |
curl https://cangxinai.com/api/v1/me \
-H "X-WMS-Key: <appKey>" \
-H "Authorization: Bearer <appSecret>"
Request signing is optional: see Signed requests. / 请求签名为可选项,见下文"请求签名"。
Lingxing functions and endpoints / 领星对接功能与接口对应
| # | Lingxing function / 领星功能 | Endpoints / 接口 | Webhooks / 推送 |
|---|---|---|---|
| 1 | 1. 基础对接(商品、仓库、库存、订单) Base data: SKUs, warehouses, inventory, orders |
GET /api/v1/skusPOST /api/v1/skusPUT /api/v1/skus/{id}GET /api/v1/warehousesGET /api/v1/inventoryGET /api/v1/ordersGET /api/v1/orders/{id} |
- |
| 2 | 2. FBA 备货入库(入库预报) FBA stock inbound: inbound notice (ASN) and received quantities |
POST /api/v1/inboundGET /api/v1/inboundGET /api/v1/inbound/{id}Related / 相关 (FBA replenishment, warehouse → Amazon / 仓库发往亚马逊 FBA):
POST /api/v1/transfers GET /api/v1/transfers/{id}
|
inbound.received |
| 3 | 3. 出库发货(推单、跟踪号回传、截单) Outbound: push orders, tracking number back, cancel / intercept |
POST /api/v1/ordersGET /api/v1/orders/{id}GET /api/v1/orders/by-external/{external_id}POST /api/v1/orders/{id}/cancelPOST /api/v1/orders/{id}/hold |
order.label_createdorder.shipped |
| 4 | 4. 退件入库 Returns: announce a return, inspection result, instruction |
POST /api/v1/returnsGET /api/v1/returnsGET /api/v1/returns/{id}POST /api/v1/returns/{id}/instruction |
return.awaiting_instruction |
Tracking numbers: GET /orders/{id} returns carrier and tracking_numbers as soon as the warehouse buys the label. / 跟踪号:仓库买面单后,GET /orders/{id} 即返回 carrier 和 tracking_numbers。
1. Authentication
The seller creates an API key in the seller portal (API page, login required; only the seller account owner can create keys) and gives it to the system that calls the API. Requests run with that seller's permissions: only the seller's own data in its contracted warehouses.
curl https://cangxinai.com/api/v1/warehouses \
-H "Authorization: Bearer <API_KEY>"
appkey / appSecret: for ERPs that ask for two values, appkey is the seller code (key.appkey in the GET /me answer; it identifies the seller and is not secret) and appSecret is the API key. With Bearer authentication the appkey may also be sent in the X-WMS-Key header; it must then match the key's seller, otherwise the answer is 401 appkey_mismatch.
Signed requests (optional)
Instead of sending the key, sign every request. Headers: X-WMS-Key (appkey, or the key prefix), X-WMS-Timestamp (Unix seconds; accepted within 300 seconds of our clock), X-WMS-Nonce (8-64 characters [A-Za-z0-9_-], never reused for the same key) and X-WMS-Signature. Errors: 401 invalid_signature, signature_expired, nonce_reused.
import hashlib, hmac, secrets, time
def sign(app_secret: str, method: str, path: str, query: str, body: bytes) -> dict:
# path and query exactly as sent, e.g. "/api/v1/orders" and "status=pending&page=1"
timestamp, nonce = str(int(time.time())), secrets.token_hex(16)
message = "\n".join([method.upper(), path, query, timestamp, nonce,
hashlib.sha256(body).hexdigest()])
key = hashlib.sha256(app_secret.encode()).hexdigest() # signing key = sha256_hex(appSecret)
signature = hmac.new(key.encode(), message.encode(), hashlib.sha256).hexdigest()
return {"X-WMS-Key": "<appkey>", "X-WMS-Timestamp": timestamp,
"X-WMS-Nonce": nonce, "X-WMS-Signature": "sha256=" + signature}
Per key, the seller can also require signed requests (Bearer then answers 401 signature_required) and set an IP allow-list (other addresses get 403 ip_not_allowed).
| Scope | Grants |
|---|---|
orders | Outbound orders and FBA / transfer orders |
inbound | Inbound notices (ASN) and SKU master data |
stock | Inventory and SKU read access |
returns | Return announcements and instructions |
vas | Value-added service requests |
billing:read | Prepaid balance, transactions and charges (read only) |
2. Conventions
- JSON in and out, UTF-8. Dates are ISO 8601 in UTC (
2026-11-10T08:00:00Z). - Lists:
?page=1&page_size=50(max 200) →{"data": [...], "pagination": {...}}. - Errors:
{"error": {"code": "...", "message": "...", "details": {...}}}with HTTP 400 / 401 / 403 / 404 / 409 / 422 / 429. Records of other sellers answer 404. - On POST, send an
Idempotency-Keyheader with a unique value (for example your order number): a retry with the same key and body within 7 days returns the first response (headerIdempotent-Replayed: true) and creates nothing twice. - Rate limit per key (default 120 requests / minute): see
X-RateLimit-*headers; HTTP 429 comes withRetry-After. - Add
Accept-Language: zh-CNto get error messages in Chinese. - Warehouses:
GET /warehousesreturns each contracted warehouse with itspublic_code(e.g.WS-ACME-LAX1): unique on the whole platform and never changed. Use it aswarehouse_code(or sendwarehouse_id). The shortcodeis unique per warehouse company only: when two contracted warehouses share it, the API answers 422ambiguous_warehouse_code.
3. Endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/me |
- | Current API key, user and seller |
| GET | /api/v1/warehouses |
- | Contracted warehouses |
| GET | /api/v1/skus |
stock, inbound, orders | List SKUs |
| GET | /api/v1/skus/{id} |
stock, inbound, orders | Get a SKU |
| POST | /api/v1/skus |
inbound | Create a SKU |
| PUT | /api/v1/skus/{id} |
inbound | Update a SKU (only the fields sent) |
| POST | /api/v1/orders |
orders | Create an outbound order |
| GET | /api/v1/orders |
orders | List outbound orders |
| GET | /api/v1/orders/{id} |
orders | Order status and tracking |
| POST | /api/v1/orders/{id}/cancel |
orders | Cancel an order (before picking starts) |
| POST | /api/v1/orders/{id}/hold |
orders | Ask the warehouse to hold / intercept an order |
| GET | /api/v1/orders/by-external/{external_id} |
orders | Order status and tracking, by your order number |
| POST | /api/v1/orders/by-external/{external_id}/cancel |
orders | Cancel an order by your order number (before picking starts) |
| POST | /api/v1/orders/by-external/{external_id}/hold |
orders | Hold / intercept an order by your order number |
| POST | /api/v1/inbound |
inbound | Create an inbound notice (ASN) |
| GET | /api/v1/inbound |
inbound | List inbound notices |
| GET | /api/v1/inbound/{id} |
inbound | Inbound notice status and received quantities |
| GET | /api/v1/inventory |
stock | Stock by warehouse / SKU / batch |
| POST | /api/v1/returns |
returns | Announce a return |
| GET | /api/v1/returns |
returns | List returns |
| GET | /api/v1/returns/{id} |
returns | Get a return |
| POST | /api/v1/returns/{id}/instruction |
returns | Give the instruction for an inspected return |
| GET | /api/v1/vas/services |
vas | Services that can be requested |
| POST | /api/v1/vas |
vas | Request a value-added service |
| GET | /api/v1/vas |
vas | List value-added service requests |
| GET | /api/v1/vas/{id} |
vas | Get a value-added service request |
| POST | /api/v1/transfers |
orders | Create an FBA / transfer order (box labels as base64 PDF) |
| GET | /api/v1/transfers |
orders | List FBA / transfer orders |
| GET | /api/v1/transfers/{id} |
orders | Get an FBA / transfer order |
| GET | /api/v1/wallets |
billing:read | Prepaid balance per warehouse company |
| GET | /api/v1/wallets/{id}/transactions |
billing:read | Prepaid balance transactions |
| GET | /api/v1/charges |
billing:read | Charges (billable events) |
Create an order
The order goes through the same pipeline as uploaded order files: SKUs are matched by SKU code, barcode or your SKU mappings; the same external_id twice answers 409.
curl -X POST https://cangxinai.com/api/v1/orders \
-H "Authorization: Bearer <API_KEY>" -H "Content-Type: application/json" \
-H "Idempotency-Key: 111-1234567-1234567" \
-d '{"warehouse_code": "WS-ACME-LAX1", "external_id": "111-1234567-1234567", "channel": "amazon",
"ship_by_date": "2026-11-12T07:59:59Z",
"recipient": {"name": "Jane Smith", "street": "1 Main St", "city": "Los Angeles",
"state": "CA", "zip": "90001", "country": "US", "phone": "555-0100"},
"lines": [{"sku": "MUG-01", "quantity": 2, "external_item_id": "10000000000001"}]}'
Then poll GET /orders/{id} or GET /orders/by-external/{external_id} (status pending → processing → shipped, with tracking_numbers), or subscribe to the order.label_created / order.shipped webhooks. Cancel or hold by your own order number with POST /orders/by-external/{external_id}/cancel and /hold (URL-encode it; order numbers containing "/" must use the numeric id).
4. Webhooks
Register HTTPS URLs on the seller portal API page. We POST a JSON event; answer any 2xx within 10 seconds. Failed deliveries are retried after 1, 5, 15, 60 minutes, 3, 6, 12 and 24 hours (8 attempts), and are listed with their result on the API page. Events may arrive more than once: ignore an id you have already processed.
| Event | When |
|---|---|
order.label_created | A shipping label was bought: first tracking number, before the order leaves the warehouse |
order.shipped | An order or transfer left the warehouse (tracking numbers included) |
order.held | An order was put on hold |
inbound.received | Goods of an inbound notice were received |
return.awaiting_instruction | An inspected return waits for your instruction |
wallet.low_balance | A prepaid balance fell below its low-balance threshold |
order.label_created is sent once per order and tracking number, as soon as the warehouse buys the label (before the parcel leaves); order.shipped follows when it leaves.
POST https://erp.example.com/wms/webhook
X-WMS-Event: order.shipped
X-WMS-Timestamp: 1762761600
X-WMS-Signature: sha256=5d41402abc4b2a76b9719d911017c592...
{"id": "evt_...", "type": "order.shipped", "created_at": "2026-11-10T08:00:00Z",
"seller": {"id": 7, "code": "S001"},
"data": {"order": {"id": 42, "external_id": "111-1234567-1234567", "status": "shipped",
"carrier": "UPS", "tracking_numbers": ["1Z999AA10123456784"], ...}}}
Verify the signature (HMAC-SHA256 of timestamp + "." + raw body with your endpoint secret) and reject old timestamps:
import hashlib, hmac, time
def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
timestamp = headers["X-WMS-Timestamp"]
if abs(time.time() - int(timestamp)) > 300:
return False
expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body,
hashlib.sha256).hexdigest()
return hmac.compare_digest("sha256=" + expected, headers["X-WMS-Signature"])