Skip to main content
Not every fleet can serve its full daily demand in a single loop. When total demand across a wave of orders exceeds the fleet’s total capacity, resources need to return to the depot, reload, and go out again — potentially several times — inside their workingTimeWindow. Nothing about this requires a dedicated field: it’s a modeling pattern built from capacities and stop kind, the same primitives covered in the data model.
Before modeling multi-trip tours, check whether you actually need them, using the exact test from the agent modeling checklist (§2, “Check capacity feasibility before modeling orders”): sum your stops’ capacities per dimension and compare against your fleet’s total capacity in a single loop. That test is a strict binary, not a margin call — total demand either fits within total fleet capacity (single-loop, no reload needed) or exceeds it (single-loop is infeasible, not merely suboptimal; the shortfall will show up as unplanned stops, not an error). This page assumes that check already came out on the “exceeds” side. It isn’t a separate, softer threshold of its own, and a ratio close to but under 1 doesn’t independently justify reaching for this pattern — per the checklist, don’t resolve a capacity shortfall by defaulting to multi-trip on your own; surface it as an open question, and only model this pattern once the client’s own data or context confirms the fleet genuinely reloads at a depot mid-shift.

The core mechanic: pair each delivery with its own depot pickup

Rather than pre-computing a fixed number of “reload rounds” per vehicle, pair every delivery with a pickup of the same cargo at the depot, inside the same order:
Since an order’s stops all run on the same resource, in array order, and the pickup adds exactly what the delivery then removes, each pair is capacity-neutral by construction — it never touches the resource’s baseline load. That has two consequences that make it a better default than sizing a handful of large reload stops to a resource’s full capacity:
  • No fixed round count. The engine is free to chain any number of these pairs on a single resource, going back to the depot as many times as the schedule and capacity allow — you don’t need to guess in advance how many rounds each vehicle will need, or leave unused "optional": true reload orders on the table.
  • No per-vehicle sizing. A pickup sized to one delivery’s own weight/piles fits any resource with that much spare capacity, heterogeneous fleet or not. There’s no need for a skills / requiredSkills pair to reserve a reload for a specific vehicle — whichever resource ends up serving the delivery automatically picks up its own matching cargo first, because both stops belong to the same order.
If you also want to force or bias which stops happen before versus after each other (for example, a fixed morning sector followed by a fixed afternoon sector), combine this with tagged phases and the maximizePrecedences objective. That’s a separate concern from the capacity mechanic above.

Charging depot time once per visit, not once per pickup

With one pickup stop per delivery, a vehicle loading five deliveries’ worth of cargo before a round would otherwise pay operationDuration five times over for what is physically a single dock visit. Keep each pickup’s operationDuration negligible (as in the example above) and instead charge the real access time once per visit with the plan-level accessDurationsByStopTag field:
This adds the duration once, before the first of a run of consecutive stops sharing the tag — matching a single loading operation at the dock, regardless of how many individual pickups happen during that visit. See Data model for the full field.

Limiting simultaneous depot visits

A physical depot usually has a limited number of loading docks or bays, and can’t serve every vehicle at once. Tag every pickup stop with a shared depot tag (as in the example above) and cap simultaneous presence with overlappingCapacitiesByStopTag at the plan level:
This caps the number of resources simultaneously present at any stop tagged depot:main to 2 — matching, for example, a depot with two loading docks. Because every load in this pattern is an explicit tagged stop that’s part of an order — including the first load of the day, not just later reloads — the limit applies uniformly from the very first pickup, with nothing left uncovered. departure and arrival (see Depot vs. position) are only used for the resource’s idle start/end-of-day position; they carry no cargo and no tag, so keep the actual loading out of them entirely.
Without a fairness objective, the engine can load one or two resources up to their limit on repeated depot rounds while others in the fleet sit comparatively idle — all while still satisfying maximizeMandatoryStops and the delay/cost objectives ahead of it. Include minimizeLargestTourDuration (see How the optimization engine works) to cap how unbalanced the longest single tour can get relative to the rest of the fleet — it’s placed after minimizeResources above so fleet size is still minimized first, but tour lengths are then balanced across whatever fleet size that settles on.
If resources are arriving back at the depot and then waiting idle for their next reload window, consider setting the plan-level lateDeparture: true (see Plan-level fields) so the engine compacts departure timing to reduce that idle wait, instead of always having resources leave as early as possible.

Swap-body and container-exchange orders

A different, unrelated vertical also produces multi-stop orders on a single resource: fleets where the vehicle carries exactly one exchangeable unit at a time — a container, a skip (“benne”), a tank — and each trip’s job is to exchange whatever unit is already at a site for the next one, then haul the unit away to its destination (a disposal site, a treatment plant, a depot). Roll-off/skip trucks and tanker/cistern-swap vehicles are the two most common examples. This is a distinct pattern from the depot-reload pattern above — it isn’t about a vehicle running low on capacity and returning to top up, it’s about a vehicle that only ever carries one unit and needs to swap it out at (or near) the point of use.

Two stop shapes, decided by what actually happens at the site

Whether a given exchange needs two stops or three is a question about the data, not a stylistic default to pick between:
  • Three stops, when an exchange happens at the origin. The vehicle already has a unit to drop off before it can take on the next one: a delivery stop drops the returning/empty unit, immediately followed by a pickup stop that takes on the next unit — both at the same position — and then a third stop, at a different position, delivers/hauls the newly picked-up unit to its destination.
  • Two stops, when no exchange happens at the origin. If the vehicle already has nothing to drop off at the pickup site (for example it starts the trip already running with an empty unit loaded, or the site has nothing to collect first), the order is a plain pickup at the origin followed by a delivery elsewhere — the same shape used elsewhere in this guide and in Data model.
Don’t standardize on one shape across a fleet or a dataset. Each order should reflect what its own site actually requires: if the source data (or the client) confirms a real drop-off-then-pickup happens at that origin, model three stops; if nothing is dropped off there, two stops is the correct — not a simplified — model.

Preventing double-booking: three legitimate mechanisms

A vehicle that carries only one unit at a time must never be assigned a second pickup before the current unit is off-loaded. There are three legitimate ways to enforce that, and they are not mutually exclusive — a real payload may use one, several, or all together, depending on what else the data needs to express:
  1. successiveStops alone. Set successiveStops: true on the order (see Order, Stop) with no tags, no setupDurations, and no capacity signal at all. Array order plus successiveStops is already enough to guarantee the drop-off and the pickup happen back-to-back, in that order, with nothing else interleaved — which is all that’s needed to prevent double-booking when the exchange itself has no real handling-time cost worth modeling and there’s no capacity dimension the exchange needs to express. This is often the simplest correct option, and in practice the one actually used, when neither of the other two mechanisms’ extra machinery corresponds to anything real in the data — don’t reach for tags + setupDurations by default on every swap-body order just because the pattern involves an exchange; reserve it for exchanges that genuinely carry their own timing cost (see the next option).
  2. Tags + setupDurations, combined with successiveStops. Tag the relevant stops with something that captures the vehicle’s state (for example a tag meaning “carrying a unit” versus “empty”), and use the plan-level setupDurations array to charge a duration whenever a resource transitions from a stop tagged one way to a stop tagged another way — see the field’s own definition in the OpenAPI reference (SetupDuration: fromStopTag, toStopTag, setupDuration). Set successiveStops: true on the order so nothing from another order can be interleaved between the drop-off and the pickup. Together, these charge a realistic handling/swap duration for the exchange itself and guarantee the two visits happen back-to-back, in that order.
  3. Signed capacities pairs. Track the unit with two capacity dimensions that move in lockstep and opposite directions — one incrementing, one decrementing at each step (for example a stop that changes capacities by {"unit": 1, "missingUnit": -1}, and the matching drop-off stop by the inverse). The running capacity balance itself then prevents a second pickup from happening before the current unit is dropped off, because doing so would push a dimension out of range. This is the same family of mechanism as shared capacity pools, applied within a single order rather than across several.
A bare count-based ceiling on its own — for example capacities: {"units": 1} on the resource and on every stop that adds or removes a unit, with no successiveStops, no tags/setupDurations, and no signed pair — is not, by itself, a correct model of this pattern once a real drop-off-then-pickup step exists at the same site. A capacity ceiling only bounds how many units are aboard at any point; it doesn’t add the exchange’s own handling time, and nothing about it forces the drop-off and the pickup to happen back-to-back without another order’s stop landing in between. Use it only when the trip is genuinely the simpler two-stop shape above (no exchange at the origin) — and even then, prefer whichever of the three mechanisms above the client’s data actually calls for once an exchange is involved.

Example: tags + setupDurations for a three-stop exchange

The PT20M figure here is illustrative, not a universal constant — it stands in for whatever real handling/administrative time a given client’s exchange actually takes, which you should confirm against their own operation rather than assume. Because the drop-off and pickup stops share the same position, the resource’s travel time between them is zero; the setupDurations entry is what charges the exchange’s own realistic duration on top of that, and successiveStops: true guarantees nothing else gets scheduled in between. The final haul stop keeps the unit:loaded tag since the resource is still carrying the unit it just picked up — no further transition (and so no further setup duration) is charged until it next transitions to a differently-tagged stop. Option 2 (signed capacities pairs) doesn’t need a new worked example here — it follows the same Capacities mechanics already documented, just applied with two dimensions per unit instead of one.

See also

  • Data modelcapacities, stop kind, accessDurationsByStopTag, and the rest of the constraints catalog referenced above.
  • Modeling advanced constraints — heterogeneous capacities, skills, breaks, and multiple time windows.
  • Handling infeasibility — diagnosing a plan where demand still doesn’t fit even with multiple rounds.