Wiring Line-Level Order Routing Into Your OMS/ERP: The Integration Data Contract for a China Fulfillment Partner
Short answer first: line-level order routing is not a toggle in a dashboard — it is a data contract. The moment one order can contain both stock lines (received, evidence-captured and consolidated at our China warehouse) and supplier-direct lines, your OMS or ERP has to treat each order line as an independently routed object with its own state, its own tracking and its own exception path. This article covers the implementation layer: which objects and fields to define, how to design event semantics so you are not undone by duplicate dispatches and out-of-order states, and what can honestly be integrated today.
If the business decision itself is still open, read stock fulfillment vs supplier-direct shipping and line-level routing for mixed orders first. If the direction is settled but you are unsure what data to clean up, see the master-data types to prepare before integration. For the field list behind automation exchanges, see fulfillment data in the MCP/API era. This article assumes the decision is made and you are holding a schema diagram.
Why per-line has to be a property of the data model, not a column in the UI
One order, one example: six units of a SKU already stocked at our China warehouse, two units of another SKU that can only ship from your supplier. Same order number. Two fulfillment models. Two tracking numbers. Two different owners of the exception path. If your order table carries a single shipping-status field, you cannot express “line 1 has left the China hub while line 2 is still waiting on supplier pickup.” You end up encoding that into free-text notes, and every downstream consumer — support, reconciliation, your own dashboard — has to guess.
The consequences are concrete: support cannot answer “where is my order” without a human, partial refunds get attributed to the wrong lines, and reconciliation between your system and your fulfillment partner drifts quietly until peak season exposes it.
So routing belongs on the line: each line carries a routing_mode (stock fulfillment or supplier-direct), set when the order enters your system and changeable only through an auditable change event.
The integration data contract: objects to define before you write code
These are the objects we walk through with integration partners. Field names are yours; the semantics are not optional.
| Object | Key fields | Owner | Why it matters |
|---|---|---|---|
| SKU ↔ supplier ↔ routing map | sku, supplier_id, routing_mode, consolidation eligibility, battery/hazmat flags, source of dims and weight | Merchant (we validate) | Turns routing into a lookup instead of a judgment call at packing time |
| Order line | order_id, line_id (stable), sku, qty ordered, qty dispatched, routing_mode, split sequence, status | Merchant OMS | A stable line_id is what survives edits, splits and cancellations |
| Parcel / shipment | shipment_id, linked line_ids, mode, measured weight, dims, sealed_at, dispatched_at | Fulfillment partner | Tracking attaches here, not to the order |
| Evidence record | evidence_id, parcel_id, type (photo / count / weight), captured_at, operator reference | Fulfillment partner | Makes the evidence chain readable by your system, not only by a human |
| Tracking record | shipment_id, carrier, tracking_number, tracking_url, first scan time, status | Carrier via partner | Written back per parcel, aggregated per line at read time |
| Exception event | exception_id, linked line_id or parcel_id, type, detected_at, required owner | Shared | Short-ship, damage, unreachable address, customs hold, supplier delay all get an owner |
Two of these deserve emphasis. First, the SKU-to-supplier mapping is merchant-maintained: you know which supplier makes which SKU and which SKUs you want stocked. Our side validates that mapping against parcels we actually receive and flags conflicts — one SKU mapped to two suppliers, missing SKU, quantity mismatch. Second, declare where dims and weight come from: your data or our measurement at receiving. Mixing the two makes reconciliation impossible.
State machines and tracking write-back: do not force both paths into one status set
| Fulfillment model | State sequence | Terminal states |
|---|---|---|
| Stock fulfillment | received → evidence captured → pending consolidation → consolidated → dispatched → in transit → delivered | delivered, cancelled, returned |
| Supplier-direct | instruction sent → supplier confirmed → picked up → in transit → delivered | delivered, cancelled, failed |
Every transition should carry a timestamp, the triggering party (warehouse scan, merchant API, carrier) and the source system. Do not let your system infer state from a tracking number alone: tracking tells you what transport did, not whether a parcel was received, evidence-captured or still waiting to be consolidated.
The tracking rule is simple: one order can produce N parcels and N tracking numbers. Store tracking at parcel level, aggregate to line level when you read. Never overwrite an order-level field with it.
On weight: consolidation can change the volumetric weight of a shipment and therefore can change how it is billed, but that is not a guarantee of lower shipping cost — it depends on what is in the parcel and on the carrier’s billing rules. The mechanics are covered in volumetric weight vs chargeable weight.
Exception events should at minimum cover: short-ship at receiving (received quantity below expected), damage at receiving, unreachable address, customs or documentation hold, and supplier failure to hand over on time. Each one needs a field for who acts next, or it just becomes another email thread.
Events, idempotency, and the tests that find the bugs
- Event envelope: every event carries event_id, event_type, occurred_at (UTC), entity references, state_version and payload. Consumers deduplicate on event_id and apply by state_version, so a late-arriving event cannot roll state backwards.
- Commands need idempotency keys: creating a receiving instruction, editing a line, cancelling a line, changing an address, requesting dispatch. A retried command must not create a second parcel or a second dispatch.
- Partial quantities must be expressible: track dispatched quantity per line; the line stays open until dispatched quantity equals the ordered quantity or the remainder is explicitly cancelled. Cancelling after dispatch is not a cancellation — it is a return.
- The evidence chain should be machine-readable: photos, counts and weight readings become structured records with timestamps and parcel references, so your system can render what was verified, when, and with what result.
Test these before go-live; they are exactly where integrations break:
- A mixed order whose line quantity is edited after receiving;
- Cancelling one of two lines;
- Splitting one line into two parcels;
- A short-ship at receiving;
- An address change after receiving but before dispatch;
- The same event delivered twice;
- Events arriving out of order (dispatch before evidence capture);
- Delivered, but tracking not yet written back;
- A supplier-direct line with no carrier scan for a long period.
Also write down what “accepted” means: which events, which fields, which exception types, which test cases passing. Settling that during a small pilot is far cheaper than settling it before peak season.
Where integration actually stands today
So that automation promises do not run ahead of capability, here is the honest scope. Integration interfaces are currently handled as design-partner / private-pilot work. We do not promise publicly available API keys, and we do not promise production webhooks or a marketplace app for general use. Channel status is stated only as verified: our WooCommerce plugin currently has a listing in the WordPress.org directory — a directory listing, which is not the same as a listing in the WooCommerce Marketplace. For Shopify and Amazon FBA we state only the progress we have verified, and we will not describe a channel as generally available before a first real order has been delivered through it.
The practical path is: submit a workflow review at /request-workflow-review → we review order intake, routing rules, exception handling and data ownership together → then field mapping → then a scoped pilot exchange → then acceptance against agreed test cases. That sequencing is deliberate. Routing projects usually fail on the data contract, not on the interface itself.
FAQ
Can we integrate without an app in any marketplace?
Yes. Pilot integrations are defined per partner: agreed field mapping, agreed event set, agreed test cases. None of that requires a published app.
Who maintains the SKU mapping?
You do. You know which supplier makes which SKU and which SKUs you want stocked. We validate the mapping against parcels we actually receive and flag conflicts. Goods remain your property throughout; we do not purchase from your suppliers and we never handle payment to them.
What data actually moves in an MCP/API integration?
Inbound and receiving status, evidence events (photo, count, weight), consolidation events, outbound dispatch and tracking write-back, and exception events with a required-owner field. Not payment information, and not your supplier purchase orders.
How long does acceptance take?
We do not publish fixed timelines, because scope drives it. The sequence is review → mapping → test cases → acceptance, and the definition of done is agreed in writing before work starts.
Can one order run on both models?
Yes — that is the case this contract exists for. Per-line quantity, state and tracking is the premise of the design.
What happens if a line cannot ship as planned?
It becomes an exception event with an owner and a next action, rather than a status that changes quietly.
If you want to map this against your own schema, start at /developers and submit a workflow review at /request-workflow-review. If you are still evaluating fulfillment models, /for-merchants and /how-it-works cover the operational side, and /integrations lists channel status exactly as verified.