Skip to main content
V3 is in beta. General availability (GA) is planned for January 2027.

Overview

POST /v3/routing/suggest answers one question: given the plan I already have, where can this new task go, and what does each insertion cost? You send the same body as /v3/routing/evaluate: the problem and the plan you have committed to. Every task of the problem that is not in the plan is a task to place, and for each one you get back a ranked list of feasible insertion positions, best first. Use it when a new order arrives during the day and a dispatcher (or your own code) should pick a slot, rather than letting the solver rearrange routes that are already on the road. The committed plan is never changed: the API holds no state, so nothing happens until you apply an insertion yourself.

The request

The body has two required parts, problem and plan, plus optional options:
object
required
The same problem object as on /v3/routing/solve: jobs, shipments, vehicles, depots, relations, objective, travel. It must contain the tasks already in the plan and the tasks you want to place: every task not in plan is placed.
object
required
The committed plan, in the shape every solve and evaluate response returns as plan: one routes[] entry per vehicle shift, each with vehicle, shift and an ordered stops[] list. Leave the tasks to place out of it; at least one task of problem must be missing from it. A route’s locked_count marks stops that are already dispatched: no insertion lands before them.
integer
default:"5"
Maximum number of ranked insertions per task. 0 returns every feasible placement. Positive values above 100 are lowered to 100.
number
default:"1"
Search budget in seconds, at most 300; fractions are allowed (2.5). Defaults to 1, as on solve. A value below 0.1 is raised to 100 ms. The budget is split across the tasks to place in problem order, each getting an equal share of the time left. The clock starts when the search starts, so fetching travel times does not eat into it.
There is no list of task ids to send: a task you do not want suggestions for must either be in plan or be left out of problem. An old options.tasks field is rejected as unknown_field.

Example

One truck already serves delivery-1 and delivery-2. A third order, delivery-3, comes in and must be served between 08:00 and 11:00 local time.

The response

The figures are illustrative; yours depend on live travel times. Three positions exist on this route and only two come back: inserting delivery-3 after delivery-2 would reach it after its window closes, so that position is not feasible and is not listed. suggestions[] has one entry per task outside plan, in the order the tasks appear in problem (jobs first, then shipments). Tasks tied together by a relation share one entry: see Shipments and related tasks.
string
The task this entry is about.
array
Feasible insertions, lowest delta.cost first. Empty when none was found; reason_code then says why.
string
Present only when insertions is empty: time_limit, candidates_exhausted or infeasible. See When a task gets no insertions. New codes may be added; handle a code you do not know generically.
string
Present exactly when reason_code is: the same fact as an English sentence, for people and logs.
boolean
One flag for the whole response. true when options.time_limit_s cut the search short for any task, or when an empty list for a multi-task unit is inconclusive. The lists may then be incomplete or miss the best insertion.
integer
Wall-clock time of the whole request, in milliseconds.

When a task gets no insertions

An empty insertions list is a 200, not an error. The entry’s reason_code says why, and reason says the same in a sentence: In code, branch on reason_code, never on reason. Only infeasible is final; the other two can change with a larger budget or search. New codes may be added: treat one you do not know as not final and show reason. truncated covers the whole response: a task whose list is not empty can also have been cut short, and carries no reason_code. Send one task per request when you need to know which task was cut short.

Reading an insertion

An insertion says what it changes (delta), which stops it adds (placements) and how every changed route reads with it in (routes). Insertions are ordered by delta.cost, lowest first. Each placement says who serves the new stop and when, which is what a booking screen shows:
delta.cost and delta.duration_s measure different things. In the example the first insertion adds more driving than the second, yet costs less: the truck used to wait at delivery-1 for the window to open, and the detour fits inside that wait, so the route does not end any later. See Objective function for what the cost is made of.

Applying a suggestion

Suggest only reports insertions. To act on one, swap its routes into your plan:
1

Swap in the routes

For each entry of the insertion’s routes, replace the plan.routes[] entry with the same vehicle and shift, or add it when plan has none. To use one of interchangeable_vehicles instead, first put its vehicle and shift in the insertion’s route. For the first insertion above, plan becomes:
2

Confirm or re-optimise

Send the edited plan to /v3/routing/evaluate to get the full timeline and cost of the new plan, or to /v3/routing/solve as a warm start if the solver may also rearrange the other stops.
Each task is ranked on its own against the same committed plan. If two tasks are outside the plan and you apply an insertion for the first, the insertions for the second were computed without it: call suggest again with the updated plan.
  • Shipments. Leave both legs of the shipment out of plan. Its pickup and delivery are placed together, on one route: the insertion’s placements[] lists both legs in route order, each with its type of pickup or delivery and its leg’s stop id. A shipment with only one leg in plan is rejected by the plan import, as on evaluate.
  • sequence, same_vehicle and same_day relations. Tasks tied together by one of these relations are placed as one unit and get one entry, named after the first new task of the unit.
  • synchronized and time_lag relations. A task outside plan that takes part in one of these cannot be suggested, and one such task makes the whole request a 400, not just its own entry. Put the task in plan, or leave the relation out, to get suggestions for the other tasks.
  • Reloads. An insertion can land in any existing trip of a multi-trip route, but suggest never adds a new reload trip.

Errors

Suggest returns the same error body as the other endpoints. The cases specific to it: ?validate_only=true checks problem, options, and that plan leaves at least one task out, without fetching travel times or searching. It does not check that plan fits the problem; only the real call does.

Evaluate

Score the plan after you apply an insertion.

Errors

The error body, every code, and what to do about each.

The V3 API model

The problem and plan objects in full.

API reference

Request and response schema for every endpoint.