Skip to main content

Multi-Package Order Workflow (Multicolis)

This guide demonstrates the complete workflow for creating and managing multi-package orders with advanced fulfillment order management through the Wing API v3.

Overview

A multi-package order involves multiple fulfillment orders that may be shipped separately, split, merged, or migrated between warehouses. The workflow includes:

  1. Authentication - Obtain an access token
  2. Create Order - Create an order with multiple fulfillment orders
  3. Manage Fulfillment Orders - Split, merge, or migrate as needed
  4. Create Parcels - Generate shipping labels for each fulfillment order

Step 1: Authentication

First, obtain an access token using your credentials:

mutation {
createAccessToken(
input: { email: "your_email@example.com", password: "your_password" }
) {
accessToken
refreshToken
expiresAt
}
}

Store the accessToken for all subsequent requests.

Step 2: Create Multi-Package Order

Create an order with multiple fulfillment orders, each representing a separate package:

mutation {
createOrder(
input: {
ref: "ORDER-MULTI-2024-001"
recipient: {
firstName: "Pierre"
lastName: "Martin"
email: "pierre.martin@example.com"
phone: "+33623456789"
line1: "15 Avenue des Champs-Élysées"
city: "Paris"
zip: "75008"
countryCode: "FR"
}
fulfillmentOrders: [
{
service: STANDARD
products: [
{
designation: "Laptop Computer"
sku: "LAPTOP-PRO-15"
quantity: 1
price: 1299.99
weight: 2.5
length: 40.0
width: 30.0
height: 5.0
}
]
isInsuranceEnabled: true
isSignatureEnabled: true
}
{
service: EXPRESS
products: [
{
designation: "Laptop Bag"
sku: "BAG-LAPTOP-001"
quantity: 1
price: 79.99
weight: 0.8
}
{
designation: "USB-C Charger"
sku: "CHARGER-USBC-65W"
quantity: 1
price: 49.99
weight: 0.3
}
]
}
{
service: STANDARD
products: [
{
designation: "Wireless Mouse"
sku: "MOUSE-WIRELESS-PRO"
quantity: 2
price: 39.99
weight: 0.15
}
{
designation: "Mouse Pad"
sku: "MOUSEPAD-XL"
quantity: 2
price: 14.99
weight: 0.2
}
]
}
]
}
) {
id
ref
status
recipient {
id
firstName
lastName
}
fulfillmentOrders {
id
status
service
products {
id
designation
sku
quantity
}
}
}
}

Response:

{
"data": {
"createOrder": {
"id": "ord_multi123",
"ref": "ORDER-MULTI-2024-001",
"status": "PENDING",
"recipient": {
"id": "rec_abc789",
"firstName": "Pierre",
"lastName": "Martin"
},
"fulfillmentOrders": [
{
"id": "fo_laptop_001",
"status": "PENDING",
"service": "STANDARD",
"products": [
{
"id": "fop_001",
"designation": "Laptop Computer",
"sku": "LAPTOP-PRO-15",
"quantity": 1
}
]
},
{
"id": "fo_accessories_002",
"status": "PENDING",
"service": "EXPRESS",
"products": [
{
"id": "fop_002",
"designation": "Laptop Bag",
"sku": "BAG-LAPTOP-001",
"quantity": 1
},
{
"id": "fop_003",
"designation": "USB-C Charger",
"sku": "CHARGER-USBC-65W",
"quantity": 1
}
]
},
{
"id": "fo_peripherals_003",
"status": "PENDING",
"service": "STANDARD",
"products": [
{
"id": "fop_004",
"designation": "Wireless Mouse",
"sku": "MOUSE-WIRELESS-PRO",
"quantity": 2
},
{
"id": "fop_005",
"designation": "Mouse Pad",
"sku": "MOUSEPAD-XL",
"quantity": 2
}
]
}
]
}
}
}

Step 3: Manage Fulfillment Orders

Split Fulfillment Order

Split a fulfillment order to create a new separate package:

Use case: You want to ship some items from fo_peripherals_003 separately (e.g., ship 1 mouse immediately and keep 1 mouse for later).

mutation {
splitFulfillmentOrder(
input: {
sourceFulfillmentOrderId: "fo_peripherals_003"
productList: [{ fulfillmentOrderProductId: "fop_004", quantity: 1 }]
}
) {
sourceFulfillmentOrder {
id
status
products {
id
designation
quantity
}
}
targetFulfillmentOrder {
id
status
products {
id
designation
quantity
}
}
}
}

Result: Creates a new fulfillment order with 1 mouse, leaving 1 mouse + 2 mouse pads in the source.

Merge Fulfillment Orders

Merge two fulfillment orders into a single package:

Use case: Combine fo_accessories_002 and the remaining items from fo_peripherals_003 into one shipment.

mutation {
mergeFulfillmentOrder(
input: {
sourceFulfillmentOrderId: "fo_peripherals_003"
targetFulfillmentOrderId: "fo_accessories_002"
}
) {
sourceFulfillmentOrder {
id
status
products {
id
quantity
}
}
targetFulfillmentOrder {
id
status
products {
id
designation
quantity
}
}
}
}

Result: All products from source are moved to target. Source fulfillment order becomes empty.

Migrate Fulfillment Order

Migrate products between warehouses or change shipping method:

Use case: Move specific products from one fulfillment order to another (e.g., different warehouse or service level).

mutation {
migrateFulfillmentOrder(
input: {
sourceFulfillmentOrderId: "fo_laptop_001"
targetFulfillmentOrderId: "fo_new_warehouse"
productList: [{ fulfillmentOrderProductId: "fop_001", quantity: 1 }]
}
) {
sourceFulfillmentOrder {
id
status
products {
id
quantity
}
}
targetFulfillmentOrder {
id
status
products {
id
designation
quantity
}
}
}
}

Result: Specified products migrated to target fulfillment order.

Step 4: Create Parcels

Once fulfillment orders are finalized, create shipping labels for each:

mutation {
createFulfillmentParcel(
input: {
fulfillmentOrderId: "fo_laptop_001"
organizationPickupId: "pickup_warehouse_paris"
}
) {
id
trackingNumber
trackingUrl
carrierLabel
status
fulfillmentOrder {
id
status
}
}
}

Repeat for each fulfillment order to generate all necessary shipping labels.

Complete JavaScript Example

const WING_API_URL = 'https://api-developer.wing.eu/v3'

async function multiPackageOrderWorkflow() {
// Step 1: Authenticate
const authResponse = await fetch(WING_API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
mutation {
createAccessToken(input: {
email: "${process.env.WING_EMAIL}"
password: "${process.env.WING_PASSWORD}"
}) {
accessToken
}
}
`,
}),
})

const { data: authData } = await authResponse.json()
const accessToken = authData.createAccessToken.accessToken

console.log('✓ Authenticated successfully')

// Step 2: Create Multi-Package Order
const orderResponse = await fetch(WING_API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation CreateOrder($input: OrderToCreateInput!) {
createOrder(input: $input) {
id
ref
fulfillmentOrders {
id
service
products {
id
designation
quantity
}
}
}
}
`,
variables: {
input: {
ref: `ORDER-MULTI-${Date.now()}`,
recipient: {
firstName: 'Pierre',
lastName: 'Martin',
email: 'pierre.martin@example.com',
phone: '+33623456789',
line1: '15 Avenue des Champs-Élysées',
city: 'Paris',
zip: '75008',
countryCode: 'FR',
},
fulfillmentOrders: [
{
service: 'STANDARD',
products: [
{
designation: 'Laptop Computer',
sku: 'LAPTOP-PRO-15',
quantity: 1,
price: 1299.99,
weight: 2.5,
},
],
isInsuranceEnabled: true,
},
{
service: 'EXPRESS',
products: [
{
designation: 'Laptop Bag',
sku: 'BAG-LAPTOP-001',
quantity: 1,
price: 79.99,
weight: 0.8,
},
],
},
],
},
},
}),
})

const { data: orderData } = await orderResponse.json()
const fulfillmentOrders = orderData.createOrder.fulfillmentOrders

console.log(`✓ Order created: ${orderData.createOrder.ref}`)
console.log(` ${fulfillmentOrders.length} fulfillment orders created`)

// Step 3: Split a fulfillment order (optional)
if (fulfillmentOrders.length > 0) {
const foToSplit = fulfillmentOrders[0]
const productToSplit = foToSplit.products[0]

const splitResponse = await fetch(WING_API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation SplitFulfillmentOrder($input: SplitFulfillmentOrderInput!) {
splitFulfillmentOrder(input: $input) {
sourceFulfillmentOrder {
id
products {
id
quantity
}
}
targetFulfillmentOrder {
id
products {
id
quantity
}
}
}
}
`,
variables: {
input: {
sourceFulfillmentOrderId: foToSplit.id,
productList: [
{
fulfillmentOrderProductId: productToSplit.id,
quantity: Math.floor(productToSplit.quantity / 2) || 1,
},
],
},
},
}),
})

const { data: splitData } = await splitResponse.json()
console.log(`✓ Split fulfillment order`)
console.log(
` New FO created: ${splitData.splitFulfillmentOrder.targetFulfillmentOrder.id}`,
)
}

// Step 4: Create parcels for each fulfillment order
const parcels = []
for (const fo of fulfillmentOrders) {
const parcelResponse = await fetch(WING_API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation CreateParcel($input: CreateFulfillmentParcelInput!) {
createFulfillmentParcel(input: $input) {
id
trackingNumber
trackingUrl
carrierLabel
}
}
`,
variables: {
input: {
fulfillmentOrderId: fo.id,
organizationPickupId: process.env.WING_PICKUP_LOCATION_ID,
},
},
}),
})

const { data: parcelData } = await parcelResponse.json()
parcels.push(parcelData.createFulfillmentParcel)

console.log(`✓ Parcel created for FO ${fo.id}`)
console.log(
` Tracking: ${parcelData.createFulfillmentParcel.trackingNumber}`,
)
}

return {
order: orderData.createOrder,
parcels,
}
}

// Run the workflow
multiPackageOrderWorkflow()
.then(result => {
console.log('\n✓ Multi-package workflow completed!')
console.log(` Order: ${result.order.ref}`)
console.log(` Parcels created: ${result.parcels.length}`)
})
.catch(error => {
console.error('✗ Workflow failed:', error)
})

Fulfillment Order Management Scenarios

Scenario 1: Partial Shipment (Stock Split)

Problem: Only 2 of 5 ordered items are in stock.

Solution: Split the fulfillment order to ship available items immediately.

mutation {
splitFulfillmentOrder(
input: {
sourceFulfillmentOrderId: "fo_original"
productList: [
{
fulfillmentOrderProductId: "fop_123"
quantity: 2 # Ship 2 now
}
]
}
) {
# Creates new FO with 2 items (ship now)
# Source FO keeps 3 items (ship later)
targetFulfillmentOrder {
id
}
}
}

Scenario 2: Consolidation for Cost Savings

Problem: Multiple small fulfillment orders to the same address.

Solution: Merge them into one package to reduce shipping costs.

mutation {
mergeFulfillmentOrder(
input: {
sourceFulfillmentOrderId: "fo_small_1"
targetFulfillmentOrderId: "fo_small_2"
}
) {
# All items now in fo_small_2
# fo_small_1 is emptied
targetFulfillmentOrder {
id
products {
designation
quantity
}
}
}
}

Scenario 3: Warehouse Transfer

Problem: Product available at different warehouse, needs migration.

Solution: Migrate items to fulfillment order at the other warehouse.

mutation {
migrateFulfillmentOrder(
input: {
sourceFulfillmentOrderId: "fo_warehouse_paris"
targetFulfillmentOrderId: "fo_warehouse_lyon"
productList: [{ fulfillmentOrderProductId: "fop_456", quantity: 3 }]
}
) {
targetFulfillmentOrder {
id
products {
designation
quantity
}
}
}
}

Management Operations Comparison

OperationPurposeSource FO AfterTarget FO AfterCreates New FO
SplitSeparate products into new packageKeeps remaining productsReceives specified products✓ Yes
MergeCombine all products into one packageEmptied (no products)Receives all products✗ No (uses existing)
MigrateTransfer specific productsKeeps remaining productsReceives specified products✗ No (uses existing)

Status Tracking

Query the current state of all fulfillment orders:

query {
order(input: { id: "ord_multi123" }) {
id
ref
status
fulfillmentOrders {
id
status
service
products {
id
designation
quantity
}
parcels {
id
trackingNumber
status
}
}
}
}

Best Practices

1. Plan Fulfillment Strategy

  • Group products by warehouse location
  • Consider shipping speed requirements (STANDARD vs EXPRESS)
  • Evaluate cost vs speed tradeoffs

2. Use Split for Stock Management

  • Ship available items immediately
  • Keep out-of-stock items in separate fulfillment order
  • Update customers with partial shipment tracking

3. Use Merge for Efficiency

  • Combine small packages to reduce costs
  • Consolidate same-warehouse fulfillments
  • Check size/weight limits before merging

4. Use Migrate for Flexibility

  • Transfer products between warehouses
  • Change shipping method by migrating to different service level
  • Balance inventory across locations

5. Error Handling

Always check operation results:

const { data, errors } = await response.json()

if (errors) {
console.error('GraphQL Errors:', errors)
// Handle specific error cases
for (const error of errors) {
if (error.extensions?.code === 'INVALID_QUANTITY') {
console.error('Cannot split: quantity exceeds available')
}
}
}

Common Errors

ErrorCauseSolution
INVALID_QUANTITYSplit/migrate quantity exceeds availableCheck product quantities before operation
FULFILLMENT_ORDER_NOT_FOUNDInvalid fulfillment order IDVerify IDs from createOrder or query response
CANNOT_MERGE_DIFFERENT_SERVICESMerging incompatible shipping servicesOnly merge fulfillment orders with same service type
PARCEL_ALREADY_CREATEDTrying to modify FO with existing parcelCannot split/merge/migrate after parcel creation
EMPTY_PRODUCT_LISTNo products specified in split/migrateInclude at least one product in productList

Next Steps