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:
- Authentication - Obtain an access token
- Create Order - Create an order with multiple fulfillment orders
- Manage Fulfillment Orders - Split, merge, or migrate as needed
- 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:
quantityequals the line's full quantity → the whole product line moves to the newFulfillmentOrder, and disappears from the source. - Split by quantity:
quantityis 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
| Operation | Purpose | Source FO After | Target FO After | Creates New FO |
|---|---|---|---|---|
| Split | Separate products into new package | Keeps remaining products | Receives specified products | ✓ Yes |
| Merge | Combine all products into one package | Emptied (no products) | Receives all products | ✗ No (uses existing) |
| Migrate | Transfer specific products | Keeps remaining products | Receives 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.
COLISSIMOvsCHRONOPOST) - 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
| Error | Cause | Solution |
|---|---|---|
INVALID_QUANTITY | Split/migrate quantity exceeds available | Check product quantities before operation |
FULFILLMENT_ORDER_NOT_FOUND | Invalid fulfillment order ID | Verify IDs from createOrder or query response |
CANNOT_MERGE_DIFFERENT_SERVICES | Merging incompatible shipping services | Only merge fulfillment orders with same service type |
PARCEL_ALREADY_CREATED | Trying to modify FO with existing parcel | Cannot split/merge/migrate after parcel creation |
EMPTY_PRODUCT_LIST | No products specified in split/migrate | Include at least one product in productList |
Next Steps
- Learn about Single-Package Order Workflows for simpler orders
- Explore the Returns Workflow for handling returns end-to-end
- Batch shipped fulfillment orders into a Collection & Pickup
- Check Fulfillment Order for shipment tracking
Related API Operations
- createOrder - Create multi-package order
- splitFulfillmentOrder - Split products into separate package
- mergeFulfillmentOrder - Combine packages
- migrateFulfillmentOrder - Transfer products between warehouses
- createFulfillmentParcel - Generate shipping labels
- order - Query order status
- fulfillmentOrders - Query all fulfillment orders