DEVELOPERS / MERCHANT API V1

One contract from warehouse receipt to delivery.

WooliiPorter, operated by WOOLII, LLC of Newark, Delaware, provides China warehouse receiving, parcel consolidation and international forwarding for individuals, and fulfillment services for merchants. Forwarding customers and merchants buy from their own suppliers; the separate personal purchase concierge can buy supported products after a confirmed quote and payment. 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 implemented V1 route surface.

Read the whole contract now, without an account. Merchant signup, business details, human review, online agreement signing, then sandbox and production key management from the active merchant console.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

Contract release · V1
2.2.0
HTTP operations
51
Webhook events
13
MCP tools
26
Documentation revision
2026-10-28.1

Contract principles

Reliable order automation from one API

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 repeatable path from order submission to settlement and shipment updates.

PropertyIntegration benefit
Idempotent writes with stored-response replayRepeat an eligible request with the same key and payload to retrieve its original response and continue the same order.
HMAC signature over the raw body, immutable event IDs, at-least-once deliveryVerify the raw-body signature and deduplicate by event ID to keep your order status synchronized through retries.
Estimates typed separately from packed quotesPlan with an estimate, then pay against the quote produced from packed measurements and applicable fees.
Per-merchant keys with explicit scopes and a visible execution modeRun a scoped integration for your account and test order workflows in the key’s sandbox environment.

Merchant control

Clear products, quotes and fulfillment actions

Select a shipping product or save a store default. Review the complete itemized settlement and authorize payment through your approved workflow. When an order combines stocked and supplier-forwarded lines, the fulfillment intent presents the actions needed to proceed. Inventory reservation, warehouse work and shipment updates then follow the same business record.

Connect the same workflow through MCP

Payment-required handling

What a short wallet balance returns

Warehouse-service intake can return HTTP 402 with the wallet shortfall. A stocked fulfillment order or commerce intent can instead remain on hold with a balance reason. Show the returned action, open the merchant finance workspace for top-up, then retry the documented confirmation or re-evaluation action.

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. Most responses carry 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 commerce-order workflow lets an enabled WooCommerce store submit one order snapshot and receive a stocked, forwarding or merchant-review outcome, with the selected public product carried through execution. Prefer to let an agent drive it? See the MCP server.

Read shipping products first. A product’s fixed country assignment defines its transport service; quote checks validate cargo, postcode, weight and dimensions. Customer responses carry public product names, sell quotes and stable WooliiPorter tracking identities. Direct estimate/order endpoints use the account default; commerce intents also resolve the registered store default. Pass productCode explicitly to keep the same selection across calls.

Versions & changes

The URL /v1/merchant identifies the API major version. OpenAPI info.version identifies the Merchant API contract release; 3.1.0 is the OpenAPI document format. The documentation revision is a separate audit marker.

We use semantic versions: patches correct compatible defects or documentation; minor versions add compatible capabilities. Incompatible fields, states, payment semantics or webhook payloads require a major version and a documented migration. Published releases are immutable. Deprecation notices specify the replacement and retirement date before an old contract is retired.

Shopify and WordPress/WooCommerce connectors keep their own versions. Each coordinated release records their supported API contract, upgrade instructions and verification results. A new plugin version does not by itself change the API major version.

2.2.0 — changes

2.2.0 is an additive minor release over 2.1.0. Requests and existing fields keep their meaning. A declined value-added service now says why in services[].declineReason; the order object stays open in the OpenAPI, so additional response fields are not a validation change.

  • Value-added servicesA services[] entry whose state is declined adds declineReason: customer_declined (you declined it), expired (the approval window closed without a decision) or withdrawn (WooliiPorter operations withdrew it, for example after you asked by phone). state stays declined and no fee is charged in any case. Treat an unknown declineReason as declined.
  • OpenAPICreate, retrieve, cancel, simulate and list order responses now reference MerchantOrderResponse / MerchantOrderPage, with services[] typed as MerchantOrderService. The order object remains open (additionalProperties: true); only the service entries are described in detail.

2.1.0 — changes

2.1.0 is an additive minor release over 2.0.0. Existing requests keep their meaning and the new endpoints and webhook event are opt-in. Shipment detail gains the always-present actionRequired array: if you validate responses against the 2.0.0 OpenAPI with additionalProperties: false, regenerate from the 2.1.0 document before upgrading.

  • US CPSC certificatesGET, PUT and DELETE /skus/{skuCode}/cpsc maintain the Children's Product Certificate or General Certificate of Conformity of a SKU, with the same data and validation as the merchant console. The response reports a single filing status for the key environment; WooliiPorter operations file with the carrier. Certificates are account data shared with production, so sandbox keys can read them and changes need a live key (403 merchant_sandbox_read_only). SKU reads add cpscRegulated.
  • Action-required holdsShipment detail adds actionRequired. A not-yet-booked US shipment with a regulated SKU that is not cleared lists code cpsc_filing_required and, per SKU, the reason and whether you must act. The shipment stays in the warehouse and is not charged or booked until the list is empty.
  • Webhooksshipment.action_required is sent once per distinct hold; a hold that changes and returns is sent again. Subscribe explicitly: existing endpoints do not receive it, and WooCommerce/Shopify store registration and reconnection never add it (an explicit subscription on a store endpoint is kept). After the hold clears the shipment continues with the usual quote, payment and dispatch events.

2.0.0 — changes

2.0.0 is a major release over 1.3.0 on the same /v1 path. Items marked Migration required change published 1.3.0 behaviour; update an existing integration before relying on them.

  • Wallet paymentMigration requiredMerchant orders are paid with the explicit finance:pay wallet payment, which requires the current settlement version and an Idempotency-Key. The 1.3.0 card payment entry (POST /orders/{id}/pay) now returns merchant_payment_unavailable and never debits a wallet. Existing keys gain no spending power until finance:pay is granted.
  • DeclarationsMigration requiredSupplied per-unit declaration values must be at least USD 1 (1.3.0 accepted zero). Validate before submitting; existing frozen declarations are not rewritten.
  • SettlementMigration requiredSettlement reads merchant fee rows, bills and wallet refund evidence instead of the 1.3.0 payment projection. validForDispatch is a current settlement check; stage paid alone is not dispatch authorization or proof of physical handover.
  • Delivery trackingMigration requiredFulfillment intent delivery projects every current WooliiPorter parcel. complete is true only when all child orders and shipments are handed over and delivered: do not complete an order on a single parcel event, and refresh the intent after callbacks.
  • Fulfillment cancellationMigration requiredHTTP 202 from fulfillment-order cancellation means the request was accepted for warehouse or operations action. It is not final cancellation, inventory release or refund; keep reading cancellationStatus and the order.
  • Forwarding cancellationMigration requiredAn order that is partially received can no longer be cancelled directly and returns HTTP 409; it goes to review. Channel-linked orders cancel through their fulfillment-intent endpoint.
  • Amazon FBA inboundMigration requiredAMAZON_FBA inbound requires fba.destinationPostalCode (the fulfillment-center postal code) and fba.inboundPlanId (wf…) in addition to fulfillmentCenter and shipmentId. The booked carrier delivers from the China warehouse to the fulfillment center, and WooliiPorter sends the final-leg tracking to Amazon.
  • Inbound shipmentsMigration requiredfirstMile.trackingNumber (the upstream carrier number) is removed; use WooliiPorter public shipment tracking. Stored-inventory replenishment cartons report null receipt counts and dispatchUnits; do not read null as zero received.
  • OnboardingMigration requiredNew accounts complete business review, the approved service scope and online signing before inbound and fulfilment access; endpoints return a specific 403 envelope until then. Eligible existing accounts keep access without re-signing.
  • After-salesshipment.exception also fires when the carrier returns a parcel to sender and carries trackingStatus (EXCEPTION or RETURNED). Each post-handover incident opens an after-sales review instead of blocking warehouse work.
  • Product identifiersSKUs accept manufacturerPartNumber (model / part number, up to 50 characters) and a check-digit validated gtin for the EU product identifiers required on customs declarations from 1 November 2026. Both are optional, returned on SKU reads, and frozen into the parcel declaration at packing.
  • Tracking eventstracking.updated, shipment.exception and shipment.delivered are emitted the same way whether a carrier scan arrives by polling or by tracking-provider push.
  • Receiving exceptionsClosed and cancelled receiving reviews have terminal messages; the structured exception state, resolution and customerActionRequired fields remain authoritative.
  • WooCommerceWooCommerce snapshots preserve empty checkout regions (for example France). Re-evaluation reports repairable product/routing configuration conflicts with HTTP 409 and allows retry after configuration is corrected.
  • Tax and IOSSTax documentation follows per-order IOSS verification and frozen settlement snapshots.
  • DocumentationEndpoint-specific error envelopes are documented, and the console plugin package is distinguished from the WordPress directory release.

Authentication

Authenticate with a Bearer key. Keys belong to one account, carry a sandbox or production execution mode, and grant explicit scopes. New merchants complete business review and online signing before activation, then 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, finance:read, finance:pay (explicit wallet-payment permission), 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

The standard error envelope is shown alongside. Endpoint-specific responses take precedence:

  • PUT /transport-products validation errors (422) use a top-level string error, with code or issues when supplied.
  • POST /inbound-shipments returns a top-level string error and code: merchant_agreement_required (403) when the signed agreement does not cover the service. This response does not include a request ID.

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; complete the required business review and signing, or contact support if access is suspended.
403overseas_warehouse_unavailableOverseas warehouse services (warehouse transfers) are not open yet. Use the China warehouse for inbound and fulfillment.
403merchant_agreement_requiredThe requested service is not enabled under the merchant agreement. Complete signing or contact support to review the agreed service scope.
403merchant_smart_routing_disabled / merchant_store_not_registeredFulfillment-intent access is not enabled, 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.
409customs_consent_requiredInternational orders need the account owner to accept the current customs declaration and IOSS/VOEC consent in the merchant console (details.consoleUrl). Sandbox keys are not affected.
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 fulfillment projection changed or its state is no longer eligible. Read the current intent 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.
402merchant_insufficient_balanceTop up the merchant wallet, reread settlement and retry with a new Idempotency-Key.
409merchant_quote_changedRead and confirm the current settlement version before paying.
409merchant_settlement_review_requiredResolve the pending settlement, refund or declaration review before payment.
409merchant_amount_basis_mismatchSettlement and the order payable amount disagree. Contact support to reconcile the bill before paying.
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.
410merchant_transport_rules_retiredDelivery-rule writes have migrated to productCode and the explicit account/store default at /transport-products.
422merchant_routing_retiredDirect forwarding no longer accepts transitBandId, bandPreference or maxTransitDays; select productCode.
409transport_product_required / transport_product_unavailableA fulfillment intent needs an explicit/default product that supports its destination, cargo and purpose. The estimate endpoint reports these failures as 422 merchant_estimate_unavailable.
404merchant_sku_not_foundThe SKU in the path is not registered for this account.
403merchant_sandbox_read_onlySandbox keys can read but not change account data that production depends on, such as a SKU CPSC certificate. Use a live key.
409merchant_cpsc_version_conflict / merchant_cpsc_already_filedThe CPSC certificate changed since expectedVersion, or a filed certificate cannot be deleted. Re-read it and update instead.
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 enabled features. Call this before registering a store and enabling automatic order submission. The legacy smartRouting flag controls commerce fulfillment-intent access; shipping uses your selected product and its configured destination coverage.

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 as the merchant console: account, channel services, product sync, SKU links, inbound stocking, shipping product and first-order validation, plus wallet funding. The official plugin uses this response to show the next setup action.

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.

Submit a commerce order for fulfillment

POST/v1/merchant/fulfillment-intents

Scope orders:write · requires Idempotency-Key

Submits a versioned WooCommerce order snapshot. WooliiPorter resolves productCode, then the registered store default, then the account default, and validates the product against destination, cargo and purpose. Stocked lines reserve inventory; forwarding lines await domestic supplier parcels; mixed orders return the merchant actions needed to proceed. Access requires a registered store and features.smartRouting=true.

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. For this WooCommerce snapshot, province may be an empty string when checkout has no region field (for example France); do not invent a region. Carrier-specific address requirements still apply before booking.
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.
productCodestringOptionalShipping product code; defaults to the saved store/account selection. Legacy serviceType is compatibility input only and must identify a unique product.
checkoutShippingMethodstringOptionalThe buyer’s checkout shipping method, recorded as an observation; it does not select or change the shipping product.
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 selected public product, inventory/forwarding decisions per line, routingVersion, requiredActions, editable supplier parcels, child orders, complete public parcel delivery facts and any cancellation lifecycle. routingVersion is an optimistic concurrency token for fulfillment decisions; refresh it before every mutation.

delivery contains complete (boolean) and shipments[]. Each shipment contains shipmentNumber, status, handedOverAt and parcels[] with parcelNumber, trackingNumber, trackingUrl and trackingStatus. Cancelled replacement shipments are omitted. No upstream carrier identifiers are exposed.

Use delivery.complete for whole-order delivery. A delivered child, a carrier scan before warehouse handover, or a split route whose other child is not created is not complete. Re-read the intent for the current state after every callback or missed callback; older releases may omit delivery.

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.

Choose how a mixed order proceeds

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 stock and fulfillment readiness

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 shipping products and defaults

GET/v1/merchant/transport-products

Scope orders:read

Returns public shipping products, cargo types, fulfillment purposes, product families, country readiness and saved account/store defaults. stores contains only registrations in this API key environment; the account default is shared by the merchant account. Use countries[].available to check operational readiness, then estimate the selected product with actual destination and measurements.

Body parameters

ParameterTypeRequiredDescription

Product codes and coverage come from this catalog; names shown here are illustrative. A listed country with available=false remains a configured destination but is not ready for a quote.

This endpoint returns the product catalog and defaults without an X-Request-Id success header. Authentication failures use the standard merchant error envelope.

Save a default shipping product

PUT/v1/merchant/transport-products

Scope orders:write

Saves an explicit product selection for new orders. Specify storeId to change that registered store, or omit it for the account default. Existing intents and shipments keep their selected product. Send null to clear a default.

Body parameters

ParameterTypeRequiredDescription
productCodestring | nullRequiredProduct code from the catalog, or null to clear.
storeIdstringOptionalOwned store ID returned by the catalog; scoped to the API key environment.

storeId is the internal store resource ID returned by GET /transport-products, not externalStoreId. Store defaults are environment-scoped; omitting storeId updates the shared account default.

This endpoint currently returns validation errors as {error: string, issues: [...]}, or {error: string, code: "transport_product_unavailable" | "store_not_found"} with HTTP 422. Successful writes return {productCode} without an X-Request-Id header.

Read historical delivery rules

GET/v1/merchant/routing-rules

Scope orders:read

Historical records only. These rules no longer select a product for new orders.

Body parameters

ParameterTypeRequiredDescription

Retired delivery-rule writes

PUT/v1/merchant/routing-rules

Scope orders:write

Returns HTTP 410. Use productCode on orders or save a default through /transport-products.

Body parameters

ParameterTypeRequiredDescription

Estimate cost and transit

POST/v1/merchant/orders/estimate

Scope orders:read

Estimates freight and requested warehouse services for your selected product, destination and package measurements. The product has a fixed country assignment. The result uses current published prices, your merchant settlement policy and quote-time exchange rate. Final packed pricing uses warehouse measurements and adds applicable declaration-based taxes and fees.

Body parameters

ParameterTypeRequiredDescription
productCodestringOptionalPublic product code. If omitted, use the account default; if neither is set, legacy AIR_CARGO compatibility must resolve to exactly one destination/cargo-compatible product. This endpoint has no store selector.
destinationobjectRequiredDestination country code and postalCode.
packagesarrayRequired1–20 packages with weight (kg), length, width and height (cm). PURE_BATTERY also requires batteryCount: the number of individual batteries in each physical package.
packageTypeIdstringOptionalAPI cargo selectors: 1 (GENERAL, default) | 2 (BATTERY_COSMETIC: battery-powered, no liquids) | 3 (SENSITIVE: liquids, pastes, powders and cosmetics) | 4 (PURE_BATTERY). These are not database package-type IDs.
servicesarrayOptionalValue-added service requests, validated with the same rules as order creation.

This request accepts productCode, packageTypeId, destination, packages and services. Use the store-selected productCode explicitly when estimating for a store. A non-USD estimate returns totals.estimatedTotal=null because warehouse service lines are quoted in USD.

The estimate has no declaration or IOSS input, so it does not calculate VAT or the EU per-HS charge. Send the full customer declaration on the order/SKU and read final settlement after packing and HS review.

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 with a destination, domestic supplier parcels and a selected shipping product. Each product uses its assigned full-service supplier for that country. Transit-band and price/speed overrides are retired. Returns the Woolii order, its receiving instructions and the recorded parcels. externalOrderId is unique per account.

Body parameters

ParameterTypeRequiredDescription
productCodestringOptionalPublic product code saved on this pre-alert. Pass it explicitly for a stable store selection. When omitted, account-default or unambiguous legacy selection is resolved at pricing; direct orders have no store selector.
externalOrderIdstringRequiredYour own order id, unique per account.
destinationobjectRequiredrecipientName, phone, country, province, city, street, postalCode.
parcelsarrayRequired1–50 domestic parcels: trackingNumber (4–64 chars), optional productName, quantity (default 1), declaredUnitPrice (full customer per-unit value in USD, minimum 1 when supplied), description and cargoType: General (default) | Electronics | PureBattery | Liquid | Branded.
shipmentTypeenumOptionalSINGLE | CONSOLIDATED. Default CONSOLIDATED.
checkoutShippingMethodstringOptionalCheckout method as an observation only. It does not select the transport product.
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.

New forwarding creates return HTTP 201. The initial response confirms warehouse intake; a supplied product is fully checked against packed measurements at pricing. Quote readiness also requires complete declarations and confirmed HS classification.

Declare the complete customer value, at least USD 1 per unit when supplied. VAT uses quantity × declaredUnitPrice. New applicable EU non-IOSS quotes use the configured EUR 3 charge per distinct confirmed HS code per physical parcel. Read the final settlement lines rather than hard-coding fees; existing quote snapshots remain unchanged. IOSS treatment requires administrator verification for this order and a matching tax snapshot; an account tax number alone is insufficient. This request has no iossNumber or hsCode field; use the declaration and operations review workflows.

transitBandId, bandPreference and maxTransitDays return 422 merchant_routing_retired. serviceType is not an accepted field on this strict create schema. Use productCode.

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. In order responses, services[].state is requested | in_progress | done | skipped, plus draft (an extra job logged by the warehouse, being priced), awaiting_approval (waiting for your decision in the merchant console) and declined (not charged). A declined service also carries declineReason: customer_declined (you declined it), expired (the approval window closed) or withdrawn (WooliiPorter operations withdrew it); treat an unknown value as declined. services[].type is the type you declared, a catalog code in lower case for catalog services, or adhoc for an extra job. Orders with draft or awaiting_approval services are not packed or quoted until those are resolved.

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. A declined entry adds declineReason (customer_declined | expired | withdrawn) and is not charged.

Cancel an order

POST/v1/merchant/orders/{id}/cancel

Scope orders:write · requires Idempotency-Key

Cancels a direct forwarding pre-alert only before any parcel has been received and before payment has settled. The aggregate awaiting_parcels state alone does not prove that no goods are in the warehouse. Receipt and cancellation share a transaction lock.

If any parcel has arrived, returns 409 merchant_order_conflict. Contact support for cancellation review; goods, photos and warehouse locations remain recorded.

Channel-linked orders return 409 with details.fulfillmentIntentId. Cancel through POST /fulfillment-intents/{id}/cancel so the channel and child order remain synchronized.

Retry an ambiguous response with the same Idempotency-Key. A different key is a new request and is checked against the current order state.

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

Read packed freight, packaging, insurance and transit estimates for the selected product. New amounts come from each parcel’s current versioned sell quote; historical orders retain their legacy quote projection. For versioned quotes, amounts.total is the shipping/packaging/insurance subtotal. Read settlement for the complete itemized bill, including warehouse services and applicable taxes.

stage: pending_packed_quote | final_packed_quote | unavailable. The final versioned quote is visible after every packed parcel has a current quote in one currency.

For source=versioned_shipment_quote, amounts.total excludes VAS, VAT and per-HS fees. Use GET /settlement and its total to display the payment amount.

Retrieve the settlement

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

Scope orders:read

Read the complete itemized bill from merchant FeeItems, the merchant wallet payment and refunded amounts. Total is the sum of the returned fee rows in the order currency. Lines remain empty before the final quote. Copy settlementVersion into an explicit wallet-payment request after confirming the current total.

stage retains pending_final_quote | payable | paid | cancelled. A historical paid stage does not prove current funds: inspect payment.status and validForDispatch, especially after a refund or declaration correction.

payment includes provider, status, amount, currency, paidAt and refundedAmount. Wallet status may be open, completed, refund_pending, partially_refunded, refunded or void. billId identifies the merchant ledger bill.

settlementVersion is null before final pricing. A changed price, parcel, address or discount produces another version. Never substitute a client-entered amount.

validForDispatch checks current settlement evidence; booking, labels, exceptions and physical PDA handover remain separate gates. Sandbox is never eligible for real dispatch.

Existing legacy aggregate-only fees require reconciliation before wallet payment; the candidate no longer invents aggregate rows absent from the merchant fee ledger. This difference requires compatibility review.

Pay from the merchant wallet

POST/v1/merchant/orders/{id}/wallet-payment

Scope finance:pay · requires Idempotency-Key

Pay the confirmed final quote from the merchant wallet using the same bill and debit transaction as the console. Requires explicit finance:pay, Idempotency-Key and settlementVersion. Sandbox simulates payment without spending wallet funds. No card secret is returned.

Body parameters

ParameterTypeRequiredDescription
settlementVersionstringRequired64-character version returned by GET /settlement. Confirm the current total before submitting.

Create a payment-enabled key in the developer console as an authorized account owner. finance:pay is never added to existing or default keys automatically. Wallet funding uses the existing merchant console top-up flow.

402 merchant_insufficient_balance leaves the bill OPEN and moves no money. Top up, reread settlement and use a new Idempotency-Key. The original key continues replaying the original response.

409 merchant_quote_changed requires rereading and confirming the revised quote. Other payment blocks require the existing review workflow. Cancelled, refunded and under-review orders cannot be charged again here.

A replay is the original response snapshot; GET /settlement returns the current refund and dispatch eligibility. After a timeout retry the same key and version. The debit, order advancement, notification, webhook and replay response commit together. A different key for an already validly settled order returns its settlement without another debit.

payment.completed indicates settled funds; it is not a carrier booking or physical dispatch event.

Legacy card payment entry

POST/v1/merchant/orders/{id}/pay

Scope orders:write

Deprecated candidate behavior: production returns 503 merchant_payment_unavailable without collecting money because the personal Stripe completion rail rejects merchant orders. Use the merchant console or explicitly migrate to wallet-payment. Sandbox retains its simulated response. This is a compatibility-impacting safety change, not an automatic wallet debit.

Do not retry this production response automatically or expect a Stripe clientSecret. First upgrade the integration, obtain an explicitly authorized key and confirm a current quote. No JSON body is required here.

Existing sandbox simulation and pre-payment validation errors remain. Production rollout requires the migration and client-compatibility gate in the release plan; this candidate is not yet published.

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. actionRequired lists holds that keep a not-yet-booked shipment in the warehouse; it is empty otherwise.

A held US shipment returns, for example, "actionRequired": [ { "code": "cpsc_filing_required", "message": "…", "skus": [ { "skuCode": "TOY-BLOCKS-50", "reason": "CERTIFICATE_REFERENCES_MISSING", "merchantAction": true, "message": "…" } ] } ].

Reasons: CERTIFICATE_REFERENCES_MISSING and FILING_REJECTED need you to update the certificate (merchantAction true); FILING_PENDING and SERVICE_UNSUPPORTED are handled by WooliiPorter operations. Treat unknown codes and reasons as holds you cannot clear yourself.

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.
declaredValuenumberOptional at registrationFull customer customs value per unit in USD, minimum 1 when supplied. Required with customsNameEn and confirmed HS classification before international pricing and reservation can proceed.
customsNameEnstringOptionalEnglish customs name.
hsCodestringOptionalHS code, up to 20 characters.
manufacturerPartNumberstring | nullOptionalThe manufacturer's model or part number, up to 50 characters — the non-standard product identifier EU customs declarations require from 1 November 2026. skuCode is sent as your merchant identifier. null clears it.
gtinstring | nullOptionalGTIN-8, -12, -13 or -14 (EAN/UPC) with a valid check digit; required for EU declarations when the product has one. Kept separate from barcode, which may be a warehouse code. null clears it.
routingTagsstring[]OptionalUp to 20 normalized historical tags; transport product selection uses productCode.
lowStockThresholdinteger | nullOptional0–1,000,000; null clears the SKU low-stock threshold.

List SKUs

GET/v1/merchant/skus

Scope orders:read

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

cpscRegulated is true when the SKU has a US CPSC certificate on file; read it with GET /skus/{skuCode}/cpsc.

Retrieve a SKU CPSC certificate

GET/v1/merchant/skus/{skuCode}/cpsc

Scope orders:read

Returns the US CPSC certificate (Children's Product Certificate or General Certificate of Conformity) kept for one SKU and the filing status for your key environment. certificate is null when the SKU is not regulated. A SKU with a certificate is held from US shipment until WooliiPorter has filed the current certificate for the selected service; the shipment's actionRequired list is authoritative for a specific shipment.

filing.status: NOT_FILED (nothing filed yet), ACCEPTED (the current certificate was accepted), REJECTED (the latest filing of the current certificate was rejected — review the data or contact support), UNCONFIRMED (the result was not confirmed; WooliiPorter files again) or OUTDATED (filed before the certificate last changed).

Carrier identities, filing request bodies and raw carrier responses are never returned.

Save a SKU CPSC certificate

PUT/v1/merchant/skus/{skuCode}/cpsc

Scope orders:write

Creates or replaces the US CPSC certificate of a registered SKU. Replaying the same content is a no-op, so the call is safe to retry without an Idempotency-Key. Any change to the content invalidates earlier filings: the SKU is held from US shipment until WooliiPorter files the new version. Send expectedVersion from your last read to avoid overwriting a concurrent edit.

Body parameters

ParameterTypeRequiredDescription
certificateTypestringRequiredCHILDRENS_PRODUCT (CPC) or GENERAL_PRODUCT (GCC).
certifierId / productId / versionIdstring | nullTogetherCPSC Product Registry references (up to 100, 100 and 19 characters). Send all three or none; without them US shipments stay held with CERTIFICATE_REFERENCES_MISSING.
intendedUseCodestring | nullOptionalProduct Registry intended-use code, up to 16 characters.
fullDeclarationobject | nullOptionalComplete PGA data set (product, manufacturer, labs, certifier, point of contact) for a full filing. Field lengths follow the carrier eFiling table and are checked on save; see the OpenAPI CpscFullDeclaration schema.
expectedVersionintegerOptionalcertificate.version from your last read. A mismatch returns 409 merchant_cpsc_version_conflict.

404 merchant_sku_not_found when the SKU is not registered; register it with POST /skus first.

403 merchant_sandbox_read_only for sandbox keys: the certificate is shared with production, where a change re-holds live US shipments until it is filed again.

Remove a SKU CPSC certificate

DELETE/v1/merchant/skus/{skuCode}/cpsc

Scope orders:write

Removes a certificate entered by mistake, which also lifts the US CPSC hold for that SKU. A certificate that was already filed with a carrier is kept as evidence and returns 409 merchant_cpsc_already_filed; update it instead.

Create an inbound shipment

POST/v1/merchant/inbound-shipments

Scope orders:write

Creates stored-inventory intake or an enabled Amazon FBA prep/first-mile workflow. The compatibility MARKETPLACE_FBM input is normalized to STORED_FULFILLMENT for new stocking receipts. WooliiPorter returns a public IB number and assigned receiving address; place the IB number on every carton.

Body parameters

ParameterTypeRequiredDescription
externalRefstringOptionalYour own reference for this inbound, up to 120 characters.
originobjectOptionalcountryCode (2–3 letters), optional province and city; used by the warehouse intake assignment.
businessTypeenumOptionalSTORED_FULFILLMENT (default) | AMAZON_FBA. Legacy MARKETPLACE_FBM is accepted and normalized to STORED_FULFILLMENT.
fbaobjectRequired for AMAZON_FBAregion, fulfillmentCenter, destinationPostalCode (the FC postal code), shipmentId and inboundPlanId (wf…, used to send final-leg tracking to Amazon) are required. Also accepts productCode, firstMileMode (EXPRESS | AIR | SEA, default AIR) and the actual destinationCountryCode. EU is a region, not a destination country. The booked carrier delivers from the China warehouse to the FC.
marketplaceobjectOptional compatibility metadataplatform and optional storeRef. New stocking intake is normalized to STORED_FULFILLMENT; marketplace origin belongs to outbound channel orders.
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.region, fulfillmentCenter and shipmentId plus explicit RELABEL, POLYBAG and PALLETIZE prep covering every declared unit. Use an available product supporting AMAZON_FBA and an actual destination country for an EU shipment. Activation also requires the dedicated FBA warehouse workflow.

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.

Stored inventory follows expected → receiving → processing → putaway → stocked (or cancelled); sellable stock is available after putaway. FBA uses its dedicated prep/first-mile workflow. firstMile appears after recorded physical dispatch and contains WooliiPorter and dispatchedAt only; upstream carrier names and tracking/booking numbers are internal.

List inbound shipments

GET/v1/merchant/inbound-shipments

Scope orders:read

Your inbound shipments, newest first, with IB numbers, per-line quantities and customer-safe exceptions. source distinguishes inbound_delivery from stored_inventory replenishment. Replenishment has null declared/received cartons and no new receiving instructions; dispatchUnits reports actual carton and pallet counts. 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.
productCodestringOptionalPublic shipping product code; direct fulfillment uses the account default when omitted. This endpoint has no store selector. Legacy serviceType defaults to AIR_CARGO and must identify an unambiguous product if no explicit/default selection exists.
serviceTypeenumOptional compatibility fieldAIR_CARGO (default) | EXPRESS | BULK_SHIPPING. New integrations select productCode.
checkoutShippingMethodstringOptionalThe buyer’s checkout shipping method, recorded as an observation; it does not select or change the shipping product.
draftOnlybooleanOptionalDefault false: attempts confirmation and reservation; unmet prerequisites leave the order on_hold. true stops at draft until you call confirm.
notesstringOptionalFree-form note, up to 1000 characters.

Reservation is atomic and consumes stock oldest-first (FIFO) within the assigned warehouse. Product eligibility, a valid freight quote, full SKU declarations, confirmed HS classification and sufficient wallet funds are checked before reservation. When a prerequisite or stock is missing, the order is on_hold with holdReason; correct it, then call confirm.

Unregistered SKU codes return 422 merchant_sku_unregistered with the codes in details.missing.

state can be awaiting_payment, draft, confirmed, reserved, on_hold, picking, packed, dispatched, in_transit, delivered, cancelled or exception. Read holdReason and cancellation when present; do not treat an unfamiliar state as dispatch permission.

Fulfillment fees must be settled from the merchant wallet before dispatch. fees[] and monthly statements report ledger entries; neither is permission to ship before payment.

List fulfillment orders

GET/v1/merchant/fulfillment-orders

Scope orders:read

Your fulfillment orders in this API key environment, most recently updated first, with lines and fee items. Incremental polling uses updatedSince and cursor; continue until nextCursor is null.

Body parameters

ParameterTypeRequiredDescription
updatedSincequery stringOptionalISO 8601 update timestamp.
cursorquery stringOptionalContinuation value from nextCursor.
limitquery integerOptional1–100; default 100.

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

Requests cancellation. Returns HTTP 200 only when cancellation is complete. HTTP 202 means warehouse handling or administrative review is still required; inventory and any refund are finalized only after that workflow completes.

cancellationStatus is CANCELLED (200), or WAREHOUSE_STOPPED, WAREHOUSE_RETURNED or ADMIN_REVIEW (202). HTTP 202 is not confirmation of a refund or released stock. Read the order again to follow completion.

Before warehouse work or booking, cancellation can release reservations and refund prepaid fees automatically. After work has begun, warehouse handling and administrative cost review must finish first.

A standalone order already handed over returns 409 merchant_fo_state. A channel-linked order follows its fulfillment-intent cancellation workflow and may enter administrative review.

Repeating a completed cancellation returns HTTP 200. This endpoint does not use Idempotency-Key response replay; subsequent requests reflect the current cancellation 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. Overseas warehouse services are not open yet: until they launch this endpoint returns 403 overseas_warehouse_unavailable.

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.

Creation returns HTTP 201 with a frozen quote and OPEN bill; warehouse execution starts after payment. Fee amounts in this example are placeholders, not a rate promise. freightPending=true means freight still requires an operational quote; it is not free shipping. This strict V1 request accepts toWarehouseId, lines and notes only.

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. Overseas warehouse services are not open yet: until they launch this endpoint returns 403 overseas_warehouse_unavailable.

Body parameters

ParameterTypeRequiredDescription
cursorquery stringOptionalContinuation value from nextCursor.
limitquery integerOptional1–100; default 100.

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.dispatchedWarehouse handover or the supported dispatch workflow confirmed physical dispatch. Booking confirmation remains a separate state.
tracking.updatedA normalized WooliiPorter parcel tracking fact changed. The payload contains public shipment and parcel identifiers only.
shipment.exceptionThe carrier reported a parcel exception after handover: loss, damage or failed delivery (trackingStatus EXCEPTION), or a return to sender (trackingStatus RETURNED). WooliiPorter opens an after-sales review; read the order for the outcome.
shipment.deliveredEvery outbound package on the order has a delivered carrier fact.
shipment.action_requiredA packed shipment is held in the warehouse before quoting and booking. data.actionRequired matches the shipment detail field (today: code cpsc_filing_required with per-SKU reasons). Sent once per distinct hold; a cleared hold continues with the usual shipment events.
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_costEstimate the selected public product with current prices and measurements; final packed pricing adds applicable declaration-based fees.
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_orderLegacy entry: production payment is unavailable; migrate explicitly.
pay_order_from_walletPay a confirmed quote with finance:pay, a settlement version and idempotency key.
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.
get_sku_cpscRead the US CPSC certificate of a SKU and its filing status.
save_sku_cpscCreate or replace the US CPSC certificate of a SKU; changes require a new filing before US shipment.
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_shipping_productsPublic shipping products and saved account/store defaults.
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 directly to fulfillment-intent endpoints. Use the standalone MCP server to connect an authorized assistant in your own runtime with the same merchant API key and scopes.

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. Shopify-managed authorization connects those assigned orders to the shared fulfillment backend and sends eligible shipment updates back to the store.

WooliiPorter Fulfillment for WooCommerce

Plugin version 0.9.0 connects one WooCommerce installation to Woolii-operated fulfillment in China. Test and connect validates the account, API-key environment, required scopes and compatibility smartRouting feature flag, then registers the store. 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.

Stock, supplier parcels and merchant review

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. A product or variation marked Always forwardfollows supplier-parcel processing. Choose the store’s shipping product to define transport for each supported destination.

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 fulfillment 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.
Order processingThe compatibility smartRouting setting enables fulfillment intents after features.smartRouting=true and Store registration. The shipping product is an explicit selection.
Automatic submissionOff by default. Can be enabled only after connection, environment, scope, Store, webhook-secret and Action Scheduler checks all pass.
Shipping productSelect productCode or save a store/account default. Each product has a fixed provider per country. Orders retain their selected product and public name snapshot.
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. Choose a shipping product, enable the order-processing setting, then enable automatic submission after the connection, Store, webhook secret and Action Scheduler release gates all pass.

The WordPress.org listing currently offers stable version 0.9.0 through standard WordPress updates. Active merchants can download the reviewed 0.9.0 package from the Integrations workspace for the latest product-selection workflow. WordPress.org publication of that package is tracked separately from the merchant download.

The connector synchronizes store identity, order snapshots, catalog/SKU links and supplier-parcel allocations with the shared WooliiPorter backend. Its privacy disclosure explains the operational data used for those workflows.

Get access

Create a merchant login, submit business details and expected order volume, then complete human review and online signing. Your offer specifies services, freight discount and settlement currency. Shopify, WooCommerce, Amazon and direct API merchants follow the same process. Signing activates the merchant console and API-key management.

While your application is under review or awaiting signature, you can sign in, view your application and agreements, and use the public shipping calculator. API-key creation stays closed until the account is active.

Eligible existing merchant accounts retain their access without retroactive signing; no signature record is created on their behalf.

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 — 51 HTTP operations covering product selection, order creation, estimates, packed quotes, value-added services, tracking, settlement and inventory — with an OpenAPI 3.1 contract publicly available at /api/developers/openapi. Supported mutations use stored-response replay or resource-specific deduplication, and 13 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?

Idempotency lets you safely retry a request after a timeout. Direct forwarding creation and fulfillment-intent mutations require a stable Idempotency-Key and replay the stored response. Stocked fulfillment deduplicates by account, environment and externalOrderId. Follow each endpoint’s contract so retries keep the same business record.

How do I verify a WooliiPorter webhook signature?

Verify HMAC-SHA256 over timestamp.rawBody before parsing the body, check timestamp freshness, then deduplicate the immutable event ID and process it. WooliiPorter delivers 13 signed event types at least once. Use the signature headers and receiver example below so accepted duplicate deliveries keep the same result.

How do I get a WooliiPorter API key?

API keys are managed from an active merchant console after review and signing. Start with a sandbox key, validate the workflow, then use production within your agreed service scope.

What can I evaluate before applying?

Everything that decides an integration: the OpenAPI 3.1 contract, all 51 operations with their parameters, the error table, the HMAC signature scheme, the idempotency contract, the 13 webhook event types and the 26 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