V3 is in beta. General availability (GA) is planned for January 2027.
Overview
POST /v3/routing/evaluate takes a problem and a plan and tells you what that plan is worth. It computes arrival times, loads and the cost breakdown exactly as a solve would, and lists every hard constraint the plan breaks. It never moves a stop.
Typical uses:
- Check a plan a dispatcher edited by hand before you commit it.
- Compare your current planning against a solve result on the same cost model.
- Get the full timeline after you apply a suggestion.
- Re-score yesterday’s plan against today’s constraints.
The request
The same
problem object as on /v3/routing/solve. See the V3 API model.options: there is no search, so there is nothing to tune. An options key is rejected as an unknown field.
The plan shape
The value ofplan:
A stop is one of:
Start, end and break stops are not listed; they are placed for you. Every solve and evaluate response returns its routes in this shape as
plan, so a result’s plan can be sent back unchanged.
A task you leave out of every route is not an error. It is reported in unassigned[], with a category that says whether it could be added: see What unassigned means on evaluate.
Example
The quickstart’s two deliveries, withdelivery-1 due by 11:00 local time and a hand-made plan that visits delivery-2 first:
The response
The response has the same four parts as a solve response:summary, routes, plan and unassigned. The figures below are illustrative; yours depend on live travel times.
delivery-1 at 11:29:20 local time (+02:00, the shift’s offset), 1,760 seconds after its window closed at 11:00:00. The plan is still scored in full, so you can see what it would cost and how late it would be.
How it differs from a solve response
Everything else, including the stop timeline and
score.components, reads exactly as on solve. See the Quickstart for the stop fields and Objective function for the cost breakdown.
In a request that contains shipments, summary.tasks.served and summary.tasks.unassigned count a shipment once, like a job, whether its two stops are in the plan or not; unassigned[] has one entry per job or shipment the plan leaves out, so its length equals summary.tasks.unassigned.
What unassigned means on evaluate
Evaluate never adds a task to your plan. For each task the plan leaves out,unassigned[].category tells you whether it could be added to the plan as it stands:
A task that fits has one reason and no
constraint: no constraint refused it, unless its all_or_none group holds it back (all_or_none) or its all-or-none crew cannot be placed together (synchronized, time_lag). To place such a task, add it to plan or ask /v3/routing/suggest for its best position. The constraint vocabulary is listed in Errors.
Violations
Each violation is{ "code", "message" }, plus how far over the limit it is where that is one number. code is a stable string to branch on; message is for people. Unassigned reasons[].constraint uses the same values.
New codes can be added as new constraints ship. Handle the ones you care about and treat anything else as a generic violation.
A time window priced as soft, through
time_windows[].per_late_hour or objective.per_late_hour, is not a violation when it is missed. The lateness shows up in the stop’s lateness_s and in score.components.lateness instead.What evaluate checks, and when
Evaluate needs real travel times to compute a timeline, so it fetches them the same way solve does. What it skips is the search.-
The request is validated. Schema, references and limits are checked first. A problem here is a
400and no travel times are fetched. -
Travel times are fetched. If they cannot be, the plan is scored on estimated travel times and
summary.travel_estimatedistrue; evaluate has no option to fail instead. The timeline, the violations and the score then hold only against the estimate, which can be tens of percent off: evaluate again once travel times can be fetched if the verdict must hold on the road. Coordinates that span more than one region are a400. See Distance matrices. -
The plan is matched to the problem. A plan that cannot be laid out at all is a
400with one item, whose pointer is/plan/routes/{i}or/plan/routes/{i}/stops/{k}. The first such problem is reported: -
The plan is scored. Everything else, such as a missed window or an overloaded vehicle, is reported as a violation in a
200response.
Validate without scoring
POST /v3/routing/validate/evaluate runs step 1 only and returns 200 {"valid": true, "warnings": []} or the 400 the real call would return. It checks problem; it does not look at plan, because matching the plan needs the later steps.
Errors
Evaluate returns the same error body and status codes as solve, except502 and 503: travel times it cannot fetch are estimated, and it never waits for a free solver. A plan with violations is not an error: it is a 200 with summary.score.violation_count above 0.
Suggest
Find the best slot for a new task in a plan you keep.
Errors
The error body, every code, and what to do about each.
Constraints
The rules behind each violation code.
API reference
Request and response schema for every endpoint.