Skip to main content
V3 is in beta. General availability (GA) is planned for January 2027.
This page describes the V3 API (/v3/routing). For the V2 API (/v2/vrp), see the V2 version of this page.
Every request asks the solver two questions: which plans are allowed, and which allowed plan is best. Your constraints answer the first and your objective answers the second. This page explains the model behind both for POST /v3/routing/solve. For each field and the JSON to send, see Constraints. On this page a task is a job or a shipment. Where a rule applies to jobs only, the page says “job”.
Coming from V2? There are no constraint weights and no hard/medium/soft score levels here. A rule is either hard (it always holds) or it is a price in your own currency. See Migration from V2.

Hard and soft

Every rule you send is one of two kinds: A solve result never contains a broken hard rule: summary.score.violation_count is always 0 on /v3/routing/solve. To check a plan of your own, which may break hard rules, use /v3/routing/evaluate.

Hard constraints

Only the rules you send are active. A request with capacities and nothing else is checked for capacity and nothing else.

Capacity

Dimensions are named and matched by name between capacity and pickup_load.

Time windows

A window is hard unless you price lateness. This job must be reached between 09:00 and 12:00:
Several windows on one task mean “any of these”. Arriving early always waits for from; only the late side can be made soft.

Relations

relations[] ties jobs and shipment legs to each other. It has ten types, and all of them are hard.
An unknown relation type is rejected with HTTP 400.
A relation reaches a shipment through its legs: sequence, same_vehicle, same_day and same_route accept a leg’s stop id (by default "{shipment}:pickup" or "{shipment}:delivery") and bind that leg like a job. A leg coupled to other tasks by synchronized or time_lag is rejected with HTTP 400. A shipment’s own id in the stops of a relation, and a shipment that carries the tag of a same_vehicle or same_day relation, is rejected with HTTP 400 not_supported; tag_order rejects a tag that selects a shipment. The one exception is all_or_none, which takes a shipment’s own id (both legs) in a group that is exactly one consecutive chain. No member is silently dropped. See Shipments in relations.
A shipment already has one built-in rule of its own: the pickup comes before the delivery, on the same route. See Relations for every field and every rejected combination.

Soft constraints

Use these levers to allow something at a price. Each one reports what it cost in summary.score.components. How they combine:
  • Lateness, per window or fleet-wide. Use time_windows[].per_late_hour when only some tasks may run late: it softens that one window and leaves every other window hard. It must be greater than 0 and is allowed only on a task with exactly one window. Use objective.per_late_hour to soften all windows at once; a window with its own rate keeps that rate. Omit the fleet rate to keep windows hard: an explicit 0.0 is rejected with HTTP 400.
  • Alternative-window cost does not soften anything. Every window stays hard; choice_cost only grades which of a task’s two or more windows is preferred. A non-zero choice_cost anywhere in the request cannot be combined with objective.per_late_hour (HTTP 400).
  • A target is a point, not a window. It never makes a task unassignable and never forces a vehicle to wait. On a shipment it is measured on the delivery leg. The stop reports target_lateness_s.
  • Overtime is bounded. max_overtime_s grants the extension; anything beyond to + max_overtime_s is still forbidden. Without per_overtime_hour overtime carries no extra charge beyond the normal hourly route rate.
  • Preference entries are independent. A task listing several preferred vehicles pays for every listed vehicle that does not serve it, including when one of the others does. A vehicle with several preferred_tags charges each tag the task lacks.
A job with a soft window, a target, and a preferred vehicle:
Three more priced fields shape a plan without softening a rule: drop_cost makes a task optional at a price, assignment_costs prices serving a task on a given vehicle, and objective.grouping keeps nearby tasks together. Skills, capacity, eligibility, relations, limits and quotas have no soft form.

From constraints to the objective

Among the plans that respect every hard rule, the solver compares in a fixed order: least unassigned_priority first, then least cost. Serving required tasks always comes before saving money; every soft lever above is part of cost. Objective function covers the comparison, priority, and the cost model in full. You set cost rates at three levels: fleet-wide under objective.costs, per vehicle under vehicles[].costs, and per shift under shifts[].costs. A lower level replaces a rate field by field.
Put a shift’s start and end on the shift itself, as start and end, each naming a location or a depot.

Why a task is left unassigned

When the solver leaves a task out of the plan, the solve response says why. Each task left out has an entry in unassigned[]; you do not need an extra request or flag.
Read the entry in this order:
  1. category tells you what kind of fix can work. impossible and blocked mean a hard rule is in the way: change the request, or add capacity or working time. priced_out means the task has a drop_cost and paying it was cheaper than serving the task. time_limit means a worthwhile place exists and the time budget ran out first.
  2. reasons[] has one entry per rule that blocked the task, once each, named in constraint. For priced_out and time_limit there is one reason without a constraint.
  3. relaxations[] gives the smallest change that would have let the task fit, when one number can express it: reduce_load (in load units) for capacity, extend_time_window (in seconds) for time windows. It is empty for rules such as skills, where the fix is structural. pointer is a JSON Pointer to the field to change in your request body, such as /problem/jobs/8/pickup_load.
Errors lists every category and reason code with what to check for each. Tasks that were routed carry no explanation. See Explainable AI for worked examples.

Performance impact

The solver checks only the rules your request uses, so a plain capacity problem is the fastest kind to search. Each additional kind of rule (time windows, skills, relations, shipments, breaks) adds work to every candidate move, which means fewer moves explored in the same options.time_limit_s. Send the rules your operation needs and leave out the ones it does not. See the Performance guide for the other levers.

Best practices

  1. Use a hard rule for anything that must always hold. Capacity, skills, eligibility and limits belong in their own fields, not in a large cost.
  2. Pick the narrowest lateness lever. Soften one task’s single window with time_windows[].per_late_hour, soften all windows with objective.per_late_hour, and use target for a deadline that must never make a task unassignable.
  3. Make a task optional with drop_cost. Tasks are required by default.
  4. Rank required tasks with priority. When not every required task fits, the solver keeps the higher-priority ones. See Objective function.
  5. Read unassigned[] before tuning costs. Cost rates do not rescue a task that a hard rule blocks; category, reasons and relaxations tell you what does.
  6. Relate a shipment through its legs. A shipment’s own id in a relation is rejected with HTTP 400 not_supported (all_or_none over a consecutive chain is the exception); name {shipment}:pickup or {shipment}:delivery instead. To tie a shipment to a vehicle, set eligible_vehicles on the shipment.
Fields are validated strictly: an unknown or misspelled field, and contradictory combinations such as priority together with drop_cost on one task, are rejected with HTTP 400 and a message describing the problem. See Errors.

Constraints

Every constraint field, with the JSON to send

Objective function

How plans are compared, priority, and the cost model

Explainable AI

Worked examples of the unassigned[] diagnosis

Performance guide

How problem size and constraint choice affect solve time