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.

PropertyPrevents
Idempotent writes with stored-response replayA 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 deliveryA 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 quotesAn 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 modeReading 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.

The same boundary, over MCP

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

StatusCodeMeaning
400merchant_invalid_json / merchant_idempotency_requiredThe body is not valid JSON, or this operation requires a missing/invalid Idempotency-Key.
402merchant_insufficient_balanceAvailable wallet balance cannot cover the pre-checked warehouse service amount. Top up before retrying.
401merchant_auth_missing / merchant_auth_invalidMissing or unknown API key.
401merchant_key_revoked / merchant_key_expiredThe key was revoked or has passed its expiry.
403merchant_scope_missingThe key lacks the scope this endpoint requires.
403merchant_account_inactiveThe merchant account is not active.
403merchant_smart_routing_disabled / merchant_store_not_registeredThe private smart-routing rollout is disabled, or the Store is not registered in this key environment.
404merchant_order_not_foundNo such resource in your account. Cross-account ids also return 404.
404merchant_intent_not_foundNo such fulfillment intent exists in the authenticated merchant account.
404merchant_shipment_not_foundNo such shipment exists in the authenticated merchant account. Cross-account shipment numbers also return 404.
409merchant_idempotency_conflictThe Idempotency-Key was already used with a different request body.
409merchant_request_in_progressA matching idempotent request still owns the processing lease. Retry after a short delay with the same key and body.
409merchant_external_order_existsexternalOrderId already maps to an order (id included in details).
409merchant_parcel_conflictA tracking number already belongs to an active order.
409merchant_order_conflictThe order is past the stage this action allows.
409merchant_not_sandboxSandbox simulation was attempted against a production order.
409merchant_intent_version_conflict / merchant_intent_state_conflictThe smart-routing projection changed or is no longer eligible for the requested action. Refresh before retrying.
409merchant_intent_already_executed / merchant_supplier_parcels_lockedA child order or submitted parcel makes automatic re-routing unsafe. Human review is required.
409merchant_intent_warehouse_missingThe routed fulfillment work has no warehouse assignment, so cancellation requires an authorized data-repair review.
409merchant_not_payable / merchant_already_paid / merchant_nothing_duePay was called before the final packed quote, after payment, or with a zero balance.
409merchant_fo_stateThe 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.
409merchant_no_receiving_workflowNo active stocking warehouse is currently available for inbound shipments.
409merchant_endpoint_limitThe account already has the maximum five active webhook endpoints.
422merchant_validation_failedField validation failed; details.issues lists each problem.
422merchant_vas_incompleteA value-added service request is missing instructions, illustrations or printable label files; details.issues lists every gap at once.
422merchant_fba_details_required / merchant_fba_prep_required / merchant_fba_prep_quantity_invalidAMAZON_FBA workflows need fba details plus explicit prep services covering every declared unit.
422merchant_estimate_unavailableNo shipping route is configured for the requested destination.
422merchant_sku_unregisteredAn inbound shipment or fulfillment order references SKU codes that are not registered; details.missing lists every one. Register them via POST /skus first.
422merchant_cursor_invalidThe inbound pagination cursor is malformed or uses an unsupported version. Restart from the first page instead of sending a database ID.
422merchant_supplier_parcels_incomplete / merchant_supplier_parcel_overallocatedSupplier parcel line allocations do not exactly cover the forwarding intent or exceed a line quantity.
422merchant_store_id_invalid / merchant_https_requiredThe connector Store identity is invalid or a production Store URL is not HTTPS.
422merchant_warehouse_invalidThe requested transfer destination is not an active overseas warehouse.
422merchant_webhook_url_rejectedThe webhook URL is not https or resolves to a private/loopback/metadata address.
404merchant_inbound_not_foundThe inbound IB number does not exist in the authenticated merchant account.
429merchant_rate_limitedPer-key request budget exceeded. Honor the Retry-After header.
429merchant_sandbox_capDaily sandbox order limit reached for this account.
500merchant_intent_cancellation_failedThe cancellation request could not be applied. Reconcile with GET fulfillment intent before retrying.

Check the API connection

GET/v1/merchant/connection

Scope orders:read

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}

Scope orders:write

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

ParameterTypeRequiredDescription
externalStoreIdpath stringRequiredA stable wcstore_<UUID> generated by the plugin.
platformliteralRequiredWOOCOMMERCE.
storeUrlURLRequiredCanonical storefront URL, up to 500 characters.
pluginVersionstringRequiredInstalled connector version, up to 40 characters.
webhookUrlURLRequiredThe 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

Scope orders:read

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

ParameterTypeRequiredDescription
externalStoreIdpath stringRequiredA 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

Scope orders:write

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

ParameterTypeRequiredDescription
externalStoreIdpath stringRequiredA registered wcstore_<UUID> in the API-key environment.
productsarrayRequired1-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

Scope 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

ParameterTypeRequiredDescription
externalStoreIdstringRequiredA registered wcstore_<UUID> in the API-key environment.
returnPathenumOptionalOne 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

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
platformliteralRequiredWOOCOMMERCE.
externalStoreIdstringRequiredA Store registered in the current API environment.
externalOrderIdstringRequiredStable WooCommerce database order ID and intent idempotency identity, up to 120 characters.
externalOrderNamestringOptionalMerchant-facing value from WC_Order::get_order_number(), including sequential-order-number extensions, up to 120 characters.
externalOrderVersionstringRequiredStable content hash/version for this exact order snapshot.
destinationobjectRequiredrecipientName, phone, country, province, city, street and postalCode.
linesarrayRequired1–100 unique externalLineId entries with productName, qty and optional productId, variationId, skuCode and fulfillmentPreference.
fulfillmentPreferenceenumOptional per lineAUTO (default) | FORWARDING. FORWARDING never consumes stocked inventory.
serviceTypeenumOptionalEXPRESS | AIR_CARGO | BULK_SHIPPING. Default AIR_CARGO.
checkoutShippingMethodstringOptionalThe 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 / orderTotalstring / numberOptionalThree-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}

Scope orders:read

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

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
routingVersionintegerRequiredThe latest routingVersion returned by the fulfillment-intent projection.
reasonstringRequiredNon-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

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
routingVersionintegerRequiredThe latest value returned by GET fulfillment intent.
parcelsarrayRequired0–50 supplier parcels. Each has trackingNumber and 1–100 line allocations; carrier, supplierName and notes are optional.
linesarrayRequired per parcelexternalLineId 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

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
routingVersionintegerRequiredThe 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

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
routingVersionintegerRequiredThe latest routingVersion returned by the intent.
selectionenumRequiredFORWARD_ALL | WAIT_FOR_STOCK.

Re-evaluate inventory routing

POST/v1/merchant/fulfillment-intents/{id}/re-evaluate

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
routingVersionintegerRequiredThe latest routingVersion returned by the intent.

List delivery rules

GET/v1/merchant/routing-rules

Scope orders:read

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

ParameterTypeRequiredDescription

Replace delivery rules

PUT/v1/merchant/routing-rules

Scope orders:write

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

ParameterTypeRequiredDescription
rulesarrayRequiredUp to 50 rules in priority order. Send an empty array to clear them.
rules[].namestringRequiredShown on every order this rule matches, so name it for the reason, not the mechanism.
rules[].destinationCountryCodesarrayOptionalTwo-letter codes. Empty means any destination.
rules[].skuRoutingTagsarrayOptionalMatches when any item in the order carries any of these tags. Set tags on your SKUs.
rules[].checkoutShippingMethodsarrayOptionalStorefront shipping method ids (e.g. flat_rate:2). An exact instance is preferred; the method type is the fallback.
rules[].minDeclaredValuenumberOptionalInclusive lower bound. An order with no declared value does not match rather than counting as zero.
rules[].minTotalWeightKgnumberOptionalInclusive lower bound in kilograms.
rules[].maxTransitDaysintegerConditionalCeiling 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[].preferenceenumConditionalPRICE | SPEED. Picks within whatever bands remain.
rules[].transitBandIdstringConditionalPin a specific published band. Only meaningful when the rule is limited to one destination country, because bands are defined per country.
defaultPreferenceenumOptionalPRICE | SPEED applied when no rule matches.

Estimate cost and transit

POST/v1/merchant/orders/estimate

Scope orders:read

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

ParameterTypeRequiredDescription
destinationobjectRequiredDestination country code and postalCode.
packagesarrayRequiredDeclared parcels: weight (kg) plus length, width and height (cm). 1–20 entries.
transitBandIdstringOptionalQuote 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.
packageTypeIdstringOptional1 (general, default) | 2 (battery/cosmetic) | 3 (sensitive). These are separately routed and priced cargo types.
servicesarrayOptionalValue-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

Scope orders:write · requires Idempotency-Key

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

ParameterTypeRequiredDescription
externalOrderIdstringRequiredYour own order id, unique per account.
destinationobjectRequiredrecipientName, phone, country, province, city, street, postalCode.
parcelsarrayRequiredDomestic supplier parcels: trackingNumber, plus optional productName, quantity, declaredUnitPrice, description. cargoType: General | Electronics | Liquid | Branded. 1–50 entries.
shipmentTypeenumOptionalSINGLE | CONSOLIDATED. Default CONSOLIDATED.
transitBandIdstringOptionalA delivery-time band returned by /orders/estimate for this destination country. Use this when you have already quoted and want that exact band.
bandPreferenceenumOptionalCHEAPEST | 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.
checkoutShippingMethodstringOptionalWhat 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.
maxTransitDaysintegerOptionalCeiling 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.
inspectionbooleanOptionalRequest an inbound inspection. Default false.
servicesarrayOptionalValue-added work for the warehouse to execute — see Value-added services below.
destinationTypeenumOptionalCUSTOMER (default) | AMAZON_FBA — see Amazon FBA prep below.
fbaobjectRequired for FBA{ region: US | EU | CA | MX, fulfillmentCenter, shipmentId? }.
notesstringOptionalFree-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

Scope orders:read

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}

Scope orders:read

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

Scope orders:write · requires Idempotency-Key

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

Scope orders:write

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

ParameterTypeRequiredDescription
eventenumRequiredparcel.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

Scope orders:read

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

Scope orders:read

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

Scope orders:read

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

Scope orders:write

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

Scope orders:read

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

Scope tracking:read

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

Scope orders:read

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

ParameterTypeRequiredDescription
cursorquery stringOptionalOpaque cursor from nextCursor.
limitquery integerOptional1–100; default 20.
updatedSincequery ISO 8601OptionalOnly shipments updated at or after this timestamp. A timezone offset is required.
externalOrderIdquery stringOptionalExact merchant/store order identity.
trackingNumberquery stringOptionalExact WooliiPorter public tracking number.
sourceTypequery enumOptionalORDER | FULFILLMENT_ORDER.
statusquery enumOptionalPACKED | ROUTED | BOOKING_PENDING | BOOKING_UNKNOWN | BOOKED | HANDOVER_PENDING | HANDED_OVER | IN_TRANSIT | DELIVERED | CANCELLED | EXCEPTION.
trackingStatusquery enumOptionalUNKNOWN | 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}

Scope orders:read

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

Scope tracking:read

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

Scope orders:write

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

ParameterTypeRequiredDescription
skuCodestringRequiredYour SKU code, unique per account — the upsert key. Up to 80 characters.
namestringRequiredProduct name, up to 200 characters.
imageUrlstringOptionalProduct image URL — shown to warehouse operators at putaway and picking.
barcodestringOptionalScannable barcode, up to 64 characters.
unitWeightnumberOptionalWeight per unit in kg.
unitLength / unitWidth / unitHeightnumberOptionalUnit dimensions in cm. They also derive the per-unit volume used for the storage lines on your monthly statement.
declaredValuenumberOptionalDeclared customs value per unit.
customsNameEnstringOptionalEnglish customs name.
hsCodestringOptionalHS code, up to 20 characters.

List SKUs

GET/v1/merchant/skus

Scope orders:read

Your active SKUs, sorted by skuCode. Up to 500 entries are returned.

Create an inbound shipment

POST/v1/merchant/inbound-shipments

Scope orders:write

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

ParameterTypeRequiredDescription
externalRefstringOptionalYour own reference for this inbound, up to 120 characters.
businessTypeenumOptionalSTORED_FULFILLMENT (default) | AMAZON_FBA | MARKETPLACE_FBM.
fbaobjectRequired for AMAZON_FBAregion, fulfillmentCenter and optional shipmentId.
marketplaceobjectRequired for MARKETPLACE_FBMplatform and optional storeRef.
cartonCountintegerRequiredExpected physical carton count, 1–1000. The warehouse records actual cartons, weight and volume at receipt.
linesarrayRequiredDeclared contents: skuCode plus expectedQty. 1–200 entries. Every skuCode must already be registered.
servicesarrayOptional0–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.
notesstringOptionalFree-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

Scope orders:read

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

ParameterTypeRequiredDescription
cursorquery stringOptionalOpaque versioned cursor returned as nextCursor. Database IDs are never accepted as cursors.
limitquery integerOptional1–100; default 100.

Retrieve an inbound shipment

GET/v1/merchant/inbound-shipments/{inboundNumber}

Scope orders:read

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

Scope orders:read

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

Scope orders:write

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

ParameterTypeRequiredDescription
externalOrderIdstringRequiredYour own order id, unique per account — the idempotency key (e.g. wc-1042).
destinationobjectRequiredrecipientName, phone, country, province, city, street, postalCode.
linesarrayRequiredskuCode plus qty per line. 1–100 entries. Every skuCode must already be registered.
serviceTypeenumOptionalEXPRESS | AIR_CARGO | BULK_SHIPPING. Default AIR_CARGO.
checkoutShippingMethodstringOptionalThe 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.
draftOnlybooleanOptionalDefault false: the order auto-confirms and reserves stock immediately. true stops at draft until you call confirm.
notesstringOptionalFree-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

Scope orders:read

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

Scope orders:write

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

Scope orders:write

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

Scope finance:read

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

Scope orders:write

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

ParameterTypeRequiredDescription
toWarehouseIdstringRequiredActive overseas warehouse ID supplied by WooliiPorter operations. V1 does not yet expose warehouse discovery.
linesarrayRequired1–200 registered SKU lines with positive integer qty.
notesstringOptionalTransfer 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

Scope orders:read

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

Scope webhooks:write

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

ParameterTypeRequiredDescription
urlstringRequiredPublic https URL that receives deliveries.
eventTypesarrayRequiredEvent types to deliver — see Webhook events below.

List webhook endpoints

GET/v1/merchant/webhook-endpoints

Scope webhooks:write

Lists your endpoints with status and failure counters. Signing secrets are never returned again.

Disable a webhook endpoint

DELETE/v1/merchant/webhook-endpoints/{id}

Scope webhooks:write

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

EventMeaning
parcel.receivedA domestic supplier parcel was scanned and recorded at the warehouse.
inspection.completedAn inspection you requested finished.
quote.readyPacking and measurement produced the final packed quote; the order moved to payment.
payment.completedThe shipment settlement was paid; the order moved on to dispatch.
shipment.dispatchedCarrier events confirm the shipment entered the provider network.
tracking.updatedA normalized WooliiPorter parcel tracking fact changed. The payload contains public shipment and parcel identifiers only.
shipment.exceptionAn exception was recorded against the shipment.
shipment.deliveredEvery outbound package on the order has a delivered carrier fact.
inbound.exception.openedWooliiPorter opened an inbound receiving review. The payload contains the IB number and customer-safe exception projection only.
inbound.exception.updatedThe public state, customer-action requirement or resolution of an inbound receiving review changed.
cancellation.updatedThe 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_lowThe 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

ToolWhat it does
create_forwarding_orderPre-alert an order: parcels, destination, value-added service requests. Idempotent per externalOrderId.
estimate_shipping_costRoute-aware planning estimate using the same versioned engine as the final packed quote; current route/rate/FX can differ before packing.
get_orderRead one order, including value-added service progress.
list_orders_updated_sinceIncremental polling: orders changed since a timestamp.
get_packed_quoteFinal packed quote and transit ETA.
get_settlementThe line-item bill — every charge explainable.
pay_orderPay per shipment; sandbox settles instantly, production returns a Stripe client secret.
get_trackingNormalized carrier tracking for every outbound package.
get_timelineUnified China-leg + carrier-leg event timeline.
list_shipmentsCursor-paginated unified shipments, filterable by source, status, tracking and update time.
get_shipmentOne shipment with public service, parcels, merchant sell quote and normalized events.
get_shipment_trackingShipment-native WooliiPorter tracking for every outbound parcel.
cancel_orderCancel while the order is still awaiting parcels.
cancel_fulfillment_intentCancel one unified commerce fulfillment intent; the outcome distinguishes terminal cancellation from warehouse or admin review.
register_skuRegister or update a merchant SKU (upsert by skuCode) — required before stocking or fulfillment.
create_inbound_shipmentCreate a stocking/FBA/FBM inbound and receive an IB inbound number and address; no supplier tracking is required.
get_inbound_shipmentRead one inbound by public IB number, including carton progress and customer-safe exception state.
get_inventoryLive stock levels per SKU per warehouse plus aging, from the immutable movement ledger.
create_fulfillment_orderCreate a dropship order from stocked inventory — auto-confirms and reserves FIFO; ON_HOLD names any shortfall.
confirm_fulfillmentConfirm a draft order, or retry reservation on an on-hold order after restocking.
get_statementMonthly statement: per-lot storage fees plus fulfillment fees, every line explainable.
list_shippable_countriesDestinations 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

SettingWhat it controls
API key & environmentUse wp_sk_test_… for sandbox and wp_sk_live_… for production. Test and connect verifies the real key environment and refuses a mismatch.
API baseProduction 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 routingOff by default. Enables fulfillment intents only after the platform account exposes smartRouting and this Store is registered.
Automatic submissionOff by default. Can be enabled only after connection, environment, scope, Store, webhook-secret and Action Scheduler checks all pass.
Delivery speed preferenceLowest 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 secretRequired in every environment. The receiver verifies HMAC, Store identity, intent identity (when known), timestamp and eventId deduplication before writing order state.

Install

  1. Active merchants can download the reviewed ZIP from the Integrations workspace. Upload it through Plugins → Add New → Upload Plugin, or upload the wooliiporter-fulfillment directory to wp-content/plugins/. Requires WordPress 6.4+, PHP 7.4+ and WooCommerce 8.2+.
  2. Activate WooliiPorter Fulfillment for WooCommerce on the Plugins screen.
  3. Open WooCommerce → WooliiPorter, paste a wp_sk_test_… key, configure the webhook signing secret, then run Test and connect.
  4. Map stocked products/variations to WooliiPorter SKU codes; mark supplier-direct products Always forward. Exercise stocked, forwarding and mixed outcomes in sandbox.
  5. 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