V3 is in beta. General availability (GA) is planned for January 2027.
Overview
V2 (OnRoute) and V3 describe the same routing problem in different vocabularies. This page maps one to the other: endpoints, request fields, relations, the objective, and the response. The tables below show, for each V2 field, the V3 field that replaces it and how the meaning differs. Some V2 fields have no direct V3 equivalent: V2 fields with no direct V3 equivalent lists them and what to send in V3 instead.How to port a request
1
List what has no direct equivalent
Check every field your V2 request sends against
V2 fields with no direct V3 equivalent.
A field in that list has no one-to-one V3 field, so decide its V3
replacement before you start.
2
Rewrite the body
Wrap the problem in
problem, then rename fields with the
Conventions, Vehicles,
Shifts and Jobs tables. Give every shift an id.3
Convert relations and pickup-delivery pairs
Rewrite
relations[] with the Relations table, and turn each
PICKUP_AND_DELIVERY pair into a
shipments[] entry.4
Set the objective explicitly
A V3 request without
objective prices time and distance at default rates.
Read Default objective, then convert the weights you
rely on with From weights to prices.5
Move the warm start and the options
Replace
initialResource and initialArrival with a request-level plan
(see Existing plans), and move the time budget and the
other options with the Options table.6
Validate without solving
Send the request to
POST /v3/routing/validate/solve. A valid request returns
{"valid": true, "warnings": []}. An invalid one returns the 400 the real
call would return: fix each errors[] item at its pointer. A leftover V2
key shows up as unknown_field. See Errors.7
Solve and compare
Solve the same instance and read the result with the
Response mapping tables. Compare
unassigned[], the
route totals and the stop order with your V2 result before you switch
traffic.Endpoints and lifecycle
Only solve has a background form. The three synchronous V3 endpoints hold the
connection open until the result is ready: a solve searches for
options.time_limit_s seconds (at most 60), and fetching travel times comes
on top of that. Set your HTTP client timeout above the budget you send, or
submit a run, which searches for up to 3600 seconds
without holding a connection. See Limits.
What each V2 step gave you is available like this:
- Solution. The body of the
solveresponse, or/resultof a run. - Explanation. Every entry of
unassigned[]carries acategory, machine-readablereasons[]and best-effortrelaxations[]. For a plan you built or edited yourself,evaluatereportsviolations[]per stop and per route. - Change. Keep the
planfrom the response, edit it, and send it back: toevaluateto score the edit, or tosolveas a warm start.locked_counton a plan route freezes its first stops.
Existing plans
Solve (as a warm start), evaluate and suggest all take an existing plan, and its shape changes. V2 reads it fromjobs[].initialResource and
jobs[].initialArrival; V3 takes an explicit plan object next to problem,
which is exactly what a V3 response returns under plan. Each plan.routes[]
entry names its vehicle and its shift, and lists its stops in order. On
suggest, V2 places the jobs that have no initialResource; V3 places every
task of problem that is not in plan.
plan differs from the V2 job fields in these ways:
- Shift per route.
plan.routes[].shiftnames the shift each route runs on, so a plan can span several days. V2 has no per-job shift. - Order only. A plan lists stops in visiting order, and V3 recomputes every
arrival time from that order. V2
initialArrivalvalues are not sent. - Freezing. Set
locked_counton a plan route to freeze its first stops. - Reloads. V2
initialDepotbecomes a{ "type": "reload", "depot": "<depot id>" }stop in the plan route. - Shipments. A shipment appears in the plan as
pickupanddeliverystops. See Pickup and delivery pairs.
plan.
Conventions
Request mapping
Vehicles (resources)
Per-vehicle limits live under
limits: limits.max_distance_m takes the place
of V2 maxDriveDistance, and limits.max_route_duration_s caps the whole route
span (travel, service and waiting), not driving time alone. V3 has no cap on a
route’s total driving time, so maxDriveTimeInSeconds and maxDriveTime have
no counterpart.
Shifts
Multi-day planning works the same way in both versions: one shift per working
day. In V3 each shift has its own
id, and each route reports the shift it
runs on.
Breaks
deductPreviousBreaks has no V3 counterpart: model each accumulation window
as its own break.
Depots (multi-trip reloads)
depots[]is top-level in both versions. In V2 every shift may use every depot; in V3 a shift opts in by naming depot ids in its ownreloads[]. A shift withoutreloadsis single-trip.namebecomesid, anddurationbecomesservice_duration_s. Both keep one handling time per depot.costbecomesper_reload, charged once per reload. No automatic equivalent: setper_reloadyourself.- A depot can also be a shift’s start or end:
"start": { "depot": "hub" }. shifts[].max_tripscaps the number of trips in a shift. V2 has no such cap.
Jobs
Time windows
A V3 window has nohard flag and no weight. It is hard unless lateness is
priced:
- Per window:
time_windows[].per_late_hourmakes that window soft on its late side, at that price per hour. It is allowed only on a task with exactly one window and must be greater than0. This is where a V2hard: falsewindow goes. - Fleet-wide:
objective.per_late_hourprices lateness on every window that does not set its own rate.
from. A job with several windows cannot make
them soft: per_late_hour is allowed only on a task with exactly one window.
To keep a V2 soft window beside hard windows, keep the hard windows and add a
target whose at is the soft window’s to. Only lateness past that end is
priced. Raise a window’s from to the soft start only if service before it
must be forbidden; that changes which plans are feasible.
A job can list several windows and is served in one of them. An optional
time_windows[].choice_cost ranks them when one window is preferred over another; it
cannot be combined with objective.per_late_hour.
Tags
When you port natively, decide per tag:- A requirement becomes
jobs[].required_skills: [{ "name": "plumbing" }]. The vehicle, or the shift that serves the job, must list every required skill.required_skills[].min_levelandvehicles[].skills[].leveladd proficiency levels. - A preference (V2
hard: falsewith aweight) becomesjobs[].preferences[]: one entry per vehicle that has the capability, each with the sameviolation_cost. Every listed vehicle that does not serve the job charges its cost, so a vehicle with the capability is cheaper by oneviolation_costthan a vehicle without it.
GROUP_SEQUENCE group is a group label, not a skill. It
becomes jobs[].tags.
Priority and optional jobs
V3 has two separate mechanisms. Pick by what should happen when not every job fits.
V2
priority maps to V3 priority. The V2 reference weighs an unserved job
by priority × duration. To keep V2’s ranking, send that product (duration in
seconds) as the V3 priority, or send your own rank if job length should not
count.
priority and drop_cost cannot be combined on one job (400).
A job with neither field is must-serve.
Planned arrival and urgency
A V3target is a point in time with a price for missing it on the late side:
V3 target
per_late_houris required and must be greater than0. It is charged per hour of arrival afterat, prorated per second.- Arriving before
atcosts nothing, and the vehicle does not wait forat. V2 documentsplannedArrivalas “as close as possible” to the planned time; a V3targetdoes not price the early side. Add atime_windowsentry when the job must not start before a given time. "on": "departure"measures the end of service instead of the arrival.
target needs a price you choose. Port the V2 inputs like this:
Relations
V3 relations are a tagged union underproblem.relations[], with members in
stops instead of jobs. All V3 relations are hard: there is no relation
weight. Every V2 relation type is listed here.
resource pins every member to that vehicle. V3 relations have
no such field: set eligible_vehicles: { "allowed": [resource] } on each job
instead.
Relation fields that change meaning:
-
SAME_TIMEtolerance. The V2 reference documentsmaxWaitingTime(default 1200 seconds). V3max_spread_sis required: set it explicitly. -
SAME_TIMEwithminTimeIntervalon three or more jobs: write onetime_lagper pair. -
DIRECT_SEQUENCEtime intervals. V2 appliesminTimeIntervalandmaxTimeIntervaltoDIRECT_SEQUENCEas well as toSEQUENCE. In V3, add onetime_lagper consecutive pair next to thesequenceandsame_vehiclerelations:The lag is measured between the two service starts. AddV3 relations for a DIRECT_SEQUENCE with a time interval"on": "departure"tofromfor V2timeInterval: FROM_DEPARTURE. -
hardMinWaitandweighthave no V3 counterpart: a time bound cannot be softened and a relation cannot be weighted. -
Overlapping relations. V2 lets a job sit in several relations of one type.
In V3 a job may be named by at most one
same_vehicle, onesame_day, onesame_routeand onesynchronizedrelation; a second one is a400. Merge overlappingsame_vehicleorsame_routemembers into one relation. For a job in severalSAME_TIMErelations, write onetime_lagper pair of members instead, withmin_sset to minus the tolerance andmax_sto the tolerance. -
partialPlanning.same_vehicle,same_dayandtime_lagbind the members that are served: a member that ends up inunassigned[]imposes nothing on the others, andsequenceonly orders the members that share a route. That is the V2partialPlanning: truebehaviour. V2 defaults topartialPlanning: false, so aSAME_TIMErelation of two or more jobs that does not set it totrueis all-or-nothing. To keep that in V3, add anall_or_nonerelation over the samestopsnext to thesynchronizedone. ASAME_TRIPis all-or-nothing in V2 unless it setspartialPlanning: true. V3 has an all-or-nothing form for one shape of it: a pickup-and-delivery pair and aDIRECT_SEQUENCEthrough the same jobs, with a job such as a tow between the pickup and the delivery. To keep it, add anall_or_noneover the shipment id and the other jobs of theconsecutivechain, and name the whole chain. Otherwiseall_or_nonetakes jobs that asynchronizedortime_lagrelation also names, or jobs that form oneconsecutivechain, so for the other relation types checkunassigned[]for members of the relation after each solve. A shipment is different: it is one task, so its pickup and delivery are served as a pair or the shipment is listed once inunassigned[].
Pickup and delivery pairs
A V2 pickup-and-delivery pair is two jobs plus a relation. In V3 it is oneshipments[] entry and no relation.
- On the leg (
pickupordelivery):location,setup_duration_s,service_duration_sandtime_windows. A legidis optional; set it to the V2 job name to keep that name on the stop in the response. - Once on the shipment:
load(the load carried from pickup to delivery),priority,drop_cost,required_skills,eligible_vehicles,preferences,service_factors,assignment_costsandtarget. They apply to the pair. - The shipment
idmust be unique acrossjobsandshipments.
resource becomes eligible_vehicles.allowed with that one id, as does a
plannedResource on either job.
To keep a pair as two jobs instead, use a sequence and a same_vehicle
relation over them.
Period rules
V2resources[].rules[] splits across two V3 mechanisms.
limits.periods[] caps a vehicle’s totals per "every": "day", per
"every": "week", or per explicit from and to window, with any of
max_route_duration_s, max_distance_m and max_stops. A shipment counts as two
stops toward max_stops. See Constraints.
Options
New in V3:
options.seed for repeatable runs, and problem.travel.departure_time
to plan against the traffic of a given instant (not with osm).
V3 has no caller-supplied distance matrix. The API computes travel times and
distances itself, selected by travel.engine and vehicles[].profile.
Default objective
A V3 request that sends noobjective is optimized with the V3 defaults, which
price distance as well as time. Amounts are in the objective’s currency.
A request ported field for field therefore starts paying for distance, which can
change which routes win. To optimize route time only, set the distance rate to
zero. Rates you leave out keep their defaults:
options.minimizeResources: true becomes a per_route charge, paid once per
route, so a vehicle pays it once for every shift it uses. Set
objective.costs.per_route to the amount you want to pay to open a route, for
example one hour of route time:
From weights to prices
V3 has no weights. Every objective term is a price in the objective’s currency: a fleet rate inobjective.costs, or a price on the field itself (drop_cost,
preferences[].violation_cost, assignment_costs[].cost,
target.per_late_hour, time_windows[].per_late_hour). There is no conversion
factor from a V2 weight to a price, so choose each price yourself: a price is the
amount of route cost you accept to avoid the thing it prices.
A price only has meaning next to the hourly rate it competes with
(objective.costs.per_route_hour, 35 by default). Choose each price against the
rate you use.
In V3 an unlisted vehicle in
assignment_costs costs nothing, while V2 counts an
unranked resource as ranking 100: give every vehicle an assignment_costs
entry, with the most preferred vehicle at the smallest cost.
Response mapping
objective.
Routes and stops
stops[].type takes seven values, and a client must handle all of them:
A
break stop appears for each break a route takes, and a reload stop for
each mid-shift return to a depot. V2 reports a reload as a visit with
activity: "DEPOT" and has no visit for a break.
Summary, score and unassigned jobs
New in V3:
summary.tasks.served, tasks.unassigned, vehicles_used,
timings.total_ms, and a top-level plan that mirrors routes in the
request’s vocabulary. Send that plan back unchanged to evaluate, to
suggest, or to solve as a warm start.
unassigned[].category says what kind of miss it is: impossible (no vehicle
can ever serve it), blocked (it does not fit alongside the jobs that were
served), priced_out (its drop_cost was cheaper than serving it) or
time_limit (a feasible position exists but was not found in the time
budget).
Suggest response
V2 returns onesuggestions[] entry per alternative, each with a score and
its assignments[]. V3 returns one suggestions[] entry per task outside
plan, with that task’s alternatives as ranked insertions[], best
first. Tasks linked by a relation are placed together and share one entry.
New in V3:
suggestions[].reason (code and message) when insertions is empty,
placements[].service_start (when service starts, after any wait),
insertions[].interchangeable_vehicles, and the top-level truncated and
timings.total_ms. See Suggest for every field.
V2 fields with no direct V3 equivalent
The fields below have no one-to-one V3 field. The right-hand column is what to send in V3 instead, or says that there is no equivalent.Top-level fields
Resource and shift fields
Job fields
Relation fields
Depot fields
Option fields
Feature parity
Quickstart
Send your first native V3 request.
The V3 API model
The full request and response model.
Constraints
Every constraint field, with examples.
Errors
The error body and its codes.
Field service day
A worked V3 example: one solve, then evaluate and suggest on the same data.
Suggest
Rank insertion positions for new tasks.