DEVELOPERS / MERCHANT API V1
One contract from warehouse receipt to delivery.
WooliiPorter is a China fulfillment network, operated by WOOLII, LLC of Newark, Delaware, that gives merchants who buy from their own China suppliers one integration to mainstream China fulfillment capacity: it receives, preps, consolidates and dispatches their shipments, selects the best carrier channel for each parcel against a tracked delivery promise, and never buys goods or handles supplier payments. Merchant API V1 is that operation, callable: direct forwarding, optional merchant-owned inventory, warehouse fulfillment, unified shipments, packed quotes, line-item settlement and signed event delivery, behind one stable contract. Every operation in this reference is reconciled against the deployed V1 route surface.
Read the whole contract now, without an account. Create a merchant login, submit the integration application from inside the account, and approval opens the merchant console and API-key management. Start with a sandbox key, where an integration can create orders, run the lifecycle and settle them without moving real funds.
OpenAPI 3.1 JSONMCP serverApply for API access
- Version
- V1
- HTTP operations
- 45
- Webhook events
- 12
- MCP tools
- 22
- Contract audited
- 2026-08-03.1
Contract principles
Four properties, and what each one prevents
Merchant API V1 rests on four properties: idempotent writes with stored-response replay, HMAC-signed webhooks with immutable event IDs delivered at least once, estimates typed separately from packed quotes, and per-merchant keys carrying explicit scopes and a visible execution mode. Each one removes a specific way an automated integration ships the wrong thing.
| Property | Prevents |
|---|---|
| Idempotent writes with stored-response replay | A retry shipping the same goods twice. A repeated request returns the original response. |
| HMAC signature over the raw body, immutable event IDs, at-least-once delivery | A forged webhook, and a duplicate delivery being processed twice. Verify the signature over the raw body, deduplicate on the event ID, then process. |
| Estimates typed separately from packed quotes | An estimate being mistaken for the binding number. The packed quote is a distinct object produced after measurement. |
| Per-merchant keys with explicit scopes and a visible execution mode | Reading another merchant’s data from a resource ID, and a sandbox call reaching production. |
Autonomy boundary
Two decisions the merchant keeps
Two decisions belong to the human merchant and are enforced by the contract: approving the packed quote before dispatch, and choosing the route for a mixed order containing both stocked and unstocked lines. The API also has no path that buys from a supplier or moves a purchase payment, and no path by which one client issues credentials to another — the merchant’s money and the merchant’s identity stay with the merchant.
Payment-required handling
What a short wallet balance returns
If the merchant wallet balance is short, creating new warehouse work returns a payment-required response naming the shortfall, and the order is HELD pending top-up. Shipments already in flight are never held. Surface this to the merchant as an action rather than retrying it as a transient failure.
Overview
Base URL: https://api.woolii.com. All endpoints live under /v1/merchant, accept and return JSON, and are bounded to the authenticated merchant account — a resource id alone is never sufficient to read data. Every response carries an X-Request-Id header; include it when you contact us about a request. Requests are rate-limited per key; a 429 includes a Retry-After header. Webhook endpoints must be public https URLs — loopback, private and cloud-metadata addresses are rejected.
The API covers three connected workflows, and in all of them you (or your supplier) buy and own the goods. The forwarding workflow is pass-through: parcels arrive at the assigned Woolii China warehouse, and WooliiPorter operates receiving, optional inspection, value-added services (relabeling, repacking, FBA prep), consolidation, the packed quote, line-item settlement, dispatch and WooliiPorter tracking. The optional stocking workflow adds merchant-owned inventory on top: register SKUs, pre-alert inbound shipments, and dispatch fulfillment orders from stock — with live ledger-computed inventory, warehouse transfers and a monthly statement that reconciles line by line. The early-access smart-routing workflow lets an approved WooCommerce Store submit one order snapshot and receive a safe stocked, forwarding or manual-review decision. None replaces the others; storage is never required to ship. Prefer to let an agent drive it? See the MCP server.
WooliiPorter is the only carrier identity in the merchant contract: behind it, each parcel is routed across the aggregated carrier channels, and the grounds of that decision are recorded on the merchant-visible order. Booking identifiers, upstream costs and raw transport payloads remain private and are never exposed through REST, MCP or webhooks.
Authentication
Authenticate with a Bearer key. Keys belong to one account, carry a sandbox or production execution mode, and grant explicit scopes. A Registration creates a login, not a merchant account. Approval creates and activates that account; after approval, create either a wp_sk_test_… (sandbox) or wp_sk_live_…(production) key in the developer console. Start in sandbox and move to production only after the workflow is validated and the applicable account terms are accepted. Each key carries explicit scopes: orders:read, orders:write, tracking:read, webhooks:write, console:sso.
Keys can be rotated and revoked without losing the request audit trail. Store them like passwords.
Sandbox
Orders created with a wp_sk_test_… key are sandbox orders: they carry"sandbox": true, and they never enter real fulfilment — no warehouse receiving, no live tracking, no dispatch. They share your account's data space, so use a distinctexternalOrderId range (e.g. a test- prefix) to keep them recognizable.
Because there is no warehouse behind a sandbox order, advance its lifecycle yourself to exercise your webhook receiver end to end:
Simulation is rejected (409 merchant_not_sandbox) for production orders — those advance from real warehouse events only.
Idempotency
Operations explicitly marked requires Idempotency-Key accept any stable unique string up to 200 characters. Retrying one of those operations with the same key and body returns the original stored response; the same key with a different body returns 409 merchant_idempotency_conflict. The direct forwarding create/cancel operations and every fulfillment-intent mutation use this stored-response contract.
Other writes are not interchangeable: SKU and Store registration are natural upserts, fulfillment creation deduplicates by externalOrderId, while payment, sandbox simulation, inbound, transfer and webhook creation have endpoint-specific behavior. Unless an endpoint says it is replay-safe, reconcile with a GET before retrying after an ambiguous timeout.
Errors
All errors use one envelope:
Error codes
| Status | Code | Meaning |
|---|---|---|
| 400 | merchant_invalid_json / merchant_idempotency_required | The body is not valid JSON, or this operation requires a missing/invalid Idempotency-Key. |
| 402 | merchant_insufficient_balance | Available wallet balance cannot cover the pre-checked warehouse service amount. Top up before retrying. |
| 401 | merchant_auth_missing / merchant_auth_invalid | Missing or unknown API key. |
| 401 | merchant_key_revoked / merchant_key_expired | The key was revoked or has passed its expiry. |
| 403 | merchant_scope_missing | The key lacks the scope this endpoint requires. |
| 403 | merchant_account_inactive | The merchant account is not active. |
| 403 | merchant_smart_routing_disabled / merchant_store_not_registered | The private smart-routing rollout is disabled, or the Store is not registered in this key environment. |
| 404 | merchant_order_not_found | No such resource in your account. Cross-account ids also return 404. |
| 404 | merchant_intent_not_found | No such fulfillment intent exists in the authenticated merchant account. |
| 404 | merchant_shipment_not_found | No such shipment exists in the authenticated merchant account. Cross-account shipment numbers also return 404. |
| 409 | merchant_idempotency_conflict | The Idempotency-Key was already used with a different request body. |
| 409 | merchant_request_in_progress | A matching idempotent request still owns the processing lease. Retry after a short delay with the same key and body. |
| 409 | merchant_external_order_exists | externalOrderId already maps to an order (id included in details). |
| 409 | merchant_parcel_conflict | A tracking number already belongs to an active order. |
| 409 | merchant_order_conflict | The order is past the stage this action allows. |
| 409 | merchant_not_sandbox | Sandbox simulation was attempted against a production order. |
| 409 | merchant_intent_version_conflict / merchant_intent_state_conflict | The smart-routing projection changed or is no longer eligible for the requested action. Refresh before retrying. |
| 409 | merchant_intent_already_executed / merchant_supplier_parcels_locked | A child order or submitted parcel makes automatic re-routing unsafe. Human review is required. |
| 409 | merchant_intent_warehouse_missing | The routed fulfillment work has no warehouse assignment, so cancellation requires an authorized data-repair review. |
| 409 | merchant_not_payable / merchant_already_paid / merchant_nothing_due | Pay was called before the final packed quote, after payment, or with a zero balance. |
| 409 | merchant_fo_state | The fulfillment order is not in a state that allows this action (e.g. confirm on a dispatched order); the current state is named in the message. |
| 409 | merchant_no_receiving_workflow | No active stocking warehouse is currently available for inbound shipments. |
| 409 | merchant_endpoint_limit | The account already has the maximum five active webhook endpoints. |
| 422 | merchant_validation_failed | Field validation failed; details.issues lists each problem. |
| 422 | merchant_vas_incomplete | A value-added service request is missing instructions, illustrations or printable label files; details.issues lists every gap at once. |
| 422 | merchant_fba_details_required / merchant_fba_prep_required / merchant_fba_prep_quantity_invalid | AMAZON_FBA workflows need fba details plus explicit prep services covering every declared unit. |
| 422 | merchant_estimate_unavailable | No shipping route is configured for the requested destination. |
| 422 | merchant_sku_unregistered | An inbound shipment or fulfillment order references SKU codes that are not registered; details.missing lists every one. Register them via POST /skus first. |
| 422 | merchant_cursor_invalid | The inbound pagination cursor is malformed or uses an unsupported version. Restart from the first page instead of sending a database ID. |
| 422 | merchant_supplier_parcels_incomplete / merchant_supplier_parcel_overallocated | Supplier parcel line allocations do not exactly cover the forwarding intent or exceed a line quantity. |
| 422 | merchant_store_id_invalid / merchant_https_required | The connector Store identity is invalid or a production Store URL is not HTTPS. |
| 422 | merchant_warehouse_invalid | The requested transfer destination is not an active overseas warehouse. |
| 422 | merchant_webhook_url_rejected | The webhook URL is not https or resolves to a private/loopback/metadata address. |
| 404 | merchant_inbound_not_found | The inbound IB number does not exist in the authenticated merchant account. |
| 429 | merchant_rate_limited | Per-key request budget exceeded. Honor the Retry-After header. |
| 429 | merchant_sandbox_cap | Daily sandbox order limit reached for this account. |
| 500 | merchant_intent_cancellation_failed | The cancellation request could not be applied. Reconcile with GET fulfillment intent before retrying. |
Check the API connection
GET/v1/merchant/connection
Validates the key and returns the merchant account, environment, granted scopes and rollout flags. Connectors should call this before registering a store or enabling automatic submission. A successful response proves authentication; it does not by itself enable smart routing.
mcp: false means no hosted remote MCP endpoint is enabled. mcpStdioPreview: true identifies the optional standalone stdio adapter, which uses this same API key and scopes.
Register a WooCommerce store
PUT/v1/merchant/stores/{externalStoreId}
Creates or updates one WooCommerce installation in the API-key environment. The operation is naturally idempotent because the account, environment, platform and externalStoreId form the upsert key. The official plugin generates the Store identity once and keeps it stable across reconnects.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalStoreId | path string | Required | A stable wcstore_<UUID> generated by the plugin. |
platform | literal | Required | WOOCOMMERCE. |
storeUrl | URL | Required | Canonical storefront URL, up to 500 characters. |
pluginVersion | string | Required | Installed connector version, up to 40 characters. |
webhookUrl | URL | Required | The plugin receiver at /wp-json/wooliiporter/v1/webhook. |
Production storeUrl and webhookUrl values must use HTTPS. Explicit loopback URLs are accepted only by the isolated local release harness.
Read the store onboarding checklist
GET/v1/merchant/stores/{externalStoreId}/onboarding
Returns the same seven-step onboarding snapshot the merchant console shows for this WooCommerce Store — account, channel services, product sync, SKU links, inbound stocking, routing and first-order validation — plus the wallet funding status. The official plugin renders its Get-started wizard from this endpoint so plugin and console never disagree about progress.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalStoreId | path string | Required | A registered wcstore_<UUID> in the API-key environment. |
Warehouse work is gated on wallet funds (ON_HOLD_BALANCE). The wizard should route merchants to the console finance workspace through the console-sso handoff rather than collecting payment details inside WordPress.
Sync the WooCommerce catalog and link SKUs
POST/v1/merchant/stores/{externalStoreId}/catalog
Registers up to 250 WooCommerce products or variations per call and links them to warehouse SKUs. A row with an explicit skuCode is linked to that SKU; a row whose channel sku exactly matches one active warehouse SKU is linked automatically; every other row is returned as unmatched for a manual decision. The sync never creates warehouse SKUs and never guesses a link. A successful call records the catalog sync evidence used by onboarding step 3.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalStoreId | path string | Required | A registered wcstore_<UUID> in the API-key environment. |
products | array | Required | 1-250 rows of {externalProductId, externalVariationId, name, sku?, skuCode?}. |
An invalid or inactive explicit skuCode rejects the whole batch with merchant_sku_code_invalid so a typo never half-links a catalog.
Open the Woolii merchant console
POST/v1/merchant/console-sso
Creates a 60-second, single-use merchant-console handoff for one registered WooCommerce Store. The official plugin calls this endpoint from WordPress after a manage_woocommerce nonce check; the API key never enters the browser or handoff URL. The returned URL must be opened immediately and must never be logged, cached, emailed or persisted.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalStoreId | string | Required | A registered wcstore_<UUID> in the API-key environment. |
returnPath | enum | Optional | One of the bounded merchant-console paths. Unknown values fall back to /merchant. |
This is a connector handoff, not a general authentication API. It requires an active member linked to the Store account and rejects expiry or replay.
Create a smart fulfillment intent
POST/v1/merchant/fulfillment-intents
Submits a versioned commerce-order snapshot for backend-owned routing. Each line is assessed against its SKU mapping, product preference and available inventory. A fully stocked order creates and reserves a fulfillment order; a fully forwarding order waits for real supplier tracking; a mixed order stops for an explicit merchant decision. This early-access endpoint requires both a registered Store and the smartRouting account flag.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
platform | literal | Required | WOOCOMMERCE. |
externalStoreId | string | Required | A Store registered in the current API environment. |
externalOrderId | string | Required | Stable WooCommerce database order ID and intent idempotency identity, up to 120 characters. |
externalOrderName | string | Optional | Merchant-facing value from WC_Order::get_order_number(), including sequential-order-number extensions, up to 120 characters. |
externalOrderVersion | string | Required | Stable content hash/version for this exact order snapshot. |
destination | object | Required | recipientName, phone, country, province, city, street and postalCode. |
lines | array | Required | 1–100 unique externalLineId entries with productName, qty and optional productId, variationId, skuCode and fulfillmentPreference. |
fulfillmentPreference | enum | Optional per line | AUTO (default) | FORWARDING. FORWARDING never consumes stocked inventory. |
serviceType | enum | Optional | EXPRESS | AIR_CARGO | BULK_SHIPPING. Default AIR_CARGO. |
checkoutShippingMethod | string | Optional | The buyer’s chosen checkout shipping method (e.g. "Express"), up to 120 characters. Mapped to a delivery-time band by your delivery rules; persisted on the intent so re-evaluation keeps the same band. Omit to fall back to the merchant default preference. |
currency / orderTotal | string / number | Optional | Three-letter order currency and non-negative order total for diagnostics. |
Possible statuses include MAPPING_REQUIRED, WAITING_SUPPLIER_TRACKING, MIXED_REVIEW_REQUIRED, ROUTED_FULFILLMENT, ROUTED_FORWARDING, ON_HOLD_BALANCE, STALE, ROUTING_BLOCKED and CANCELLED. Follow requiredActions and the optional cancellation projection rather than inferring the next action from status text.
A changed externalOrderVersion may replace only an intent with no child order and no submitted supplier parcel. Otherwise the intent becomes STALE for human review.
The official WooCommerce plugin always sends externalOrderName for display and search. externalOrderId remains the stable identity used for idempotent intent upserts.
Retrieve a fulfillment intent
GET/v1/merchant/fulfillment-intents/{id}
Returns the backend-owned routing projection: per-line decision facts, current routingVersion, requiredActions, editable supplier parcels, any child fulfillment or forwarding order, and the cancellation lifecycle when cancellation has been requested. Refresh before every merchant decision so stale browser state cannot overwrite a newer route.
cancellation is omitted until a cancellation has been requested. Its status is the durable lifecycle projection: WAREHOUSE_STOPPED and WAREHOUSE_RETURNED require operational follow-up, ADMIN_REVIEW requires an authorized recall or after-sales decision, and CANCELLED is terminal.
Cancel a fulfillment intent
POST/v1/merchant/fulfillment-intents/{id}/cancel
Requests cancellation against the unified commerce fulfillment intent, so the API evaluates its stocked-fulfillment or supplier-forwarding child and the real physical execution stage. A 200 response means every linked operation was safely cancelled. A 202 response means the request was accepted but warehouse return or authorized admin review must finish before the intent is terminal.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
routingVersion | integer | Required | The latest routingVersion returned by the fulfillment-intent projection. |
reason | string | Required | Non-empty merchant-facing reason, up to 500 characters. |
200 outcome CANCELLED: the intent and its safe linked work are terminally cancelled; cancellation.status is CANCELLED and resolvedAt is populated.
202 outcome WAREHOUSE_REVIEW_REQUIRED: warehouse work was stopped and the cancellation projection advances through WAREHOUSE_STOPPED and WAREHOUSE_RETURNED before an authorized operator closes settlement.
202 outcome ADMIN_REVIEW_REQUIRED: transport or another protected stage requires an authorized recall or after-sales decision; cancellation.status is ADMIN_REVIEW.
The response repeats cancellation at the top level for immediate disposition handling and inside fulfillmentIntent as the canonical status projection. A terminal 200 response replays unchanged; a request first accepted with 202 keeps the same request identity but later retries project the current lifecycle, including eventual 200 CANCELLED after authorized closure.
Cancellation and retry projections preserve externalOrderName so operators can continue reconciling the WooCommerce order while externalOrderId remains unchanged.
Save supplier parcel drafts
PUT/v1/merchant/fulfillment-intents/{id}/supplier-parcels
Replaces the complete editable supplier-parcel draft for an intent that is waiting for forwarding. Each real China domestic tracking number carries one or more externalLineId quantity allocations. Draft allocations may be partial, but cannot over-allocate a line or reuse a tracking number.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
routingVersion | integer | Required | The latest value returned by GET fulfillment intent. |
parcels | array | Required | 0–50 supplier parcels. Each has trackingNumber and 1–100 line allocations; carrier, supplierName and notes are optional. |
lines | array | Required per parcel | externalLineId plus positive integer qty. |
A routingVersion mismatch returns 409 merchant_intent_version_conflict. Submitted or received parcels are locked and cannot be replaced.
Submit supplier forwarding
POST/v1/merchant/fulfillment-intents/{id}/submit-forwarding
Locks the supplier parcels and atomically creates the forwarding child order. Submission succeeds only when the saved parcel allocations exactly cover every forwarding line. The plugin deliberately separates draft saving from this irreversible warehouse handoff.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
routingVersion | integer | Required | The latest routingVersion returned by the intent. |
Incomplete exact coverage returns 422 merchant_supplier_parcels_incomplete with expected and allocated quantities per line.
Resolve a mixed-order route
POST/v1/merchant/fulfillment-intents/{id}/select-route
Resolves an unexecuted mixed order. FORWARD_ALL moves every line to supplier forwarding. WAIT_FOR_STOCK records the merchant decision but leaves the intent in review until stock changes and the merchant explicitly re-evaluates. Automatic split fulfillment is intentionally disabled in this release.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
routingVersion | integer | Required | The latest routingVersion returned by the intent. |
selection | enum | Required | FORWARD_ALL | WAIT_FOR_STOCK. |
Re-evaluate inventory routing
POST/v1/merchant/fulfillment-intents/{id}/re-evaluate
Re-runs an unexecuted mixed, mapping-required or routing-blocked intent against current mappings, route configuration and inventory. It never re-routes an intent that already has a child order or submitted supplier parcel.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
routingVersion | integer | Required | The latest routingVersion returned by the intent. |
List delivery rules
GET/v1/merchant/routing-rules
Your ordered delivery rules and the fallback preference used when none match. Rules decide which delivery-time band each order ships on. They live on WooliiPorter, not in your storefront, so the same rules apply to every sales channel you connect — a plugin should read them from here rather than keeping its own copy.
Body parameters
| Parameter | Type | Required | Description |
|---|
Replace delivery rules
PUT/v1/merchant/routing-rules
Replaces the whole rule set in one call, so saving the same payload twice gives the same result. Array order is priority order: rules are matched top to bottom and the first match wins. Each rule must set exactly one outcome — a rule that sets none would match and change nothing, and a rule that sets several either contradicts itself or has one silently ignored. Orders already placed keep the delivery promise frozen at the time they were created; changing rules never rewrites history.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
rules | array | Required | Up to 50 rules in priority order. Send an empty array to clear them. |
rules[].name | string | Required | Shown on every order this rule matches, so name it for the reason, not the mechanism. |
rules[].destinationCountryCodes | array | Optional | Two-letter codes. Empty means any destination. |
rules[].skuRoutingTags | array | Optional | Matches when any item in the order carries any of these tags. Set tags on your SKUs. |
rules[].checkoutShippingMethods | array | Optional | Storefront shipping method ids (e.g. flat_rate:2). An exact instance is preferred; the method type is the fallback. |
rules[].minDeclaredValue | number | Optional | Inclusive lower bound. An order with no declared value does not match rather than counting as zero. |
rules[].minTotalWeightKg | number | Optional | Inclusive lower bound in kilograms. |
rules[].maxTransitDays | integer | Conditional | Ceiling in days. If no published band can meet it for a destination, the order is rejected with the bands that are available — never quietly shipped slower. |
rules[].preference | enum | Conditional | PRICE | SPEED. Picks within whatever bands remain. |
rules[].transitBandId | string | Conditional | Pin a specific published band. Only meaningful when the rule is limited to one destination country, because bands are defined per country. |
defaultPreference | enum | Optional | PRICE | SPEED applied when no rule matches. |
Estimate cost and transit
POST/v1/merchant/orders/estimate
Models cost and configured transit time before order creation. It first selects the currently published WooliiPorter physical route, then applies the immutable supplier rate version, the merchant fine-weight settlement policy, and a current FX snapshot. The final packed quote reruns the same versioned engine with actual measurements and freezes its own route, rate and FX evidence, so a planning estimate is not a charge promise.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
destination | object | Required | Destination country code and postalCode. |
packages | array | Required | Declared parcels: weight (kg) plus length, width and height (cm). 1–20 entries. |
transitBandId | string | Optional | Quote only this delivery-time band. Omit to get one quote per purchasable band for the destination — compare "5-7 days" against "8-12 days" and pick. Which carrier runs the band is internal and not selectable. |
packageTypeId | string | Optional | 1 (general, default) | 2 (battery/cosmetic) | 3 (sensitive). These are separately routed and priced cargo types. |
services | array | Optional | Value-added service requests, validated with the same rules as order creation. |
A missing or ambiguous WooliiPorter service, route, rate version, settlement policy, postal zone or FX snapshot returns 422 merchant_estimate_unavailable. No legacy tariff fallback is used. Value-added service requests are validated here with the same rules as order creation — incomplete label-class jobs return 422 merchant_vas_incomplete before any pricing runs.
Create an order pre-alert
POST/v1/merchant/orders
Registers a forwarding pre-alert: the destination, the domestic supplier parcels you expect to arrive, and how fast it should ship. Omit transitBandId and bandPreference and the delivery-time band is decided by the routing rules you configured — that is the normal case, and the response tells you which rule matched and how many days were promised. Returns the Woolii order, its receiving instructions and the recorded parcels. externalOrderId is unique per account.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalOrderId | string | Required | Your own order id, unique per account. |
destination | object | Required | recipientName, phone, country, province, city, street, postalCode. |
parcels | array | Required | Domestic supplier parcels: trackingNumber, plus optional productName, quantity, declaredUnitPrice, description. cargoType: General | Electronics | Liquid | Branded. 1–50 entries. |
shipmentType | enum | Optional | SINGLE | CONSOLIDATED. Default CONSOLIDATED. |
transitBandId | string | Optional | A delivery-time band returned by /orders/estimate for this destination country. Use this when you have already quoted and want that exact band. |
bandPreference | enum | Optional | CHEAPEST | FASTEST — overrides your routing rules for this one order. Ranking uses a nominal 1 kg parcel because real weights only exist after warehouse packing; the final charge is always recomputed from actual measurements. |
checkoutShippingMethod | string | Optional | What the buyer chose at your storefront checkout (e.g. flat_rate:2). This is a fact, not a choice: what it means in days is decided by your routing rules, so the same shipping method behaves identically whether the order arrives from WooCommerce, Shopify or the API. |
maxTransitDays | integer | Optional | Ceiling in days for this one order. If no published band can meet it for the destination, the order is rejected and lists the bands that are actually available — never quietly shipped slower than the buyer paid for. |
inspection | boolean | Optional | Request an inbound inspection. Default false. |
services | array | Optional | Value-added work for the warehouse to execute — see Value-added services below. |
destinationType | enum | Optional | CUSTOMER (default) | AMAZON_FBA — see Amazon FBA prep below. |
fba | object | Required for FBA | { region: US | EU | CA | MX, fulfillmentCenter, shipmentId? }. |
notes | string | Optional | Free-form note, up to 1000 characters. |
Requires an Idempotency-Key header. Replaying the same key with the same body returns the stored response; a different body returns 409 merchant_idempotency_conflict.
A tracking number that already belongs to another active order returns 409 merchant_parcel_conflict.
Value-added services
services[] declares value-added work: RELABEL | REPACK | POLYBAG | BUNDLE | COMPLIANCE_STICKER | QC | PHOTO | PALLETIZE | REINFORCE | HANGTAG. The warehouse only executes unambiguous jobs: label-class services (RELABEL, COMPLIANCE_STICKER, HANGTAG) must carry instructionText, at least one illustrationImages entry and a printable labelFiles entry (PDF/PNG) — we print exactly what you upload, never a guess. BUNDLE and REPACK require instructionText. Anything missing returns 422 merchant_vas_incomplete with every gap listed in details.issues.
Amazon FBA prep
destinationType: CUSTOMER (default) | AMAZON_FBA. FBA first-leg orders must include fba { region: US | EU | CA | MX, fulfillmentCenter, shipmentId? }, put the fulfillment-center address in destination, and explicitly declare the RELABEL + POLYBAG + PALLETIZE prep services (RELABEL with your printable FNSKU label files). Missing prep returns 422 merchant_fba_prep_required — prep is never added silently on your behalf.
List orders
GET/v1/merchant/orders
Cursor-paginated list of your orders, newest first. Filter by externalOrderId, state, or updatedSince (ISO 8601).
Pass nextCursor back as ?cursor= to fetch the next page. limit is capped at 100.
Retrieve an order
GET/v1/merchant/orders/{id}
Returns one order with its parcels, receiving instructions and the live progress of every value-added service you requested. Order ids from another account return 404 — resource existence is never disclosed across accounts.
Each services[] entry tracks one job: stage (receive | sort | pack) pins it to a warehouse step, state moves requested → done, and the fee line reappears verbatim in the settlement.
Cancel an order
POST/v1/merchant/orders/{id}/cancel
Cancels an order while it is still awaiting parcels (state awaiting_parcels). After receiving has begun the API returns 409 merchant_order_conflict with a human escalation path — the same rule the customer dashboard enforces.
Simulate a sandbox lifecycle event
POST/v1/merchant/orders/{id}/simulate
Advances a sandbox forwarding order and emits the matching signed webhook without touching real warehouse, payment or carrier systems. Use the events in order when testing an end-to-end receiver. Production orders are always rejected.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
event | enum | Required | parcel.received | quote.ready | shipment.dispatched | shipment.delivered. |
Calling this endpoint for a production order returns 409 merchant_not_sandbox. The simulator does not require Idempotency-Key; do not retry it blindly after an ambiguous network failure.
List shippable countries
GET/v1/merchant/countries
Country-level candidates backed by a current published carrier buy-rate version and an executable published WooliiPorter route. This endpoint no longer reads legacy ShippingRate rows. Postal zone, cargo type, weight and dimension eligibility remain authoritative in the estimate endpoint.
Retrieve the quote state
GET/v1/merchant/orders/{id}/quote
Distinguishes the pending packed quote from the final packed quote. New final amounts come from each parcel’s route-matched versioned ShipmentSellQuote; legacy package snapshots are historical read-only fallback only. The public response exposes WooliiPorter shipment/parcel references and customer prices, never the selected internal carrier.
stage: pending_packed_quote | final_packed_quote | unavailable.
Retrieve the settlement
GET/v1/merchant/orders/{id}/settlement
The line-item bill. Every charge is one explainable line — category, label, amount — and the total is exactly the sum of the lines, the same amount pay charges. Before packing finalizes the quote no lines are presented: an estimate never masquerades as a bill.
stage: pending_final_quote | payable | paid | cancelled.
category: SHIPPING | PACKAGING | INSURANCE | INSPECTION | VAS | DDP | OTHER. Each completed value-added service bills as its own VAS line.
Once paid, the response also carries payment { provider, status, paidAt }.
Pay for a shipment
POST/v1/merchant/orders/{id}/pay
Pay-per-shipment settlement of exactly the settlement lines — the charged amount always equals GET settlement’s total. Sandbox orders settle instantly with a sandbox payment and emit payment.completed. Production orders return a Stripe clientSecret to confirm with Stripe.js; the payment webhook then advances the order on the same rail as dashboard payments.
Sandbox response: { "payment": { "provider": "sandbox", "status": "completed", "amount": 92.60, "currency": "USD" } } — no real funds move.
Before the final packed quote the call returns 409 merchant_not_payable; after payment, 409 merchant_already_paid; a zero balance returns 409 merchant_nothing_due.
Retrieve the timeline
GET/v1/merchant/orders/{id}/timeline
One time-ordered event stream for the whole journey: the China private leg (receiving, inspection, value-added services, packing, payment) merged with the international carrier leg. "Where is my order?" has a single answer from a single endpoint.
segment: cn (private China leg) | intl (carrier network). CN event types: order.created, parcel.received, inspection.completed, vas.completed, package.packed, payment.completed; carrier events are type carrier.event.
A carrier lookup failure never blocks the timeline — the China leg is always complete.
Retrieve tracking
GET/v1/merchant/orders/{id}/tracking
Order-keyed tracking projection for every outbound package on the order. It returns the stable WooliiPorter WP number and the same delivery timeline available on woolii.com/track.
List unified shipments
GET/v1/merchant/shipments
Lists the immutable outbound-shipment ledger across forwarding and stocked fulfillment. Use cursor pagination for continuous synchronization. The carrier is always WooliiPorter and each row uses the same stable public reference shown to the customer.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
cursor | query string | Optional | Opaque cursor from nextCursor. |
limit | query integer | Optional | 1–100; default 20. |
updatedSince | query ISO 8601 | Optional | Only shipments updated at or after this timestamp. A timezone offset is required. |
externalOrderId | query string | Optional | Exact merchant/store order identity. |
trackingNumber | query string | Optional | Exact WooliiPorter public tracking number. |
sourceType | query enum | Optional | ORDER | FULFILLMENT_ORDER. |
status | query enum | Optional | PACKED | ROUTED | BOOKING_PENDING | BOOKING_UNKNOWN | BOOKED | HANDOVER_PENDING | HANDED_OVER | IN_TRANSIT | DELIVERED | CANCELLED | EXCEPTION. |
trackingStatus | query enum | Optional | UNKNOWN | INFO_RECEIVED | HANDED_OVER | IN_TRANSIT | OUT_FOR_DELIVERY | DELIVERED | EXCEPTION | RETURNED. |
Shipment creation is warehouse-controlled. Merchants create orders or fulfillment orders; after packing, WooliiPorter materializes and routes the shipment. This endpoint is read-only and never accepts a carrier choice.
The public service is a WooliiPorter transport product such as international small parcel or express. It is distinct from the warehouse fulfillment workflow selected for the order.
Collection rows are intentionally compact: parcel lines, measurements and event history are available from the shipment detail resource. This keeps high-volume incremental synchronization bounded.
BOOKING_UNKNOWN is a reconciliable technical state, not proof of failure. Continue reading the shipment or wait for webhook updates; do not submit a duplicate order.
Retrieve a unified shipment
GET/v1/merchant/shipments/{shipmentNumber}
Returns one outbound shipment by its WooliiPorter shipment number, including public parcel measurements, merchant-owned line snapshots, the latest merchant sell quote and the WooliiPorter delivery timeline.
Retrieve shipment-native tracking
GET/v1/merchant/shipments/{shipmentNumber}/tracking
Returns the compact tracking projection for every parcel in one unified shipment. New integrations should prefer this shipment-native endpoint; the order-keyed endpoint is only an alternate projection of the same Shipment ledger.
Customer lookup is available without a merchant key at GET /public/tracking/{WP-number}; it is IP rate-limited and returns only carrier=WooliiPorter, status, public location and timestamps.
The authenticated tracking-publication feed is a separate partner contract and cannot be read with merchant keys.
Register a SKU
POST/v1/merchant/skus
Registers or updates a SKU in your catalog — an upsert keyed on skuCode, so replaying the same registration is safe without an Idempotency-Key. SKUs are the master data for stocking: inbound shipments and fulfillment orders reference skuCode and are rejected with 422 merchant_sku_unregistered until the SKU exists — stock is never put away against a guessed product. Re-registering reactivates an inactive SKU.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
skuCode | string | Required | Your SKU code, unique per account — the upsert key. Up to 80 characters. |
name | string | Required | Product name, up to 200 characters. |
imageUrl | string | Optional | Product image URL — shown to warehouse operators at putaway and picking. |
barcode | string | Optional | Scannable barcode, up to 64 characters. |
unitWeight | number | Optional | Weight per unit in kg. |
unitLength / unitWidth / unitHeight | number | Optional | Unit dimensions in cm. They also derive the per-unit volume used for the storage lines on your monthly statement. |
declaredValue | number | Optional | Declared customs value per unit. |
customsNameEn | string | Optional | English customs name. |
hsCode | string | Optional | HS code, up to 20 characters. |
List SKUs
GET/v1/merchant/skus
Your active SKUs, sorted by skuCode. Up to 500 entries are returned.
Create an inbound shipment
POST/v1/merchant/inbound-shipments
Creates a WooliiPorter inbound workflow for stored fulfillment, Amazon FBA preparation or marketplace FBM. WooliiPorter assigns a public IB inbound number and one China receiving address. Put the IB number on every carton; supplier carrier tracking is not required for these stocking workflows. Direct forwarding remains a separate order flow and still uses the real supplier tracking number.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalRef | string | Optional | Your own reference for this inbound, up to 120 characters. |
businessType | enum | Optional | STORED_FULFILLMENT (default) | AMAZON_FBA | MARKETPLACE_FBM. |
fba | object | Required for AMAZON_FBA | region, fulfillmentCenter and optional shipmentId. |
marketplace | object | Required for MARKETPLACE_FBM | platform and optional storeRef. |
cartonCount | integer | Required | Expected physical carton count, 1–1000. The warehouse records actual cartons, weight and volume at receipt. |
lines | array | Required | Declared contents: skuCode plus expectedQty. 1–200 entries. Every skuCode must already be registered. |
services | array | Optional | 0–40 value-added service requests. Label-class work requires complete instructions, illustrations and printable label files. FBA requires explicit prep coverage for every declared unit. |
notes | string | Optional | Free-form note, up to 1000 characters. |
Unregistered SKU codes return 422 merchant_sku_unregistered with every missing code in details.missing — register them via POST /skus first.
AMAZON_FBA requires fba details plus explicit RELABEL, POLYBAG and PALLETIZE prep. Prep quantities must cover every declared unit. MARKETPLACE_FBM requires marketplace details.
WooliiPorter assigns the receiving warehouse from the enabled business route. If no active route exists, the API returns a 409 routing error instead of guessing a receiving address.
The returned IB number is the customer/PDA receiving identity. Do not invent a tracking number or send a supplier carrier number in this endpoint.
exceptions contains only WooliiPorter-branded status, type and customer-action flags. Internal warehouse providers, evidence paths, liability, supplier costs and payable records are never exposed.
state: expected → receiving → processing → putaway → stocked (or cancelled). Stock becomes available only at putaway — each putaway writes an immutable ledger entry, so receivedQty and inventory always reconcile.
List inbound shipments
GET/v1/merchant/inbound-shipments
Your inbound shipments, newest first, with IB numbers, declared/received cartons, per-line received quantities and customer-safe exception status. Cursor pagination returns up to 100 entries per page.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
cursor | query string | Optional | Opaque versioned cursor returned as nextCursor. Database IDs are never accepted as cursors. |
limit | query integer | Optional | 1–100; default 100. |
Retrieve an inbound shipment
GET/v1/merchant/inbound-shipments/{inboundNumber}
Retrieves one tenant-scoped inbound by public IB number. Database IDs are internal and never become customer-facing references. The response uses the same projection as create/list and includes current customer-safe exception status.
A missing or cross-tenant IB number returns the same 404 merchant_inbound_not_found response.
Provider identities, quarantine bins, internal notes, evidence URLs, responsibility findings, customer receivable drafts and provider payables are excluded.
Retrieve inventory
GET/v1/merchant/inventory
Live stock levels per SKU per warehouse — onHand, reserved, available — plus stock aging (the receipt date of the oldest lot still holding stock). Every number is computed on read by summing the immutable stock-movement ledger, the same source the storage lines on your statement are computed from: inventory and billing can never disagree. Filter one SKU with ?sku=.
Create a fulfillment order
POST/v1/merchant/fulfillment-orders
Creates a dropship fulfillment order shipped from your stocked inventory — one destination address, one or more SKU lines. externalOrderId is the idempotency key: replaying an id that already exists returns the stored order with replayed: true instead of creating a duplicate. By default the order is confirmed automatically on creation; set draftOnly to hold it in draft for an explicit confirm call.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
externalOrderId | string | Required | Your own order id, unique per account — the idempotency key (e.g. wc-1042). |
destination | object | Required | recipientName, phone, country, province, city, street, postalCode. |
lines | array | Required | skuCode plus qty per line. 1–100 entries. Every skuCode must already be registered. |
serviceType | enum | Optional | EXPRESS | AIR_CARGO | BULK_SHIPPING. Default AIR_CARGO. |
checkoutShippingMethod | string | Optional | The buyer’s chosen checkout shipping method (e.g. "Express"), up to 120 characters. Mapped to a delivery-time band by your delivery rules; omit to fall back to the merchant default preference. |
draftOnly | boolean | Optional | Default false: the order auto-confirms and reserves stock immediately. true stops at draft until you call confirm. |
notes | string | Optional | Free-form note, up to 1000 characters. |
Reservation is atomic for the whole order and consumes lots oldest-first (FIFO). If any line lacks available stock, nothing is reserved and the whole order goes to on_hold — holdReason names the SKU, the requested quantity and what was actually available. Restock, then retry with confirm.
Unregistered SKU codes return 422 merchant_sku_unregistered with the codes in details.missing.
state: draft | reserved | on_hold | picking | packed | dispatched | in_transit | delivered | cancelled. After dispatch, fees[] carries the shipping and pick-and-pack lines — the same lines that appear on your monthly statement.
List fulfillment orders
GET/v1/merchant/fulfillment-orders
Your fulfillment orders, most recently updated first, with lines and fee items. Pass ?updatedSince= (ISO 8601) for incremental polling. Up to 100 entries are returned.
Confirm a fulfillment order
POST/v1/merchant/fulfillment-orders/{id}/confirm
Confirms a draft order, or retries reservation on an on_hold order after restocking — the same atomic, FIFO, all-or-nothing reservation as auto-confirm. With default auto-confirm you only need this call for draftOnly orders and for releasing holds.
Only draft and on_hold orders can be confirmed; any other state returns 409 merchant_fo_state with the current state in the message.
Cancel a fulfillment order
POST/v1/merchant/fulfillment-orders/{id}/cancel
Cancels a fulfillment order any time before dispatch and releases every unpicked reservation back to available stock, lot by lot, in the same ledger the inventory endpoint reads.
Orders already dispatched, in transit or delivered (and orders already cancelled) return 409 merchant_fo_state.
Retrieve the monthly statement
GET/v1/merchant/statements
The monthly stocking bill, computed live from the same ledger as inventory — never a stored snapshot, so the statement and your stock can never disagree. Storage is billed per lot: on-hand units × the lot’s unit volume (CBM) × the per-CBM-per-day price × billable days, where billing starts only after a free period from receipt. Fulfillment fees incurred in the month are listed line by line alongside. ?month=YYYY-MM; defaults to the current month.
freeDays and pricePerCbmDay are account-level billing parameters echoed in every response — read them from your statement rather than assuming fixed values; this page intentionally promises none.
Every line is explainable: a storage line names the SKU, the on-hand quantity and the billed days; a fulfillment line is exactly the fee recorded on the order it came from.
Create a warehouse transfer
POST/v1/merchant/transfer-orders
Requests a China-to-overseas-warehouse stock transfer. The source is the active China warehouse; the destination must be an active international warehouse supplied by operations. Dispatch and receiving remain warehouse-controlled actions, and the resulting quantities appear as in transit until receiving completes.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
toWarehouseId | string | Required | Active overseas warehouse ID supplied by WooliiPorter operations. V1 does not yet expose warehouse discovery. |
lines | array | Required | 1–200 registered SKU lines with positive integer qty. |
notes | string | Optional | Transfer instructions, up to 1000 characters. |
This POST is not protected by Idempotency-Key in V1. Do not retry it automatically after an ambiguous network failure; list transfers and reconcile before creating another request.
List warehouse transfers
GET/v1/merchant/transfer-orders
Returns up to 100 transfer orders plus an inTransit summary by SKU. Use the individual lines for audit and the aggregate to avoid double-counting stock that has left China but has not yet been received overseas.
Create a webhook endpoint
POST/v1/merchant/webhook-endpoints
Registers an HTTPS endpoint for the event types you select. The signing secret is returned exactly once — store it immediately. Up to 5 active endpoints per account.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | Public https URL that receives deliveries. |
eventTypes | array | Required | Event types to deliver — see Webhook events below. |
List webhook endpoints
GET/v1/merchant/webhook-endpoints
Lists your endpoints with status and failure counters. Signing secrets are never returned again.
Disable a webhook endpoint
DELETE/v1/merchant/webhook-endpoints/{id}
Disables the endpoint. The delivery ledger is evidence and is retained; nothing is deleted.
Webhook events
Deliveries are at-least-once with bounded backoff (1 minute to 24 hours, 8 attempts); deduplicate by eventId. Sustained failures disable the endpoint, and the delivery ledger is always retained.
Event types
| Event | Meaning |
|---|---|
parcel.received | A domestic supplier parcel was scanned and recorded at the warehouse. |
inspection.completed | An inspection you requested finished. |
quote.ready | Packing and measurement produced the final packed quote; the order moved to payment. |
payment.completed | The shipment settlement was paid; the order moved on to dispatch. |
shipment.dispatched | Carrier events confirm the shipment entered the provider network. |
tracking.updated | A normalized WooliiPorter parcel tracking fact changed. The payload contains public shipment and parcel identifiers only. |
shipment.exception | An exception was recorded against the shipment. |
shipment.delivered | Every outbound package on the order has a delivered carrier fact. |
inbound.exception.opened | WooliiPorter opened an inbound receiving review. The payload contains the IB number and customer-safe exception projection only. |
inbound.exception.updated | The public state, customer-action requirement or resolution of an inbound receiving review changed. |
cancellation.updated | The interception state of a cancellation request changed — warehouse stop, stock return, review, final cancellation or rejection. The payload mirrors the cancellation object returned by GET fulfillment intent; once dispatched, orders can no longer be cancelled and the request resolves through review instead. |
account.balance_low | The merchant wallet is below the operating threshold. This account-level event has no order identity. |
Verifying signatures
Every delivery is signed with your endpoint's secret: an HMAC-SHA256 over `${timestamp}.${rawBody}`. Headers: X-Woolii-Event-Id, X-Woolii-Timestamp (unix seconds), X-Woolii-Signature (v1=<hex>). Verify against the raw body before parsing.
MCP server
Let your AI agent operate forwarding directly. The WooliiPorter MCP (Model Context Protocol) server exposes the Merchant API above as agent-callable tools, so Claude, Cursor or your own copilot can pre-alert orders, estimate costs, watch the timeline, settle shipments and run the optional stocking workflow — SKUs, inbound shipments, inventory, fulfillment orders and the monthly statement — without custom glue code. It is a single zero-dependency file — Node >= 18, no install step — and it uses your existing API key with the same scopes, sandbox behavior and fool-proof value-added-service rules as raw HTTP.
MCP tools
| Tool | What it does |
|---|---|
create_forwarding_order | Pre-alert an order: parcels, destination, value-added service requests. Idempotent per externalOrderId. |
estimate_shipping_cost | Route-aware planning estimate using the same versioned engine as the final packed quote; current route/rate/FX can differ before packing. |
get_order | Read one order, including value-added service progress. |
list_orders_updated_since | Incremental polling: orders changed since a timestamp. |
get_packed_quote | Final packed quote and transit ETA. |
get_settlement | The line-item bill — every charge explainable. |
pay_order | Pay per shipment; sandbox settles instantly, production returns a Stripe client secret. |
get_tracking | Normalized carrier tracking for every outbound package. |
get_timeline | Unified China-leg + carrier-leg event timeline. |
list_shipments | Cursor-paginated unified shipments, filterable by source, status, tracking and update time. |
get_shipment | One shipment with public service, parcels, merchant sell quote and normalized events. |
get_shipment_tracking | Shipment-native WooliiPorter tracking for every outbound parcel. |
cancel_order | Cancel while the order is still awaiting parcels. |
cancel_fulfillment_intent | Cancel one unified commerce fulfillment intent; the outcome distinguishes terminal cancellation from warehouse or admin review. |
register_sku | Register or update a merchant SKU (upsert by skuCode) — required before stocking or fulfillment. |
create_inbound_shipment | Create a stocking/FBA/FBM inbound and receive an IB inbound number and address; no supplier tracking is required. |
get_inbound_shipment | Read one inbound by public IB number, including carton progress and customer-safe exception state. |
get_inventory | Live stock levels per SKU per warehouse plus aging, from the immutable movement ledger. |
create_fulfillment_order | Create a dropship order from stocked inventory — auto-confirms and reserves FIFO; ON_HOLD names any shortfall. |
confirm_fulfillment | Confirm a draft order, or retry reservation on an on-hold order after restocking. |
get_statement | Monthly statement: per-lot storage fees plus fulfillment fees, every line explainable. |
list_shippable_countries | Destinations with a live configured route. |
Start with a sandbox key: your agent can create orders, simulate the lifecycle and settle them without moving real funds. The same constraints apply as over HTTP — label-class services still require instructions, illustrations and printable label files, and pay only succeeds once the final packed quote exists.
The agent-facing page for this server — what it calls, where autonomy stops and how to get a key — is the MCP server page. Key issuance and scoping, webhook signature verification and staff access are described at Security.
Running WooCommerce? WooliiPorter Fulfillment for WooCommerce connects the Store to the fulfillment-intent endpoints directly. Version 0.7.0 does not expose WordPress Abilities or an MCP endpoint inside the Store; use this standalone MCP server only when you deliberately connect an external agent with a merchant API key.
Running Shopify? The fulfillment-service app is listed on the Shopify App Store. Install it there to handle installation, product-to-SKU linking and assigned fulfillment orders inside Shopify itself — it needs no merchant API key and exposes no public API surface. Orders from every channel land in the same fulfillment intents, orders and webhooks documented in this reference.
WooliiPorter Fulfillment for WooCommerce
Plugin version 0.7.0 connects one WooCommerce installation to Woolii-operated fulfillment in China. Test and connect validates the account, API-key environment, required scopes and smartRouting flag, then registers the Store immediately. When an eligible order reaches processing, the plugin sends a versioned snapshot through Action Scheduler—never in the checkout request—and stores the fulfillment intent, per-line route facts, required action and diagnostic request ID on the WooCommerce order.
Three explicit routing outcomes
Mapped lines with enough inventory reserve a warehouse fulfillment order. Forwarding lines wait for the merchant to enter real China domestic tracking numbers and allocate each product quantity; a forwarding child order is created only after those allocations exactly cover the intent. Mixed orders remain paused for Forward all, Wait for stock or Re-evaluate inventory. Version 0.7.0 never splits a mixed order automatically. A product or variation may be marked Always forward so it never consumes stock.
Replay, stale-order and webhook safety
The order snapshot hash is part of a stable Idempotency-Key. Duplicate hooks and retries replay the existing intent; a queued task exits safely when the order changed before execution. Lifecycle writes also carry routingVersion so a stale browser cannot overwrite a newer backend decision. Webhooks are accepted only when the HMAC timestamp is fresh and the Store and intent identities match. Event IDs are claimed atomically and retained for 90 days. A smart intent completes the WooCommerce order only after the backend says every child fulfillment is delivered.
Settings
WooCommerce → WooliiPorter
| Setting | What it controls |
|---|---|
| API key & environment | Use wp_sk_test_… for sandbox and wp_sk_live_… for production. Test and connect verifies the real key environment and refuses a mismatch. |
| API base | Production is pinned to https://api.woolii.com/v1/merchant. Developer mode accepts only exact local hosts or code-allowlisted HTTPS origins, preventing credential exfiltration. |
| Smart routing | Off by default. Enables fulfillment intents only after the platform account exposes smartRouting and this Store is registered. |
| Automatic submission | Off by default. Can be enabled only after connection, environment, scope, Store, webhook-secret and Action Scheduler checks all pass. |
| Delivery speed preference | Lowest cost | Fastest delivery. WooliiPorter quotes every delivery-time band published for the destination country and picks one by this preference; the chosen band is recorded on the order. |
| Webhook signing secret | Required in every environment. The receiver verifies HMAC, Store identity, intent identity (when known), timestamp and eventId deduplication before writing order state. |
Install
- Active merchants can download the reviewed ZIP from the Integrations workspace. Upload it through Plugins → Add New → Upload Plugin, or upload the
wooliiporter-fulfillmentdirectory towp-content/plugins/. Requires WordPress 6.4+, PHP 7.4+ and WooCommerce 8.2+. - Activate WooliiPorter Fulfillment for WooCommerce on the Plugins screen.
- Open WooCommerce → WooliiPorter, paste a
wp_sk_test_…key, configure the webhook signing secret, then run Test and connect. - Map stocked products/variations to WooliiPorter SKU codes; mark supplier-direct products Always forward. Exercise stocked, forwarding and mixed outcomes in sandbox.
- Enable smart routing, then automatic submission. The latter remains disabled until the connection, Store, webhook secret and Action Scheduler release gates all pass.
Version 0.7.0 is listed in the WordPress.org plugin directory, reviewed by the WordPress plugin team, and updates ship through the standard WordPress update channel. The WordPress.org plugin directory is distinct from the WooCommerce Marketplace, and the plugin is not represented as Marketplace-approved.
The connector does not expose WordPress Abilities or a Store-hosted MCP endpoint in version 0.7.0. It sends only the operational Store, order, recipient, product-routing and supplier-parcel data described in the plugin privacy disclosure; it does not import the entire WooCommerce catalog.
Get access
API access is tied to your WooliiPorter account: register (or sign in), open the developer console, and apply with your store URL and expected monthly volume. A direct API application under review does not block you: you can sign in, read every contract and guide, and price lanes on the public calculator — though API-key creation stays closed until the account is active. Connecting a verified store activates the account without waiting. Approval creates and activates the merchant account; after that, the account owner can manage up to three active keys per environment. We notify the account owner when review is complete.
Apply for API accessOpen the developer console
The developer console manages sandbox and production keys, webhook endpoints and one-time secrets. Store every revealed secret immediately; it is not shown again.
Questions
Is there a China 3PL with an API?
Yes. WooliiPorter publishes Merchant API V1 — 45 HTTP operations covering order creation, estimates, packed quotes, value-added services, tracking, settlement and optional inventory — with an OpenAPI 3.1 contract available publicly at /api/developers/openapi. Writes are idempotent with stored-response replay, and 12 webhook event types are HMAC-signed.
Does WooliiPorter publish an OpenAPI specification?
Yes. The OpenAPI 3.1 contract for Merchant API V1 is published at /api/developers/openapi and is readable without an account, before any application is submitted. The contract version is stated on the developers page and is reconciled against the deployed route surface rather than maintained by hand.
Why does a fulfillment API need idempotency?
Because a retried request must not create a second physical shipment. A network timeout is indistinguishable from a failure, so a client that retries an order creation without an idempotency key can dispatch the same goods twice. WooliiPorter stores the response against the key and replays it, returning the original order.
How do I verify a WooliiPorter webhook signature?
Verify the HMAC signature over the raw request body before parsing it, then deduplicate on the immutable event ID, then process. WooliiPorter delivers 12 signed event types at least once, so a receiver will see duplicates and must be idempotent. Parsing before verifying, and deduplicating on payload content, are the two common failures.
How do I get a WooliiPorter API key?
Create a merchant login, submit the integration application from inside the account, and approval activates the merchant account and opens API-key management. Start with a sandbox key, where an integration can create orders, run the whole lifecycle and settle them without moving real funds, then switch to production.
What can I evaluate before applying?
Everything that decides an integration: the OpenAPI 3.1 contract, all 45 operations with their parameters, the error table, the HMAC signature scheme, the idempotency contract, the 12 webhook event types and the 22 MCP tools. All of it is public, so the technical decision can be made before any conversation.
MCP server · Security · WooCommerce plugin · Webhooks guide · MCP for logistics