Skip to main content
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

object
required
The same problem object as on /v3/routing/solve. See the V3 API model.
object
required
The plan to score: routes[], one entry per vehicle shift that serves something.
Evaluate takes no 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 of plan:
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, with delivery-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.
The truck reaches 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.
  1. The request is validated. Schema, references and limits are checked first. A problem here is a 400 and no travel times are fetched.
  2. Travel times are fetched. If they cannot be, the plan is scored on estimated travel times and summary.travel_estimated is true; 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 a 400. See Distance matrices.
  3. The plan is matched to the problem. A plan that cannot be laid out at all is a 400 with one item, whose pointer is /plan/routes/{i} or /plan/routes/{i}/stops/{k}. The first such problem is reported:
  4. The plan is scored. Everything else, such as a missed window or an overloaded vehicle, is reported as a violation in a 200 response.

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, except 502 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.