Skip to main content

Multi Parcel 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

Data model for this workflow​

Unlike a single-package order, one Order here carries several FulfillmentOrders, each with its own products, status and parcel(s) — shipped, split, or merged independently. Each FulfillmentOrder packs a list of FulfillmentOrdersProduct lines (one per SKU, each with its own quantity) — it is exactly that per-line quantity that splitFulfillmentOrder/migrateFulfillmentOrder operate on:

Splitting a fulfillment order: by product, or by quantity​

splitFulfillmentOrder always takes the same input shape — productList: [{ fulfillmentOrderProductId, quantity }] — but what it does to the source line depends on whether quantity covers all of that line or only part of it:

  • Split by product: quantity equals the line's full quantity → the whole product line moves to the new FulfillmentOrder, and disappears from the source.
  • Split by quantity: quantity is less than the line's full quantity → the same product line ends up on both fulfillment orders, each with a share of the original quantity.

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: COLISSIMO
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: CHRONOPOST
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: COLISSIMO
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": "CREATED",
"recipient": {
"id": "rec_abc789",
"firstName": "Pierre",
"lastName": "Martin"
},
"fulfillmentOrders": [
{
"id": "fo_laptop_001",
"status": "CREATED",
"service": "COLISSIMO",
"products": [
{
"id": "fop_001",
"designation": "Laptop Computer",
"sku": "LAPTOP-PRO-15",
"quantity": 1
}
]
},
{
"id": "fo_accessories_002",
"status": "CREATED",
"service": "CHRONOPOST",
"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": "CREATED",
"service": "COLISSIMO",
"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​

splitFulfillmentOrder always takes the same productList: [{ fulfillmentOrderProductId, quantity }] input — the outcome (whole product line moved, or the line divided across both fulfillment orders) depends only on whether quantity matches the line's full remaining quantity. Both cases below start from fo_peripherals_003, which has two lines: fop_004 (Wireless Mouse × 2) and fop_005 (Mouse Pad × 2).

Split by product — move a product line entirely to a new fulfillment order (quantity equals the line's full quantity):

Use case: Ship the mouse pads separately, keeping the mice in the original package.

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

Result: fop_005 (Mouse Pad × 2) moves entirely to the new targetFulfillmentOrder. The source keeps only fop_004 (Wireless Mouse × 2).

Split by quantity — move only part of a line's quantity, so the same product line ends up on both fulfillment orders:

Use case: You only have 1 of the 2 ordered mice in stock — ship that one now, keep the other 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: fop_004 (Wireless Mouse) now exists on both fulfillment orders: 1 unit stays on the source (alongside the 2 mouse pads), 1 unit moves to the new targetFulfillmentOrder — quantity: 1 was less than the line's original quantity of 2.

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:

createFulfillmentParcel returns the FulfillmentOrder, with tracking fields nested under parcels:

mutation {
createFulfillmentParcel(
input: {
fulfillmentOrderId: "fo_laptop_001"
organizationPickupId: "pickup_warehouse_paris"
}
) {
id
status
parcels {
id
trackingId
trackingURL
labelURL
}
}
}

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: 'COLISSIMO',
products: [
{
designation: 'Laptop Computer',
sku: 'LAPTOP-PRO-15',
quantity: 1,
price: 1299.99,
weight: 2.5,
},
],
isInsuranceEnabled: true,
},
{
service: 'CHRONOPOST',
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
status
parcels {
id
trackingId
trackingURL
labelURL
}
}
}
`,
variables: {
input: {
fulfillmentOrderId: fo.id,
organizationPickupId: process.env.WING_PICKUP_LOCATION_ID,
},
},
}),
})

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

console.log(`✓ Parcel created for FO ${fo.id}`)
console.log(` Tracking: ${parcel.trackingId}`)
}

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
trackingId
status
}
}
}
}

Best Practices​

1. Plan Fulfillment Strategy​

  • Group products by warehouse location
  • Consider shipping speed requirements (e.g. COLISSIMO vs CHRONOPOST)
  • 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​