The data model covers the basic shape of a plan. This guide goes one level deeper, into five constructs that come up as soon as a fleet or a business isn’t fully uniform: vehicles that aren’t interchangeable, drivers with different qualifications, regulatory breaks, stops with more than one valid visiting window, and optional steps that only matter if they gate a mandatory stop downstream. Each section shows the hard version first, then the soft equivalent where one exists — see Hard vs soft constraints for the general distinction.
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 with several physically separate compartments (ambient / chilled / frozen, for example) doesn’t need every compartment to be full at once — it needs at least one valid combination of loads to fit. Model each compartment as its own capacity key, and use atLeastOneValidCapacity to check feasibility across the whole set rather than requiring all of them simultaneously:
atLeastOneValidCapacity checks that a valid state exists somewhere in the tour sequence — it doesn’t pin which compartment holds which stop. Use it for feasibility (“can this load physically fit”), not for sequencing which stop type comes first (that’s a separate use of the same mechanism, not covered here).
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, give the resource a cost.costsByCapacity with a steep overcostCoeff above a target costFloor instead of a hard capacity ceiling:
This makes overflow economically unattractive — the engine avoids it whenever another solution exists — without ever declaring the stop infeasible on capacity grounds alone.
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).
When the match should be a preference rather than a requirement — a senior technician should handle a demanding job, but a generalist can still cover it if needed — tag the stop and use preferredStopTags on the resource with maximizePreferredStops in the objectives, instead of requiredSkills:
The engine will assign the tagged stop to a matching resource when it can do so without hurting higher-priority objectives, but will fall back to any capable resource rather than leave the stop unplanned.
Mandatory breaks
A resource’s breaks array accepts three break types, and they aren’t mutually exclusive — combine them to model a realistic shift:
The two rows above aren’t interchangeable in how confidently you can default them: the driving-time figure is set by public statute independent of any single employer, so PT45M/PT4H30M is safe to apply outright once you’ve established the jurisdiction (see 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.
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:
The engine treats these as independent options — the stop is feasible if it can be reached in any one of them. 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.
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:
A resource without the matching tag simply doesn’t have the tagged window available to it — it’s constrained to whichever windows apply to everyone.
Optional steps that gate a mandatory stop
Not every optional step needs its own AlternativesStop. 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 Multi-compartment vehicles above and atLeastOneValidCapacity 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.
The gate and the mandatory stop it conditions are two stops of the same Order — not two separate Orders. Put the AlternativesStop gate first in that order’s stops array and the mandatory stop right after it: array position doubles as precedence here, same as everywhere else in the API (see Order, Stop). Splitting them into two Orders is the single most common way to get this pattern wrong — it looks reasonable, validates against the schema, and still produces a materially different plan, because a solver-assigned mandatory stop can then land on a different resource, or a different position in the tour, than the gate that was supposed to precede it.
Model the gated step as an AlternativesStop with two candidates — a real “fetch” stop, and a zero-effect placeholder the engine can pick instead whenever the fetch isn’t necessary — as the first stop of the order, immediately followed by the mandatory stop it conditions as the second stop of that same order. 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.
Don’t set an atLeastOneValidCapacity threshold equal to the resource’s own ceiling for that key — that’s the identical trap called out in Shared capacity pools. A resource’s base capacities already forbid exceeding the ceiling at every point in the tour (a universal check); atLeastOneValidCapacity only asks whether the listed capacities are at or below the given thresholds at some point (an existential check). If the threshold equals the ceiling, the existential check is automatically satisfied wherever the universal one already is, and the additional constraint adds nothing. Here the threshold on toolkit-available has to sit strictly below the resource’s ceiling of 1 — this example uses -1, not 1 — precisely so that a plan which never fetches can never reach it. toolkit-placed’s threshold is tightened to 0 for the same reason, even though it isn’t the one doing the discriminating below.
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 sign of the value carries the real effect here, not kind (see the note on kind vs. 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-point is picked: toolkit-available goes from 0 to -1 right after the fetch, and stays at -1 through mandatory-visit (which doesn’t touch it). At that state, toolkit-available (-1) is at or below the constraint’s -1 threshold, and toolkit-placed (still 0, since mandatory-visit hasn’t run yet) is at or below its 0 threshold — both listed capacities are satisfied together, so atLeastOneValidCapacity is satisfied and the plan is feasible.
- If
no-detour-needed is picked instead: toolkit-available never moves off 0 — nothing in this branch ever writes to it. 0 is well within the resource’s ceiling of 1, so the base capacity check has nothing to object to. But 0 is never at or below -1, at any point in the tour, in this branch — mandatory-visit running afterward only changes toolkit-placed, not toolkit-available. So atLeastOneValidCapacity is never satisfied, and the plan is correctly rejected as infeasible.
That’s the concrete sequencing this constraint rules out: skipping the fetch and still performing 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 can never produce the -1 state the tightened constraint demands. A reader can re-run this trace against the JSON above 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 above can be satisfied for a given stop.
- Data model — full field reference for
capacities, skills, breaks, time windows, and stops.