/v3/routing/solve, /v3/routing/evaluate and /v3/routing/suggest that you can see from
a client, newest first. To hear about new releases, subscribe with the RSS button next to the title.
How to read this page
- Each entry is a release of the routing service, labelled with its date and version. Releases shipped on the same day share an entry.
- Breaking items need a change in your client, and they say what to change. New items are opt-in. Fixed items correct a defect. Improved items change the plans the solver finds, but not the contract.
- Field names are today’s names. If your integration predates a rename, find the old name in Renamed and removed fields.
- The page leaves out internal changes with no effect on a request or response. Changes to the
V2 API (
/v2/vrp/...) are in the VRP API changelog.
?validate_only=true. The response lists every problem at once (see Errors).
Renamed and removed fields
If you built against an early beta, this table maps every renamed or removed name to today’s. Old request names are rejected with a400 whose error item is unknown_field (or invalid_type for an old
enum value or shape, such as a bare-string skill), and the pointer shows where.
Request
Response
Releases
Breaking
- An unknown query parameter is now a
400 invalid_requestwith anunknown_fielditem, and avalidate_onlythat is not a boolean is a400with aninvalid_typeitem. Before, unknown parameters were ignored. Check for misspelled parameters, such asvalidateOnly. The error item has nopointer, because the parameter is not in the body;messagenames it.
- Async runs:
POST /v3/routing/runstakes the body of/v3/routing/solve, answers202at once with a runid, and solves in the background with anoptions.time_limit_sof up to3600(solve:300). PollGET /v3/routing/runs/{id}untilstatusissucceeded,failedorcancelled, fetch the plan fromGET /v3/routing/runs/{id}/result, or stop a run withPOST /v3/routing/runs/{id}/cancel. A body is validated before the run is accepted, so an invalid request is a400at submit, never afailedrun.callbackis not supported yet. See Async runs. - TypeScript client
@solvice/routing(beta), typed from the OpenAPI document:npm install @solvice/routing@beta. It types every request and response and raises error responses as exceptions. See @solvice/routing on npm. Python has no V3 client yet. sequence,same_vehicle,same_dayandsame_routecan name a shipment’s legs by their stop ids,pickup.idanddelivery.id(by default"{shipment}:pickup"and"{shipment}:delivery").
- Shipments are now offered routes that are full early on but have room after their peak load. The search used to skip those routes, so it served fewer shipments.
- With
objective.per_late_hourset, a reordering within a route is now kept only when it lowers the route’s cost. Before, a move that saved a few metres could add priced lateness.
Improved
synchronizedandtime_lagchains: when an insertion pushes members of other chains out of their bounds, the solver now unassigns only the members that conflict, rather than rejecting the whole candidate. On problems with many chains that span shifts, far fewer tasks are left unassigned.
Most of the renames in the table above shipped in these two releases. The
largest changes:Breaking: request shape
- One
Coststype, used at fleet (objective.costs), vehicle (vehicles[].costs) and shift (shifts[].costs) level:fixed,per_route_hour,per_travel_km,per_wait_hour,per_overtime_hour. Each field resolves on its own: the shift’s value, then the vehicle’s, then the fleet’s, then the default. A vehicle’sper_route_hournow replaces the fleet rate (it used to be added on top of it) and is reported underscore.components.time. A vehicle that sets its own rate still pays the fleet’sfixed. Any combination of fields is allowed. costs.per_stopis removed.per_late_hourmoves fromobjective.coststoobjective.- New shift-level overrides:
shifts[].capacity(merged per dimension),shifts[].limits(per field) andshifts[].costs(per field). - Breaks are either
windowedortriggered(they used to share onefloatingtype). A break window is{ "from", "to" }only. - Every datetime in a request must carry a UTC offset (
2026-10-20T08:00:00+02:00or...Z). A datetime without one is a400with aninvalid_valueitem at that field, and all of them are listed in one response. - Every
id(jobs, shipments and their legs, vehicles, quotas) must be 1 to 128 characters long. - The day of a shift, a
preferred_dateand asame_daygroup are read in the timestamp’s own UTC offset. Before, they were read as UTC dates, which moved some shifts to a different day. options.time_limit_stakes fractions of a second, with a floor of 0.1 s.
- Solve and evaluate return the same result type.
summary.statusis gone: readsummary.tasksfor what is served, andsummary.score.violationsfor broken constraints. On solve that count is always 0. On evaluate, it is the number of constraints the plan breaks. summary.taskscounts a shipment as one task on both the served and the unserved side.- Times in the response (
arrival,service_start,departure, and suggest’sarrivalandlatest_arrival) are written in the UTC offset of the serving shift’sfrom, not always in UTC. The instants are unchanged. - New stop field
service_start: when service begins, after any wait and any break taken on arrival. - Suggest results are
insertions(formerlyoptions). Wheninsertionsis empty, the newreason_codesays why:time_limit,candidates_exhaustedorinfeasible.
options.travel_fallback: if Solvice Maps cannot be reached, a solve can now estimate travel times instead of failing.estimateis the default;failkeeps the502/503error. A plan costed on estimates hassummary.travel_estimated: trueand noroutes[].polyline. Evaluate and suggest never estimate. See Distance matrices.all_or_nonerelation: every listed task is served, or none is. Each member must also be in asynchronizedortime_lagrelation.vehicles[].limits.max_leg_travel_time_s: a hard cap on the travel time of any single leg of a route, including the first and last legs.
objective.grouping.cost_per_reentrynow accepts up to 1,000 currency units.time_lagrelations on tasks with more than one time window are scheduled correctly.- A
synchronizedstop is no longer held past the end of its shift into overtime.
Breaking
- Suggest answers in the vocabulary of a plan. Each insertion has a
delta(costin money,distance_m,duration_s),placements[]with each added stop’s vehicle, shift and arrival window, androutes[], the changed plan routes. To apply an insertion, replace the routes of yourplanthat have the samevehicleandshift. Insertions rank by lowestdelta.cost, anddeltamatches the difference between two/evaluatecalls. See Suggest. - A
quotas[]window counts a stop when its service starts inside[from, to). It used to count every stop of a shift whosefromwas in the window. A window inside a single shift is now accepted.
jobs[].assignment_costsandshipments[].assignment_costs: an absolute price for serving a task on a given vehicle. Vehicles that are not listed cost nothing.compatible_vehiclesrelation, withvehicles[].compatible_with: the vehicles that serve the relation’s tasks must be compatible, which means neither vehicle’scompatible_withlist excludes the other. A relation takes 2 to 16 tasks, and each must also be in asynchronizedortime_lagrelation: use it to choose which crews may work together.
- A windowed break now waits for a service that ends exactly when the break window opens.
Breaking
- Suggest takes the same body as evaluate:
problemandplan, plus optionaloptions. Suggest places every task inproblemthatplanleaves out, sooptions.tasksis removed. A plan that already serves every task is a400. - Suggest’s
options.time_limit_sdefaults to 1 second, the same as solve. It used to search until done. - A
synchronizedortime_lagmember thatplanleaves out, and that suggest cannot place, now fails the request with a400. Before, it was skipped. Commit it inplan, or drop it fromproblem.
same_routerelation: every assigned member is served on one route (one vehicle shift). A reload within the shift does not split the route.
New
- Top-level
quotas[]: a hard cap (max_tasks) on how many stops of the listed tasks (job_ids) the listedvehiclesmay serve between them, optionally within afrom/towindow. A shipment counts as two stops. Evaluate reports aquotaviolation when a plan exceeds it.
New
vehicles[].limits.periods[]: caps on a vehicle’s totalmax_duration_s,max_distance_mormax_tasksacross all its shifts in a period, either recurring (every: dayorweek) or explicit (from/to). Evaluate reports aperiod_limitviolation.time_windows[].per_late_hour: soft lateness priced on a single task, when the task has exactly one window.
- A shipment’s
prioritycounts once, matchingscore.unserved_priority. Before, a dropped shipment weighed twice as much as a dropped visit with the same priority. - New
400s for values that used to be accepted without effect: negative or non-finite vehicle costs, and a negativemax_overtime_s. - An invalid
problem.travel.departure_timeis reported even when the request has other errors.
- Tasks coupled by
synchronizedandtime_lagare placed together, as a unit, when one placement at a time cannot fit them.
New
eligible_vehiclesis no longer limited to fleets of 64 vehicle shifts. The limit is now 256 distinct vehicle sets across all tasks; see Limits.
/evaluatedecides feasibility with the same check the solver uses, so solve and evaluate agree on whether a plan is feasible.per_late_houris charged at the rate you set. Before, the objective charged 60 times that rate.- The search can no longer return a route that exceeds a vehicle’s route limits.
These releases redesigned the V3 contract.Breaking
- Request bodies are nested: solve takes
{ problem, plan?, options? }, evaluate takes{ problem, plan }, and suggest takes{ problem, plan, options }, withoptions.tasksnaming the tasks to place. Theplanin a solve response can be sent back unchanged to evaluate, to suggest, or to a warm-started solve. - Top-level
depots[]. Every shift needs anid. A shift’sstartandendtake either alocationor adepot, andreloadslists depot ids. Start, end and reload stops name their depot. POST /v3/routing/validateis removed. Add?validate_only=trueto any endpoint instead; a valid request returns{ "valid": true, "warnings": [] }.POST /v3/routing/solve/streamis no longer part of the public API.- One error body on every V3 endpoint:
status,code,message,request_id, anderrors[], with one item per problem, each with acodeand a JSONpointerinto your request. Schema errors are400, not422. Send anX-Request-Idheader to trace a request; it is echoed back. See Errors. - One
summary.scoreon solve and evaluate:violations,unserved_priority,cost,currencyandcomponents. The components add up tocost. See Scoring. per_travel_houris nowper_route_hour.
unassigned[].category:impossible,blocked,priced_outorsearch_limit. An optional task whosedrop_feeis cheaper than serving it is reported aspriced_out: raise the fee. It is no longer reported as a time-limit problem.
- The unload at the end depot now finishes before the shift ends. Before, only the arrival there was checked against the shift end.
- With
per_late_hourset, the end of the shift (toplusmax_overtime_s) still holds. Only task windows become soft. - Responses with many unassigned tasks use far less memory.
Breaking
- Vehicle
skillsare objects,{ "name", "level"? }; a bare string is rejected.
- Skill levels:
jobs[].skills[].min_level, served by a vehicle skill whoselevelis at least that. jobs[].service_factors: a per-vehicle work rate for one task. Its service time is divided byfactor(together with the vehicle’s or shift’sservice_factor), so1.25makes it 20 % shorter.jobs[].target: a soft target time (at,on,per_late_hour). Lateness is priced; a target never makes a plan infeasible and never forces a wait. Stops reporttarget_lateness_s.time_lagrelation: a minimum and/or maximum gap between two tasks, on any vehicles, measured from the start or end of each.- A task can belong to more than one
sequence. - Shift-level
skills, which replace the vehicle’s skills for that shift. synchronizedcan align the ends of service instead of the starts (todayon: "end"), and stops report the wait a synchronization adds ashold_s.- New limits: 8 tasks per
synchronizedrelation, and 16 tasks per group linked bysynchronizedandtime_lagrelations. See Limits.
- With
per_late_hourset, evaluate reports lateness as a cost and no longer as atime_windoworshift_endviolation. - A warm-start
planis checked for feasibility. A stop outsidelocked_countthat does not fit is re-inserted by the search, and a locked stop that cannot be driven is a400. - Suggest prices its insertions on the schedule after
synchronizedandtime_lagholds, the same schedule evaluate reports.
Fixed
- The reported time cost covers the whole route span (travel, service, waiting and breaks), which is what the solver optimises. Before, it counted driving time only.
- Travel to and from a task without a location is reported correctly in stops and totals.
- Locations on more than one continent return a
400that explains the problem, instead of a502.
Breaking
- A vehicle that omits
skillshas no skills, the same as"skills": []. Before, it could serve every skill requirement. Give each vehicle that needs skills an explicit list.
New
problem.travel.enginechooses the road data:tomtom(default, typical traffic at the departure time) orosm(free-flow).tomtom_real_timeis defined but not enabled yet; it returns403 feature_not_enabled.jobs[].priorityranks required tasks when they cannot all be served: higher-priority tasks are kept first (shipments[].prioritytoo). It never makes a task optional;drop_feedoes that.- Suggest runs on a new search engine and can place shipments.
extra_costis now the exact change in plan cost. (These option fields were replaced in v0.13.0.)
Fixed
- Request bodies between 2 MiB and 10 MiB are accepted, as Limits documents. Before,
they were rejected with a
400.
New
options.polylines:simplifiedorfullreturns each route’s road geometry asroutes[].polyline.
- A solve that starts from a
planreturns before its time limit once the search stops improving.