Define vehicle compartments for mixed cargo

With compartments, you can divide the cargo space of a vehicle into separate sections, each with its own capacity, so the HERE Tour Planning API can plan how jobs are distributed across those sections.

In real-life scenarios, a single vehicle often carries several types of goods that cannot share the same physical space. For example, a food delivery truck may have two separate compartments, one for gluten-free products and one for regular products, and these must be kept separate to prevent cross-contamination. Thanks to the compartments feature, you can now model each of these compartments individually, with its own capacity, and then combine them with mixing restrictions so that the optimization algorithm keeps conflicting job categories in separate compartments instead of restricting the whole vehicle.

📘

Note

Compartments is an ALPHA feature. For more information about feature maturity levels, see Explore experimental features.

Enable the compartments feature

Compartments is an experimental feature. To use it, add compartments to the experimentalFeatures array in the problem configuration:

{
  "configuration": {
    "experimentalFeatures": [
      "compartments"
    ]
  }
}

Define compartments on a vehicle type

To divide a vehicle into separate cargo sections, add a compartments array to the corresponding vehicle type under fleet.types, instead of using capacity. Each entry in the array requires a unique name and its own capacity:

{
  "id": "vehicle_1",
  "compartments": [
    {
      "name": "A",
      "capacity": [
        200
      ]
    },
    {
      "name": "B",
      "capacity": [
        200
      ]
    }
  ]
}
📘

Note

A vehicle type must set either capacity or compartments. If both are set, the API ignores capacity. Although the total vehicle capacity is the sum of all compartment capacities, each compartment maintains its own capacity constraint when jobs are assigned. See the Capacity constraints within separate compartments example to see this principle in action.

For more information, see Fleet.

Restrict mixing within a compartment

By default, mixing restrictions apply to the whole vehicle. To apply a restriction to specific compartments instead, add a compartments array to the corresponding entry in mixingRestrictions.restrictions. The API then applies the restriction separately to each listed compartment, so every compartment can carry only jobs from a single one of the conflicting categories.

"mixingRestrictions": {
  "level": "tour",
  "restrictions": [
    {
      "compartments": [
        "A",
        "B"
      ],
      "conflictingCategories": [
        "Gluten-Free",
        "Regular"
      ]
    }
  ]
}
📘

Note

Mixing restrictions on compartments are currently supported only at the tour level.

If a restriction entry omits compartments, the restriction applies to the whole vehicle, as described in Define mixed load restrictions.

Example: Capacity constraints within separate compartments

In this sample scenario, a vehicle has two compartments, A and B, each with a capacity of 5. Five identical jobs need to be delivered, each with a demand of 2. Even though the total vehicle capacity is 10 (sum of both compartments), goods cannot be transferred between compartments. This means only four jobs can be assigned to the vehicle (two per compartment), leaving one job unassigned due to capacity constraints.

The following section shows the full problem JSON:

Click to expand/collapse the sample JSON
{
  "configuration": {
    "experimentalFeatures": [
      "compartments"
    ]
  },
  "fleet": {
    "types": [
      {
        "profile": "vehicle",
        "amount": 1,
        "costs": {
          "time": 0.002,
          "distance": 0.001,
          "fixed": 10
        },
        "id": "vehicle_1",
        "shifts": [
          {
            "start": {
              "location": {
                "lat": 52.531,
                "lng": 13.38461
              },
              "time": "2026-01-16T08:00:00+01:00"
            },
            "end": {
              "location": {
                "lat": 52.531,
                "lng": 13.38461
              },
              "time": "2026-01-16T20:00:00+01:00"
            }
          }
        ],
        "compartments": [
          {
            "capacity": [
              5
            ],
            "name": "A"
          },
          {
            "capacity": [
              5
            ],
            "name": "B"
          }
        ]
      }
    ],
    "profiles": [
      {
        "name": "vehicle",
        "type": "car"
      }
    ]
  },
  "plan": {
    "jobs": [
      {
        "id": "Job_1",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.5259,
                    "lng": 13.39031
                  }
                }
              ],
              "demand": [
                2
              ]
            }
          ]
        }
      },
      {
        "id": "Job_2",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.52393,
                    "lng": 13.36127
                  }
                }
              ],
              "demand": [
                2
              ]
            }
          ]
        }
      },
      {
        "id": "Job_3",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.54179167844302,
                    "lng": 13.375259544700388
                  }
                }
              ],
              "demand": [
                2
              ]
            }
          ]
        }
      },
      {
        "id": "Job_4",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.52957449803126,
                    "lng": 13.335217684507372
                  }
                }
              ],
              "demand": [
                2
              ]
            }
          ]
        }
      },
      {
        "id": "Job_5",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.5494807231367,
                    "lng": 13.366856351494791
                  }
                }
              ],
              "demand": [
                2
              ]
            }
          ]
        }
      }
    ]
  }
}

Interpret the solution

As expected, the API assigns four jobs to the tour and leaves one job unassigned. The optimization algorithm distributes the jobs evenly across both compartments, with two jobs assigned to each. The response includes a compartment field on each delivery activity, and a loadPerCompartment array on each stop, showing the remaining load in every compartment as the tour progresses.

The following section shows the full solution JSON:

Click to expand/collapse the sample JSON
{
  "statistic": {
    "cost": 26.973000000000003,
    "distance": 11249,
    "duration": 2862,
    "times": {
      "break": 0,
      "driving": 1662,
      "serving": 1200,
      "stopping": 0,
      "waiting": 0
    },
    "costs": {
      "distance": 11.249,
      "fixed": 10,
      "time": 5.724
    },
    "intraStopDistance": 0
  },
  "tours": [
    {
      "shiftIndex": 0,
      "statistic": {
        "cost": 26.973000000000003,
        "distance": 11249,
        "duration": 2862,
        "times": {
          "break": 0,
          "driving": 1662,
          "serving": 1200,
          "stopping": 0,
          "waiting": 0
        },
        "costs": {
          "distance": 11.249,
          "fixed": 10,
          "time": 5.724
        },
        "intraStopDistance": 0
      },
      "stops": [
        {
          "activities": [
            {
              "jobId": "departure",
              "type": "departure",
              "location": {
                "lat": 52.531,
                "lng": 13.38461
              },
              "time": {
                "end": "2026-01-16T07:00:00Z",
                "start": "2026-01-16T07:00:00Z",
                "arrival": "2026-01-16T07:00:00Z"
              }
            }
          ],
          "load": [
            8
          ],
          "time": {
            "arrival": "2026-01-16T07:00:00Z",
            "departure": "2026-01-16T07:00:00Z"
          },
          "distance": 0,
          "loadPerCompartment": [
            {
              "load": [
                4
              ],
              "name": "A"
            },
            {
              "load": [
                4
              ],
              "name": "B"
            }
          ],
          "location": {
            "lat": 52.531,
            "lng": 13.38461
          }
        },
        {
          "activities": [
            {
              "jobId": "Job_2",
              "type": "delivery",
              "compartment": "A",
              "location": {
                "lat": 52.52393,
                "lng": 13.36127
              },
              "time": {
                "end": "2026-01-16T07:08:38Z",
                "start": "2026-01-16T07:03:38Z",
                "arrival": "2026-01-16T07:03:38Z"
              }
            }
          ],
          "load": [
            6
          ],
          "time": {
            "arrival": "2026-01-16T07:03:38Z",
            "departure": "2026-01-16T07:08:38Z"
          },
          "distance": 1780,
          "loadPerCompartment": [
            {
              "load": [
                2
              ],
              "name": "A"
            },
            {
              "load": [
                4
              ],
              "name": "B"
            }
          ],
          "location": {
            "lat": 52.52393,
            "lng": 13.36127
          }
        },
        {
          "activities": [
            {
              "jobId": "Job_4",
              "type": "delivery",
              "compartment": "B",
              "location": {
                "lat": 52.52957449803126,
                "lng": 13.335217684507372
              },
              "time": {
                "end": "2026-01-16T07:19:40Z",
                "start": "2026-01-16T07:14:40Z",
                "arrival": "2026-01-16T07:14:40Z"
              }
            }
          ],
          "load": [
            4
          ],
          "time": {
            "arrival": "2026-01-16T07:14:40Z",
            "departure": "2026-01-16T07:19:40Z"
          },
          "distance": 4018,
          "loadPerCompartment": [
            {
              "load": [
                2
              ],
              "name": "A"
            },
            {
              "load": [
                2
              ],
              "name": "B"
            }
          ],
          "location": {
            "lat": 52.52957449803126,
            "lng": 13.335217684507372
          }
        },
        {
          "activities": [
            {
              "jobId": "Job_3",
              "type": "delivery",
              "compartment": "B",
              "location": {
                "lat": 52.54179167844302,
                "lng": 13.375259544700388
              },
              "time": {
                "end": "2026-01-16T07:33:26Z",
                "start": "2026-01-16T07:28:26Z",
                "arrival": "2026-01-16T07:28:26Z"
              }
            }
          ],
          "load": [
            2
          ],
          "time": {
            "arrival": "2026-01-16T07:28:26Z",
            "departure": "2026-01-16T07:33:26Z"
          },
          "distance": 7598,
          "loadPerCompartment": [
            {
              "load": [
                2
              ],
              "name": "A"
            },
            {
              "load": [
                0
              ],
              "name": "B"
            }
          ],
          "location": {
            "lat": 52.54179167844302,
            "lng": 13.375259544700388
          }
        },
        {
          "activities": [
            {
              "jobId": "Job_1",
              "type": "delivery",
              "compartment": "A",
              "location": {
                "lat": 52.5259,
                "lng": 13.39031
              },
              "time": {
                "end": "2026-01-16T07:44:11Z",
                "start": "2026-01-16T07:39:11Z",
                "arrival": "2026-01-16T07:39:11Z"
              }
            }
          ],
          "load": [
            0
          ],
          "time": {
            "arrival": "2026-01-16T07:39:11Z",
            "departure": "2026-01-16T07:44:11Z"
          },
          "distance": 9883,
          "loadPerCompartment": [
            {
              "load": [
                0
              ],
              "name": "A"
            },
            {
              "load": [
                0
              ],
              "name": "B"
            }
          ],
          "location": {
            "lat": 52.5259,
            "lng": 13.39031
          }
        },
        {
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival",
              "location": {
                "lat": 52.531,
                "lng": 13.38461
              },
              "time": {
                "end": "2026-01-16T07:47:42Z",
                "start": "2026-01-16T07:47:42Z",
                "arrival": "2026-01-16T07:47:42Z"
              }
            }
          ],
          "load": [
            0
          ],
          "time": {
            "arrival": "2026-01-16T07:47:42Z",
            "departure": "2026-01-16T07:47:42Z"
          },
          "distance": 11249,
          "loadPerCompartment": [
            {
              "load": [
                0
              ],
              "name": "A"
            },
            {
              "load": [
                0
              ],
              "name": "B"
            }
          ],
          "location": {
            "lat": 52.531,
            "lng": 13.38461
          }
        }
      ],
      "typeId": "vehicle_1",
      "vehicleId": "vehicle_1_1"
    }
  ],
  "unassigned": [
    {
      "jobId": "Job_5",
      "reasons": [
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "cannot be assigned due to capacity of vehicle"
        }
      ]
    }
  ]
}

Example: assign single-delivery jobs to separate compartments with mixing restrictions

In this sample scenario, a vehicle has two compartments, A and B, each with a capacity of 200. A mixing restriction, defined at the tour level, prevents both compartments from carrying more than one of the following conflicting categories at a time: Gluten-Free and Regular.

The plan contains two single-delivery jobs:

  • job_1, category Gluten-Free, with a demand of 50
  • job_2, category Regular, with a demand of 60

Because Gluten-Free and Regular are conflicting categories, the optimization algorithm cannot assign both jobs to the same compartment, and instead distributes them across compartments A and B.

The following JSON snippet contains the full problem specification:

Click to expand/collapse the sample JSON
{
  "configuration": {
    "experimentalFeatures": [
      "compartments"
    ]
  },
  "fleet": {
    "types": [
      {
        "profile": "vehicle",
        "amount": 1,
        "costs": {
          "time": 0.05,
          "distance": 0.01,
          "fixed": 100
        },
        "id": "vehicle_1",
        "shifts": [
          {
            "start": {
              "location": {
                "lat": 52.53107,
                "lng": 13.38489
              },
              "time": "2026-07-21T06:00:00Z"
            },
            "end": {
              "location": {
                "lat": 52.53107,
                "lng": 13.38489
              },
              "time": "2026-07-21T20:00:00Z"
            },
            "mixingRestrictions": {
              "level": "tour",
              "restrictions": [
                {
                  "compartments": ["A", "B"],
                  "conflictingCategories": [
                    "Gluten-Free",
                    "Regular"
                  ]
                }
              ]
            }
          }
        ],
        "compartments": [
          {
            "capacity": [
              200
            ],
            "name": "A"
          },
          {
            "capacity": [
              200
            ],
            "name": "B"
          }
        ]
      }
    ],
    "profiles": [
      {
        "name": "vehicle",
        "type": "car"
      }
    ]
  },
  "plan": {
    "jobs": [
      {
        "id": "job_1",
        "category": "Gluten-Free",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.5391496506316,
                    "lng": 13.394946372136475
                  }
                }
              ],
              "demand": [
                50
              ]
            }
          ]
        }
      },
      {
        "id": "job_2",
        "category": "Regular",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.537848867881856,
                    "lng": 13.39589059352875
                  }
                }
              ],
              "demand": [
                60
              ]
            }
          ]
        }
      }
    ]
  }
}

Interpret the solution

The API assigns both jobs to the same tour, but to different compartments, as shown in the following solution:

Click to expand/collapse the sample JSON
  {
      "statistic": {
          "cost": 181.83999999999997,
          "distance": 3039,
          "duration": 1029,
          "times": {
              "break": 0,
              "driving": 429,
              "serving": 600,
              "stopping": 0,
              "waiting": 0
          },
          "costs": {
              "distance": 30.39,
              "fixed": 100,
              "time": 51.45
          },
          "intraStopDistance": 0
      },
      "tours": [
          {
              "shiftIndex": 0,
              "statistic": {
                  "cost": 181.83999999999997,
                  "distance": 3039,
                  "duration": 1029,
                  "times": {
                      "break": 0,
                      "driving": 429,
                      "serving": 600,
                      "stopping": 0,
                      "waiting": 0
                  },
                  "costs": {
                      "distance": 30.39,
                      "fixed": 100,
                      "time": 51.45
                  },
                  "intraStopDistance": 0
              },
              "stops": [
                  {
                      "activities": [
                          {
                              "jobId": "departure",
                              "type": "departure",
                              "location": {
                                  "lat": 52.53107,
                                  "lng": 13.38489
                              },
                              "time": {
                                  "arrival": "2026-07-21T06:00:00Z",
                                  "start": "2026-07-21T06:00:00Z",
                                  "end": "2026-07-21T06:00:00Z"
                              }
                          }
                      ],
                      "load": [
                          110
                      ],
                      "time": {
                          "arrival": "2026-07-21T06:00:00Z",
                          "departure": "2026-07-21T06:00:00Z"
                      },
                      "distance": 0,
                      "loadPerCompartment": [
                          {
                              "load": [
                                  60
                              ],
                              "name": "A"
                          },
                          {
                              "load": [
                                  50
                              ],
                              "name": "B"
                          }
                      ],
                      "location": {
                          "lat": 52.53107,
                          "lng": 13.38489
                      }
                  },
                  {
                      "activities": [
                          {
                              "jobId": "job_1",
                              "type": "delivery",
                              "compartment": "B",
                              "location": {
                                  "lat": 52.5391496506316,
                                  "lng": 13.394946372136475
                              },
                              "time": {
                                  "arrival": "2026-07-21T06:03:39Z",
                                  "start": "2026-07-21T06:03:39Z",
                                  "end": "2026-07-21T06:08:39Z"
                              }
                          }
                      ],
                      "load": [
                          60
                      ],
                      "time": {
                          "arrival": "2026-07-21T06:03:39Z",
                          "departure": "2026-07-21T06:08:39Z"
                      },
                      "distance": 1506,
                      "loadPerCompartment": [
                          {
                              "load": [
                                  60
                              ],
                              "name": "A"
                          },
                          {
                              "load": [
                                  0
                              ],
                              "name": "B"
                          }
                      ],
                      "location": {
                          "lat": 52.5391496506316,
                          "lng": 13.394946372136475
                      }
                  },
                  {
                      "activities": [
                          {
                              "jobId": "job_2",
                              "type": "delivery",
                              "compartment": "A",
                              "location": {
                                  "lat": 52.537848867881856,
                                  "lng": 13.39589059352875
                              },
                              "time": {
                                  "arrival": "2026-07-21T06:09:00Z",
                                  "start": "2026-07-21T06:09:00Z",
                                  "end": "2026-07-21T06:14:00Z"
                              }
                          }
                      ],
                      "load": [
                          0
                      ],
                      "time": {
                          "arrival": "2026-07-21T06:09:00Z",
                          "departure": "2026-07-21T06:14:00Z"
                      },
                      "distance": 1664,
                      "loadPerCompartment": [
                          {
                              "load": [
                                  0
                              ],
                              "name": "A"
                          },
                          {
                              "load": [
                                  0
                              ],
                              "name": "B"
                          }
                      ],
                      "location": {
                          "lat": 52.537848867881856,
                          "lng": 13.39589059352875
                      }
                  },
                  {
                      "activities": [
                          {
                              "jobId": "arrival",
                              "type": "arrival",
                              "location": {
                                  "lat": 52.53107,
                                  "lng": 13.38489
                              },
                              "time": {
                                  "arrival": "2026-07-21T06:17:09Z",
                                  "start": "2026-07-21T06:17:09Z",
                                  "end": "2026-07-21T06:17:09Z"
                              }
                          }
                      ],
                      "load": [
                          0
                      ],
                      "time": {
                          "arrival": "2026-07-21T06:17:09Z",
                          "departure": "2026-07-21T06:17:09Z"
                      },
                      "distance": 3039,
                      "loadPerCompartment": [
                          {
                              "load": [
                                  0
                              ],
                              "name": "A"
                          },
                          {
                              "load": [
                                  0
                              ],
                              "name": "B"
                          }
                      ],
                      "location": {
                          "lat": 52.53107,
                          "lng": 13.38489
                      }
                  }
              ],
              "typeId": "vehicle_1",
              "vehicleId": "vehicle_1_1"
          }
      ]
  }

As the solution shows, the departure stop loads both compartments at once: compartment A carries the load for job_2 (category Regular), and compartment B carries the load for job_1 (category Gluten-Free). Because the mixing restriction applies separately to compartments A and B, the vehicle could not have carried both categories in the same compartment during the tour, so the API planned the delivery of each job from a different compartment.

Compartments with mixing restrictions vs. mixing restrictions alone

Without the compartments feature, mixing restrictions would apply to the entire vehicle, which, in this scenario, requires multiple vehicles (each restricted goods type needs a separate vehicle) as illustrated in the following diagram:

Compartments with mixing restrictions as compared to mixing restrictions alone

This would make the tour inefficient, increasing its total cost, with both vehicles utilizing only a small capacity percentage as compared to a single compartmentalized vehicle.

To illustrate that, the problem from the preceding example was modified to use mixing restrictions alone. In addition, the number of available vehicles increased from one to two:

Click to expand/collapse the sample JSON
{
  "fleet": {
    "types": [
      {
        "profile": "vehicle",
        "amount": 2,
        "capacity": [
          400
        ],
        "costs": {
          "time": 0.05,
          "distance": 0.01,
          "fixed": 100
        },
        "id": "vehicle_1",
        "shifts": [
          {
            "start": {
              "location": {
                "lat": 52.53107,
                "lng": 13.38489
              },
              "time": "2026-07-21T06:00:00Z"
            },
            "end": {
              "location": {
                "lat": 52.53107,
                "lng": 13.38489
              },
              "time": "2026-07-21T20:00:00Z"
            },
            "mixingRestrictions": {
              "level": "tour",
              "restrictions": [
                {
                  "conflictingCategories": [
                    "Gluten-Free",
                    "Regular"
                  ]
                }
              ]
            }
          }
        ]
      }
    ],
    "profiles": [
      {
        "name": "vehicle",
        "type": "car"
      }
    ]
  },
  "plan": {
    "jobs": [
      {
        "id": "job_1",
        "category": "Gluten-Free",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.5391496506316,
                    "lng": 13.394946372136475
                  }
                }
              ],
              "demand": [
                50
              ]
            }
          ]
        }
      },
      {
        "id": "job_2",
        "category": "Regular",
        "tasks": {
          "deliveries": [
            {
              "places": [
                {
                  "duration": 300,
                  "location": {
                    "lat": 52.537848867881856,
                    "lng": 13.39589059352875
                  }
                }
              ],
              "demand": [
                60
              ]
            }
          ]
        }
      }
    ]
  }
}

The following table compares the solutions with and without compartments:

With compartmentsWithout compartments

The solution that does not use compartments requires an additional vehicle to avoid mixing the conflicting categories as compared with the one involving compartments (decreasing the number of available vehicles to one in the mixing restrictions-only case would result in an unassigned job). In addition, not including compartments results in a tour plan with a longer total duration and a significantly increased cost.

Conclusions

  • The compartments property on a vehicle type lets you model a vehicle with multiple, independently sized cargo sections instead of a single shared capacity.
  • Adding a compartments array to a mixingRestrictions entry applies that restriction separately to each listed compartment, so incompatible job categories can still travel on the same vehicle as long as they are kept in different compartments.
  • If a mixingRestrictions entry omits compartments, the restriction continues to apply to the whole vehicle.

Next steps

  • For more information about restricting the whole vehicle instead of individual compartments, see Define mixed load restrictions.
  • For more information about formulating problems in the HERE Tour Planning API, see Problem.
  • For an in-depth exploration of the HERE Tour Planning API methods, endpoints, and parameters, see the API Reference.

Did this page help you?