The scenario
Two vans, each starting and ending its day at a different depot, deliver to four stops with client-facing delivery windows.Step 1 — start minimal: resources and orders only
Get a feasible plan running before adding any constraints. Each resource needs at minimum anid, a vehicleProfile, and a workingTimeWindow; each order needs an id and at least one stop with a position.
PUT .../agencies/{agencyId}/plans/{planId}) and confirm two tours come back in the solution, together covering all four stops. Positions here are illustrative — see Data model for why real positions must be pre-geocoded before you submit them.
Step 2 — add depots as departure/arrival
The plan above already has each van start and end at a depot position rather than at the first/last stop — this is what makes the two-depot scenario realistic. If departure/arrival are omitted, the working day starts and ends at whichever stop the engine happens to assign first/last, which is rarely what a dispatcher expects for a fleet with fixed depots.
Step 3 — add delivery time windows, and choose hard vs soft deliberately
Now add a delivery window to each stop. This is the step where it’s easy to get the modeling choice wrong: a client-facing or “contractual” window is not automatically a hard constraint. Ask what should happen if the fleet can’t hit the window exactly:- If a late (or early) visit is still worth making — the customer would rather get a delayed delivery than none — use
preferredTimeWindows. Missing it costsdelay, tracked by theminimizeDelayobjective, but the stop still gets served. - Only use
authorizedTimeWindowsif a visit outside the window genuinely can’t happen (site closed, access refused). See Hard vs soft constraints for the full reasoning — the short version is: default topreferredTimeWindowsfor contractual windows unless you’ve explicitly confirmed otherwise with the business.
"minimizeDelay" to your objectives list (it’s already in the recommended default — see How the optimization engine works) so the engine actually optimizes against these windows rather than treating them as decoration.
Step 4 — read the result
Fetch the solution the same way as in First API call and check, per tour:tours[].wayPoints— the assigned stops, in visiting sequence, each with a computedarrivalTime.tours[].isValid— whether that specific tour respects all hard constraints; check this per-tour, not just at the plan level.unaffectedStopIds— any stop that couldn’t be placed at all. With everything modeled aspreferredTimeWindowsabove, this should be empty; if you’d usedauthorizedTimeWindowsinstead and a window were unreachable, the stop would show up here instead of arriving late.tours[].distanceInKmandtours[].workingDuration, useful for a sanity check against what you’d expect for the geography.
unaffectedStopIds, see Handling infeasibility for how to diagnose which constraint caused it.
Common pitfalls at this stage
- Units. Distances in the API are kilometers, not miles; durations are ISO 8601 (
PT30M, not30); make sure any values sourced from a spreadsheet are converted before submission. - Time zones. Datetimes without an explicit UTC offset are ambiguous — either include the offset on every datetime, or set the plan-level
tzfield once (e.g."Europe/Paris") and write local, offset-free datetimes. Mixing both styles in the same plan is a common source of off-by-a-few-hours bugs. - Positions.
positionfields must already be geocoded latitude/longitude pairs — the API does not geocode addresses for you. See Data model for what to use instead. - Hard vs soft defaults. As shown in Step 3, don’t default a contractual time window to
authorizedTimeWindowsjust because it’s contractual — that choice silently drops stops rather than delivering them late.
See also
- Data model — full field dictionary for everything used above.
- Hard vs soft constraints — the general reasoning behind the Step 3 choice.
- Handling infeasibility — what to do when a stop doesn’t get planned.