Skip to main content
Every change to /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.
V3 is in beta. During the beta a release can rename or remove fields, as several entries below do. After general availability, planned for January 2027, /v3 only receives additive changes.

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.
To check a request against the current contract without fetching travel times or running a search, send it with ?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 a 400 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

BreakingNewImproved
v0.15.2 and async runs
Breaking
  • An unknown query parameter is now a 400 invalid_request with an unknown_field item, and a validate_only that is not a boolean is a 400 with an invalid_type item. Before, unknown parameters were ignored. Check for misspelled parameters, such as validateOnly. The error item has no pointer, because the parameter is not in the body; message names it.
New
  • Async runs: POST /v3/routing/runs takes the body of /v3/routing/solve, answers 202 at once with a run id, and solves in the background with an options.time_limit_s of up to 3600 (solve: 300). Poll GET /v3/routing/runs/{id} until status is succeeded, failed or cancelled, fetch the plan from GET /v3/routing/runs/{id}/result, or stop a run with POST /v3/routing/runs/{id}/cancel. A body is validated before the run is accepted, so an invalid request is a 400 at submit, never a failed run. callback is 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_day and same_route can name a shipment’s legs by their stop ids, pickup.id and delivery.id (by default "{shipment}:pickup" and "{shipment}:delivery").
Improved
  • 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_hour set, 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
v0.15.1
Improved
  • synchronized and time_lag chains: 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.
BreakingNewFixed
v0.14.0 and v0.15.0
Most of the renames in the table above shipped in these two releases. The largest changes:Breaking: request shape
  • One Costs type, 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’s per_route_hour now replaces the fleet rate (it used to be added on top of it) and is reported under score.components.time. A vehicle that sets its own rate still pays the fleet’s fixed. Any combination of fields is allowed.
  • costs.per_stop is removed. per_late_hour moves from objective.costs to objective.
  • New shift-level overrides: shifts[].capacity (merged per dimension), shifts[].limits (per field) and shifts[].costs (per field).
  • Breaks are either windowed or triggered (they used to share one floating type). A break window is { "from", "to" } only.
  • Every datetime in a request must carry a UTC offset (2026-10-20T08:00:00+02:00 or ...Z). A datetime without one is a 400 with an invalid_value item 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_date and a same_day group 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_s takes fractions of a second, with a floor of 0.1 s.
Breaking: response shape
  • Solve and evaluate return the same result type. summary.status is gone: read summary.tasks for what is served, and summary.score.violations for broken constraints. On solve that count is always 0. On evaluate, it is the number of constraints the plan breaks.
  • summary.tasks counts a shipment as one task on both the served and the unserved side.
  • Times in the response (arrival, service_start, departure, and suggest’s arrival and latest_arrival) are written in the UTC offset of the serving shift’s from, 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 (formerly options). When insertions is empty, the new reason_code says why: time_limit, candidates_exhausted or infeasible.
New
  • options.travel_fallback: if Solvice Maps cannot be reached, a solve can now estimate travel times instead of failing. estimate is the default; fail keeps the 502/503 error. A plan costed on estimates has summary.travel_estimated: true and no routes[].polyline. Evaluate and suggest never estimate. See Distance matrices.
  • all_or_none relation: every listed task is served, or none is. Each member must also be in a synchronized or time_lag relation.
  • 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.
Fixed
  • objective.grouping.cost_per_reentry now accepts up to 1,000 currency units.
  • time_lag relations on tasks with more than one time window are scheduled correctly.
  • A synchronized stop is no longer held past the end of its shift into overtime.
BreakingNewFixed
v0.13.0
Breaking
  • Suggest answers in the vocabulary of a plan. Each insertion has a delta (cost in money, distance_m, duration_s), placements[] with each added stop’s vehicle, shift and arrival window, and routes[], the changed plan routes. To apply an insertion, replace the routes of your plan that have the same vehicle and shift. Insertions rank by lowest delta.cost, and delta matches the difference between two /evaluate calls. See Suggest.
  • A quotas[] window counts a stop when its service starts inside [from, to). It used to count every stop of a shift whose from was in the window. A window inside a single shift is now accepted.
New
  • jobs[].assignment_costs and shipments[].assignment_costs: an absolute price for serving a task on a given vehicle. Vehicles that are not listed cost nothing.
  • compatible_vehicles relation, with vehicles[].compatible_with: the vehicles that serve the relation’s tasks must be compatible, which means neither vehicle’s compatible_with list excludes the other. A relation takes 2 to 16 tasks, and each must also be in a synchronized or time_lag relation: use it to choose which crews may work together.
Fixed
  • A windowed break now waits for a service that ends exactly when the break window opens.
BreakingNew
v0.12.0
Breaking
  • Suggest takes the same body as evaluate: problem and plan, plus optional options. Suggest places every task in problem that plan leaves out, so options.tasks is removed. A plan that already serves every task is a 400.
  • Suggest’s options.time_limit_s defaults to 1 second, the same as solve. It used to search until done.
  • A synchronized or time_lag member that plan leaves out, and that suggest cannot place, now fails the request with a 400. Before, it was skipped. Commit it in plan, or drop it from problem.
New
  • same_route relation: every assigned member is served on one route (one vehicle shift). A reload within the shift does not split the route.
New
v0.11.7
New
  • Top-level quotas[]: a hard cap (max_tasks) on how many stops of the listed tasks (job_ids) the listed vehicles may serve between them, optionally within a from/to window. A shipment counts as two stops. Evaluate reports a quota violation when a plan exceeds it.
NewFixedImproved
v0.11.6
New
  • vehicles[].limits.periods[]: caps on a vehicle’s total max_duration_s, max_distance_m or max_tasks across all its shifts in a period, either recurring (every: day or week) or explicit (from/to). Evaluate reports a period_limit violation.
  • time_windows[].per_late_hour: soft lateness priced on a single task, when the task has exactly one window.
Fixed
  • A shipment’s priority counts once, matching score.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 negative max_overtime_s.
  • An invalid problem.travel.departure_time is reported even when the request has other errors.
Improved
  • Tasks coupled by synchronized and time_lag are placed together, as a unit, when one placement at a time cannot fit them.
NewFixed
v0.11.5
New
  • eligible_vehicles is no longer limited to fleets of 64 vehicle shifts. The limit is now 256 distinct vehicle sets across all tasks; see Limits.
Fixed
  • /evaluate decides feasibility with the same check the solver uses, so solve and evaluate agree on whether a plan is feasible.
  • per_late_hour is 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.
BreakingNewFixed
v0.11.3 and v0.11.4
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 }, with options.tasks naming the tasks to place. The plan in a solve response can be sent back unchanged to evaluate, to suggest, or to a warm-started solve.
  • Top-level depots[]. Every shift needs an id. A shift’s start and end take either a location or a depot, and reloads lists depot ids. Start, end and reload stops name their depot.
  • POST /v3/routing/validate is removed. Add ?validate_only=true to any endpoint instead; a valid request returns { "valid": true, "warnings": [] }.
  • POST /v3/routing/solve/stream is no longer part of the public API.
  • One error body on every V3 endpoint: status, code, message, request_id, and errors[], with one item per problem, each with a code and a JSON pointer into your request. Schema errors are 400, not 422. Send an X-Request-Id header to trace a request; it is echoed back. See Errors.
  • One summary.score on solve and evaluate: violations, unserved_priority, cost, currency and components. The components add up to cost. See Scoring.
  • per_travel_hour is now per_route_hour.
New
  • unassigned[].category: impossible, blocked, priced_out or search_limit. An optional task whose drop_fee is cheaper than serving it is reported as priced_out: raise the fee. It is no longer reported as a time-limit problem.
Fixed
  • 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_hour set, the end of the shift (to plus max_overtime_s) still holds. Only task windows become soft.
  • Responses with many unassigned tasks use far less memory.
BreakingNewFixed
v0.11.2
Breaking
  • Vehicle skills are objects, { "name", "level"? }; a bare string is rejected.
New
  • Skill levels: jobs[].skills[].min_level, served by a vehicle skill whose level is at least that.
  • jobs[].service_factors: a per-vehicle work rate for one task. Its service time is divided by factor (together with the vehicle’s or shift’s service_factor), so 1.25 makes 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 report target_lateness_s.
  • time_lag relation: 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.
  • synchronized can align the ends of service instead of the starts (today on: "end"), and stops report the wait a synchronization adds as hold_s.
  • New limits: 8 tasks per synchronized relation, and 16 tasks per group linked by synchronized and time_lag relations. See Limits.
Fixed
  • With per_late_hour set, evaluate reports lateness as a cost and no longer as a time_window or shift_end violation.
  • A warm-start plan is checked for feasibility. A stop outside locked_count that does not fit is re-inserted by the search, and a locked stop that cannot be driven is a 400.
  • Suggest prices its insertions on the schedule after synchronized and time_lag holds, the same schedule evaluate reports.
Fixed
v0.11.1
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 400 that explains the problem, instead of a 502.
Breaking
v0.11.0
Breaking
  • A vehicle that omits skills has no skills, the same as "skills": []. Before, it could serve every skill requirement. Give each vehicle that needs skills an explicit list.
New
v0.10.5 and v0.10.6
New
  • problem.travel.engine chooses the road data: tomtom (default, typical traffic at the departure time) or osm (free-flow). tomtom_real_time is defined but not enabled yet; it returns 403 feature_not_enabled.
  • jobs[].priority ranks required tasks when they cannot all be served: higher-priority tasks are kept first (shipments[].priority too). It never makes a task optional; drop_fee does that.
  • Suggest runs on a new search engine and can place shipments. extra_cost is now the exact change in plan cost. (These option fields were replaced in v0.13.0.)
Fixed
v0.10.4
Fixed
  • Request bodies between 2 MiB and 10 MiB are accepted, as Limits documents. Before, they were rejected with a 400.
NewImproved
v0.10.1 – v0.10.3
New
  • options.polylines: simplified or full returns each route’s road geometry as routes[].polyline.
Improved
  • A solve that starts from a plan returns before its time limit once the search stops improving.