DEVELOPERS / MERCHANT API V1

The forwarding workflow, as an API.

One merchant contract for direct forwarding, optional merchant-owned inventory, warehouse fulfillment, WooCommerce smart routing, transparent settlement and signed event delivery. Every operation below is reconciled against the deployed V1 route surface. API keys are available only after approval creates an active merchant account. Start with a sandbox key; use production only after validating the workflow and accepting the applicable account terms.

Apply for API access

Version
V1
HTTP operations
38
Scopes
4
Contract audited
2026-07-25

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 provider 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.

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 provider 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.
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_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_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.
429merchant_rate_limitedPer-key request budget exceeded. Honor the Retry-After header.
429merchant_sandbox_capDaily sandbox order limit reached for this account.

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 is a separate optional technical preview. The connection response intentionally reports mcp: false even when the downloadable stdio server is available.

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.
externalOrderIdstringRequiredWooCommerce order identity, 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.
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 and ROUTING_BLOCKED. Follow requiredActions 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.

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 and any child fulfillment or forwarding order. Refresh before every merchant decision so stale browser state cannot overwrite a newer route.

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.

Estimate cost and transit

POST/v1/merchant/orders/estimate

Scope orders:read

Models cost and transit time before you create an order, from the destination, declared package dimensions and any requested value-added services. It runs on the same rate engine as the final packed quote, so at settlement the only difference comes from actual weighed and measured parcels. An 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.
packageTypeIdstringOptional1 (general, default) | 2 (battery/liquid) | 3 (sensitive).
servicesarrayOptionalValue-added service requests, validated with the same rules as order creation.

A destination with no configured route returns 422 merchant_estimate_unavailable. 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, shipment preferences and the domestic supplier parcels you expect to arrive. 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.
serviceTypeenumOptionalEXPRESS | AIR_CARGO | BULK_SHIPPING. Default AIR_CARGO.
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

Countries you can ship to right now: active and configured with at least one live freight rate. The list reads the same data as the estimate and quote engines, so a listed country never turns out to have no route at quote time.

Retrieve the quote state

GET/v1/merchant/orders/{id}/quote

Scope orders:read

Distinguishes the pending packed quote from the final packed quote. Amounts appear only after packing and measurement produce the final quote — an estimate is never presented as a charge.

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

Normalized provider tracking for every outbound package on the order — the same events the Woolii order workflow records, including automatically re-registered tracking numbers.

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 an inbound workflow for stored fulfillment, Amazon FBA preparation or marketplace FBM. Your suppliers send declared domestic parcels to the assigned China receiving workflow; the backend selects the configured operations provider and warehouse for that business type. Stored-fulfillment goods are put away as merchant-owned inventory. FBA/FBM inbounds continue through their configured warehouse workflow and may initially rely on operator-updated provider milestones.

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.
parcelsarrayRequiredDomestic supplier parcels: trackingNumber plus optional productName. 1–100 entries.
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.

Configured service-provider scope and default routing decide the operationsProvider and receiving warehouse. If no active route exists, the API returns a 409 routing error; the API never guesses a provider.

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 parcel states and per-line received quantities. Up to 100 entries are returned.

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.
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 orders: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.updatedReserved for future granular tracking pushes; poll GET tracking today.
shipment.exceptionAn exception was recorded against the shipment.
shipment.deliveredEvery outbound package on the order has a delivered carrier fact.

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_costPre-order cost and transit estimate — same rate engine as the final quote.
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.
cancel_orderCancel while the order is still awaiting parcels.
register_skuRegister or update a merchant SKU (upsert by skuCode) — required before stocking or fulfillment.
create_inbound_shipmentPre-alert a stocking inbound: supplier parcels received and put away as merchant-owned inventory.
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.

Running WooCommerce? WooliiPorter Fulfillment for WooCommerce connects the Store to the fulfillment-intent endpoints directly. Version 0.5.1 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? A fulfillment-service app in development-store validation handles 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 on this page.

WooliiPorter Fulfillment for WooCommerce

Plugin version 0.5.1 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.5.1 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.
Default serviceEXPRESS | AIR_CARGO | BULK_SHIPPING — the serviceType used in each fulfillment intent.
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.5.1 is an official WooliiPorter early-access sideload release and is not represented as WooCommerce Marketplace-approved. Updates are manual until the official updater channel is enabled.

The connector does not expose WordPress Abilities or a Store-hosted MCP endpoint in version 0.5.1. 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. While review is pending, you can sign in and view the application status, but API-key creation and operational merchant tools remain locked. 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.