Heterogeneous capacities per vehicle
capacities is a free-form map: any key you define (weight, volume, nbPackages, a custom unit) is tracked independently by the engine, on both resources and stops. A resource’s capacity is a hard ceiling — the cumulative load of its assigned stops can never exceed it at any point in the tour.
Multi-compartment vehicles
A vehicle can have a compartment configurable in different ways before departure — for example a reefer compartment split between chilled and frozen, or run fully chilled instead — with the setup fixed for the whole tour, no reconfiguration mid-route. Model each candidate setup as its owncapacities-scoped constraint, and combine the candidates with atLeastOneConstraint:
Each
capacities-type constraint here is an AND across its own keys, enforced throughout the tour — the same as a resource’s base capacities, just scoped to a resourceTag. atLeastOneConstraint then requires only one of the two full setups to hold, letting the engine pick whichever configuration fits the orders actually assigned to that resource. AdditionalConstraintCapacities doesn’t report its own named violation and isn’t meant to be used on its own at the top level of additionalConstraints — only as a member of atLeastOneConstraint, as shown here.Sequencing pickups after deliveries
atLeastOneValidCapacity is a different constraint from the atLeastOneConstraint used for multi-compartment vehicles in the previous section. It lists capacity thresholds and watches the resource’s running load for each of them over its tour: at every stop, at least one of the listed capacities must be at or below its threshold. Because this OR is re-checked at every stop, it can express a sequencing rule, not just a feasibility check.
A typical case: a crew delivering bulky items, such as sofas, also collects old ones at some customers. The load area works last in, first out, so an old item collected early would block the items still to be delivered, and the crew would have to unload and reload it at every following delivery. The rule to express is: only collect once every delivery is done, or once no more than 300 kg is left on board.
Add two counting capacities alongside weight: every delivery stop carries nbDeliveries: 1, and every pickup stop carries nbPickups: 1. Declare both on the resource too, with ceilings that never bind. Since a delivery stop deducts its capacities from the load and a pickup stop adds them, the resource leaves with nbDeliveries equal to its number of deliveries, nbPickups at 0, and weight equal to the total weight to deliver. Over the tour, nbDeliveries falls to 0, nbPickups rises by one with each pickup, and weight follows the actual cargo:
nbDeliveries at or below 0), no pickup made yet (nbPickups at or below 0), or no more than 300 kg on board (weight at or below 300). While no pickup has happened, the nbPickups condition holds on its own, so deliveries can run in any order at the start of the tour. A pickup always raises nbPickups to at least 1, and it never goes back to 0, so from that stop to the end of the tour the resource must either have finished its deliveries or stay at or under 300 kg — otherwise that tour is infeasible. See the toolkit fetch example further down for the same OR-at-every-stop mechanism traced step by step.
This models sequencing through ordinary
capacities, not through AlternativesStop — no gating stop is introduced, the ordering falls directly out of how nbDeliveries and nbPickups move over the tour. To forbid pickups while any delivery remains, whatever the weight, drop the weight threshold: { "nbDeliveries": 0, "nbPickups": 0 }.Tolerated capacity overflow
By default, exceeding a capacity makes a stop unplannable rather than degrading gracefully. If the business would rather accept an occasional overload than drop a stop, set the resource’s hardcapacities ceiling at the absolute physical or legal limit, and add a cost.costsByCapacity entry that starts charging past the load you normally plan to:
weight handled at the tour’s stops, not on the vehicle’s load (see Piecewise-linear cost functions). On a delivery-only tour, that total is the load at departure, so the thresholds read as a load. Up to 800 kg the load costs nothing. Between 800 and 900 kg, each extra kilogram costs 1. Between 900 and 1,000 kg, the steeper overcostCoeff takes over at 50 per kilogram. Beyond 1,000 kg, the hard ceiling still makes the stop unplannable. With minimizeCosts in objectives, this makes overflow economically unattractive — the engine avoids it whenever another solution exists — without declaring a stop infeasible until the real limit is reached. Keep overcostFloor strictly below the capacities ceiling: a floor equal to or higher than the ceiling can never be reached, so its overcostCoeff never applies.
Creative capacity modeling patterns
Capacities don’t have to track anything physical. Two patterns reuse the same mechanism for a different purpose:- Bounding a number of visits. Declare a synthetic capacity that has nothing to do with load, and increment it once per relevant stop, to cap how many such stops a single tour can include — for example, at most 3 visits to a restricted zone per tour:
- Overlapping capacity dimensions. Two capacity keys can be declared independently on the same stops, for example a global
weightalongside adangerousGoodsWeightsubset of it, each enforced on its own ceiling without one having to derive from the other.
capacities (see Data model), just detached from any real-world unit.
LIFO unloading order for stacked cargo
removalStrategy: "lifo" enforces a physical loading order: pickups made in the order P1 → P2 → P3 must then be unloaded in the reverse order D3 → D2 → D1 — the last thing loaded is the first thing unloaded.
removalStrategy among the other structural hard constraints.
Driver skills and qualifications
requiredSkills on an order and skills on a resource are the hard match: a resource can only be assigned an order if it has every skill the order requires (a certification, an equipment qualification, and so on).
preferredStopTags on the resource with maximizePreferredStops in the objectives, instead of requiredSkills:
Mandatory breaks
A resource’sbreaks array accepts three break types, and they aren’t mutually exclusive — combine them to model a realistic shift:
PT45M/PT4H30M is safe to apply outright once you’ve established the jurisdiction (see §4 of the agent modeling checklist). The working-time figure is typically set by a sector- or company-level collective agreement layered on top of a lower statutory floor, so a single number here would be wrong for many employers — don’t copy a duration from this table for workingDurationSlidingBreak; treat it as an open question for client confirmation instead.
Both sliding breaks count the same break toward their respective counters — the engine looks for a single moment that satisfies both regulations rather than scheduling them separately. For long shifts, define both the driving-time and the working-time rule together; a single sliding break only covers one of the two regulatory clocks.
Sharing a single break between two counters isn’t limited to the two sliding break types — any two breaks that overlap in time count once toward both, for example a timeWindowBreak that coincides with a workingDurationSlidingBreak’s window.
Modeling multi-day sleepovers
AworkingTimeWindow spanning more than one calendar day doesn’t by itself force any rest between working days (see the Modelling pitfall on workingTimeWindow in the API reference) — encode the rest explicitly in breaks:
workingDurationSlidingBreak (here PT11H, illustrative — confirm the applicable convention or regulation before publishing, same caveat as the legal-break figures earlier on this page) forces a full night’s rest; the optional timeWindowBreak further constrains the window that rest has to fall into, for example to prevent a restart before a given hour the next day. Extend workingTimeWindow further for more than two days.
Multiple time windows per stop
authorizedTimeWindows accepts an array, so a stop can have more than one hard, disjoint window — for example, a site open in the morning and again in the late afternoon, closed in between:
preferredTimeWindows layers a soft target on top (see Hard vs soft constraints); it’s always intersected with the authorized windows, so it narrows the target without ever widening what’s actually reachable.
begin/end bound the resource’s arrival at the stop, not when it finishes — see the Modelling pitfall on authorizedTimeWindows in the API reference for how to account for operationDuration when a strict finish-by deadline matters.
Restricting a window to specific resources
A window can also apply only to resources carrying a given tag — for instance, an early access slot reserved for a certified subcontractor, while other resources only see the standard window:Optional steps that gate a mandatory stop
Not every optional step needs its ownAlternativesStop. Only reach for it when skipping the step would make a downstream mandatory stop infeasible — for example an order that needs to collect a spare part or piece of equipment before a mandatory service visit, but only when the resource isn’t already carrying it. If a step is either always required or never affects feasibility, a plain SingleStop (or no extra stop at all) is enough; AlternativesStop exists for the case where whether it’s needed depends on context the plan itself has to resolve, not on something you can decide upfront.
This is a per-order decision, applied unconditionally to every order with this dependency — it is not an aggregate feasibility check on whether enough of the item exists across the fleet. Whether this order’s assigned resource already happens to be carrying the item, on this specific tour, is something the engine resolves per instance; it isn’t something you can decide upfront from a stock count. A fleet-wide total that looks “sufficient” says nothing about whether any single resource, on any single tour, already has the unit it needs at the point it’s needed — so don’t skip modeling the AlternativesStop for an order just because some aggregate check elsewhere says the item isn’t scarce. That aggregate question — “can this load physically fit at all, across the fleet” — is a separate, complementary mechanism: see §2 of the agent modeling checklist and atLeastOneConstraint in the constraints catalog. Don’t conflate the two: an aggregate feasibility check answers “is there enough overall”; AlternativesStop answers “does this resource need a detour, right now, on this tour” — and the two questions can have opposite answers on the same plan.
Full worked example: toolkit fetch gating a mandatory visit
Full worked example: toolkit fetch gating a mandatory visit
Model the gated step as an
AlternativesStop with two candidates — a real “fetch” stop, and a zero-effect placeholder — as the first stop of the order, immediately followed by the mandatory stop it conditions as the second stop of that same order. An AlternativesStop is not optional in itself: whenever its order is planned, the engine serves exactly one of its alternatives, and it serves none only if the whole order is left unplanned. The zero-effect placeholder is what makes the detour optional: the engine picks it instead of the fetch whenever the fetch isn’t necessary. The item being fetched is typically an item a resource may already be carrying: a pooled resource drawn down by the mandatory stop and topped back up by the optional fetch immediately before it, both within the same order. Because both stops belong to the same order, the resource’s running capacity balance already carries from one to the next by default — no pooling flag is needed for this. sharedCapacities (see Shared capacity pools) is a different, additional mechanism: it’s for when the pool must be shared across separate orders on the same resource, which isn’t what this pattern needs.order-1’s mandatory-visit — listed second, right after the gate — needs one unit already on board to be feasible, and that unit is supplied by the same order’s fetch-at-supply-point, listed first. Array position is what makes “earlier in the tour” mean anything here — no separate order and no sharedCapacities flag required, since both stops already belong to one order on one resource. The engine picks fetch-at-supply-point — paying its extra travel and operationDuration — only when nothing earlier in the tour already put a unit into the pool; otherwise it picks the zero-effect no-detour-needed placeholder, whose position matches the very next stop so it adds no extra travel at all. This choice is made independently for every order with this dependency, from the actual, resource-specific state of the pool at that point in that tour — never from a fleet-wide count of how many units exist in total. Both alternatives are still mandatory to evaluate — the choice itself isn’t optional, only its real-world outcome (a detour, or none) is.Trace the numbers to see why each branch lands where it does. fetch-at-supply-point is a pickup stop with a negative capacities value, { "toolkit-available": -1 }: a pickup adds its capacities value to the running total, so adding -1 nets to a decrease — the negative value reverses the pickup’s usual effect (see the note on kind and the sign of capacities under Order, Stop). mandatory-visit never lists toolkit-available at all, so nothing else in the order ever touches it.- If
fetch-at-supply-pointis picked: check, at every stop, whethertoolkit-placedis at or below0ortoolkit-availableis at or below-1. Before the fetch,toolkit-placedis still0, so the first branch holds. Right after the fetch,toolkit-availabledrops to-1, so the second branch also holds, andtoolkit-placedis still0untilmandatory-visitruns, so the first branch keeps holding there too. Oncemandatory-visitruns,toolkit-placedbecomes1(first branch now false), buttoolkit-availableis still-1from the earlier fetch (second branch still true). At every stop, at least one branch holds, soatLeastOneValidCapacityis satisfied throughout and the plan is feasible. - If
no-detour-neededis picked instead:toolkit-availablenever moves off0— nothing in this branch ever writes to it — so it’s never at or below-1, and the second branch is false at every stop. Beforemandatory-visit,toolkit-placedis still0, so the first branch holds and the constraint is fine so far. But oncemandatory-visitruns,toolkit-placedbecomes1: now neither branch holds at that stop (1isn’t at or below0, andtoolkit-availablestill isn’t at or below-1).atLeastOneValidCapacityis violated at that stop, and the plan is correctly rejected as infeasible.
mandatory-visit is fully ceiling-compliant (toolkit-available never exceeds 1; toolkit-placed never exceeds 1) — an “otherwise valid-looking” plan by the base capacity check alone — but it’s rejected anyway, because it produces a stop, right after mandatory-visit, where neither branch of the OR holds. A reader can re-run this trace against the preceding JSON snippet to confirm it.Notice the two decoupled capacity keys, toolkit-available (whether the fetch has already happened, tracked as a signed credit) and toolkit-placed (consumed by the mandatory stop), rather than a single toolkit key shared by both stops. mandatory-visit never lists toolkit-available, and fetch-at-supply-point never lists toolkit-placed — the two keys stay fully independent capacities, each still validated and reportable on its own terms. A single net key would collapse two different questions the solver needs to answer separately — “how much has this order already placed” and “how much is currently available to it” — into one number that only reports their difference, hiding which side is actually short whenever the pattern needs to be checked or reported on independently. It’s the plan-level atLeastOneValidCapacity additional constraint — not a shared running total — that ties the two independent keys together into the “fetch before place” rule.AlternativesStop increases optimization time — see Sizing maxOptimizationDuration for a large problem. Reserve it for steps whose necessity genuinely depends on the rest of the plan; don’t reach for it just to express “this step is optional” when a plain optional: true order, or leaving the step out entirely, would do.See also
- Hard vs soft constraints — the general hard/soft distinction referenced throughout this page.
- Handling infeasibility — what happens when none of the preceding can be satisfied for a given stop.
- Data model — an overview of
capacities,skills,breaks, time windows, and stops, each linking to its schema in the API reference.

