Skip to main content
V3 is in beta. General availability (GA) is planned for January 2027.
The V3 Routing API describes a vehicle routing problem with a small set of orthogonal primitives. Every request nests the problem under a problem key; a minimal one is problem.jobs and problem.vehicles.
This page describes the request and response of POST /v3/routing/solve. /v3/routing/evaluate and /v3/routing/suggest take the same problem object. The schema is strict: a field the schema does not define, such as an old field name or a typo, is rejected with HTTP 400 and an unknown_field item. Its message names the field and its pointer locates it: the key itself, or the object that contains it. See Errors.
Throughout this page a task is a job or a shipment.

Design principles

  • Few orthogonal primitives. Eligibility, skills, and sequencing are each a single field. A pickup-then-delivery is its own shipments[] entry type, not a flag on a job.
  • Everything soft is money. Lateness costs a rate per hour, dropping a task costs a fee, a non-preferred vehicle costs an amount. That is one unit to trade against, with an itemised cost breakdown in every response.
  • Penalties live on the thing. A task’s drop price (drop_fee) and preferences sit on the task object itself: read one job and you see what it costs to bend each rule for it.
  • Strict schema, named rejections. Unknown fields are 400s with an unknown_field item. Fields that are specified but not available yet are 400s with a not_supported item, rather than being ignored.

A request at a glance

The smallest valid request serves everything and minimises travel cost:
A solve request has up to three top-level fields: problem, plan, and options. plan is an optional warm start (see Warm-starting with plan) and options is covered under Options and travel. problem holds up to eight fields. vehicles and at least one job or shipment are required; the rest are optional. Conventions:
  • Durations are seconds (_s) and distances are metres (_m).
  • Timestamps are ISO 8601 / RFC 3339 instants. Always include a UTC offset (+02:00 or Z): a timestamp without one is accepted and read as UTC, not as local time. Timestamps in a response are always UTC with a Z suffix.
  • Coordinates are [longitude, latitude].
  • Money is a plain number in your own currency.

Jobs

A job is a unit of work at one location, such as a delivery, pickup, service call, or inspection. Its visit fields (location, service_duration_s, time_windows) sit directly on the object.
string
required
Unique across jobs and shipments. Echoed as routes[].stops[].id and unassigned[].job_id.
Place
Where the job is performed: { "coordinate": [lon, lat] } (GeoJSON order), or the bare [lon, lat] array as shorthand. Omit location for a virtual (location-less) job: it still has service duration, time windows, demand, and skills, but requires no travel. A job’s location is always an inline coordinate; only a shift’s start/end can reference a depot by id. See Vehicles and shifts.
integer
Seconds paid once on arrival at a new location, distinct from service_duration_s and skipped when the previous stop in the route is already at this location. Not scaled by service_factor.
integer
Nominal on-site service time in seconds. The serving vehicle’s service_factor scales it, so the response’s stops[].service_s may differ from the value you sent.
array
One or more { "from", "to" } windows (ISO 8601). Hard by default: a job that cannot be reached inside any of its windows is left unassigned. Two optional per-window fields:
  • per_late_hour makes this window soft on its late side: arriving after to is allowed and priced at this rate per hour, in place of the fleet-wide objective.costs.per_late_hour. Must be > 0, and only on a task with exactly one window.
  • cost is a flat price for being served in this window rather than one of the task’s other windows. It grades preference among alternatives (the task needs at least two windows) and does not soften any of them.
See Constraints.
object
Load that goes onto the vehicle at this visit, as named non-negative amounts such as { "weight": 30, "volume": 2 }. It stays on board until the next reload or the end of the route, so load_after rises at the stop. Use it for goods you collect. For goods you drop off, use delivery.Dimensions are matched against vehicles[].capacity by name; order never matters. A vehicle whose capacity map omits a dimension has capacity 0 in it; a vehicle with no capacity at all is not capacity-limited. A dimension name that no vehicle lists is therefore not rejected: check for typos if a job comes back unassigned on capacity.
object
Jobs only. Load that comes off the vehicle at this visit, having been loaded at the depot, as named non-negative amounts such as { "weight": 10 }. The vehicle leaves each trip carrying the sum of that trip’s delivery quantities, so load_after starts at that sum and falls at each stop. A job can set both fields: delivery for what it drops off and demand for what it collects.
The two fields point in opposite directions, and both count against the vehicle’s capacity: On a route that only drops off, or only collects, the peak load is the same total with either field, and only the direction of load_after differs. The choice matters when one route mixes the two: a drop-off frees room for a later collection only when it is modelled as delivery. See Capacity.
boolean
default:"true"
Omitted or true = must serve. A must-serve task that cannot be placed does not fail the request: it is returned in unassigned[] with a reason and summary.status becomes partial. false is only accepted together with drop_fee; without it the request is a 400.
number
What leaving this task unserved costs, in the objective’s currency, and the only way to make a task droppable: the task is served whenever serving it is cheaper than the fee. Fees paid are reported in score.components.drop_fees. Cannot be combined with mandatory: true or with priority (400).
integer
default:"1"
Importance of a must-serve task when not all of them fit. The solver maximises the summed priority of served tasks before it considers any cost, so a higher-priority task is kept ahead of lower-priority ones. Must be >= 1. It is a rank, not money, and never makes a task droppable. Use drop_fee for that; setting both is a 400. The summed priority of must-serve tasks left out is reported as score.unserved_priority.
array
Hard capability requirements: [{ "name": "fridge" }], each with an optional min_level (default 1). The serving vehicle must provide every named skill at that level or higher. For a soft affinity use tags with the vehicle’s preferred_tags, or preferences (below).
object
Hard filter: allowed (whitelist, omit = all vehicles eligible) or excluded (blacklist), mutually exclusive.
string
Hard-pins the job to one vehicle id; survives re-optimisation. Stronger than eligible_vehicles (a set filter) and hard, unlike preferences.
array
Soft vehicle affinity: [{ "vehicle": "v2", "violation_cost": 800 }]. Each entry prices independently: a job listing several preferred vehicles pays every entry whose vehicle didn’t serve it.
array of strings
Free-form labels used for relation group matching and, when a shift scopes itself via serves_tags (hard) or a vehicle prices preferred_tags (soft), for tag matching. A task matches when it carries any one of the listed tags, unlike skills, where all must be provided.
object
Soft point deadline: { "at": "...", "event": "arrival", "per_late_hour": 60.0 }. event is arrival (default) or departure (the end of the visit). Reaching event after at is priced, never infeasible, and an early arrival never waits for it. Independent of objective.costs.per_late_hour. On a shipment, measured on the delivery leg.
string (ISO 8601 instant)
Soft preference for which calendar day (UTC) a task lands on in a multi-shift problem, priced by objective.non_preferred_date_cost. Has no effect while that cost is unset or 0.
array
Per-vehicle work-rate overrides for this task: [{ "vehicle": "v1", "factor": 1.25 }], multiplied with the serving vehicle’s or shift’s service_factor.

Shipments

A shipment is a bound pickup→delivery pair with its own shipments[] entry type. The solver guarantees the pickup precedes the delivery on the same route; carried demand counts against capacity only between the two legs.
pickup and delivery each have their own required location, plus setup_duration_s, service_duration_s, time_windows, and an optional stop id (default "{shipment}:pickup" / "{shipment}:delivery"). Every other job field (demand, mandatory, drop_fee, priority, skills, eligible_vehicles, locked_vehicle, preferences, tags, target, preferred_date, service_factors) is shared and applies to the pair as a whole: one droppability and eligibility decision, one drop_fee charged once. On a shipment, delivery is the delivery stop object shown above. It is not the jobs[].delivery load field, which exists on jobs only.
For a shipment whose two legs move different amounts (a cargo swap), set pickup.demand and delivery.demand instead of the shared shipment-level demand. Set it on both legs or neither; the two forms are mutually exclusive.
Shipments take part in relations in a limited way, and they change how the response counts tasks. See Relations and shipments and Counting with shipments.

Vehicles and shifts

A vehicle carries capacity, skills, a cost model, and one or more shifts. The shift is where the working window and start/end locations live. The fleet is one vehicles[] entry per physical vehicle.
string
required
Unique across vehicles[]. Echoed as routes[].vehicle.
string
default:"car"
Routing profile for this vehicle’s travel times and distances: "car", "truck", or "bike". Any other value is a 400.
object
Named dimensions, matched by name against job demand and delivery and shipment demand.
array
Capabilities the vehicle provides: [{ "name": "fridge", "level": 2 }] (level optional, default 1). Always objects: a bare string is rejected. A shift can replace the list for its own day with shifts[].skills.
array
Soft: [{ "tag": "north", "violation_cost": 25.0 }]. Serving a task that lacks a preferred tag incurs that entry’s cost; entries apply independently.
object
Hard caps: max_distance_m and max_route_duration_s per route (whole-route span: travel, service, and waiting all count, the same quantity as routes[].shift_duration_s), and periods[] for caps across all of the vehicle’s shifts in a day, a week, or an explicit window. See Constraints.
object
This vehicle’s own cost terms, next to the fleet-wide rates in objective.costs:
  • per_travel_km replaces the fleet objective.costs.per_travel_km for this vehicle (0 makes its distance free).
  • fixed (once for each shift the vehicle is used on), per_stop (per served stop), and per_route_hour (per hour of the route’s span) are charged on top of the fleet time and distance rates and reported in score.components.fixed. Set at most one of these three per vehicle; a second is a 400 (not_supported). Whichever you set takes the place of the fleet objective.costs.per_vehicle for this vehicle.
  • per_travel_km combines freely with whichever of the three you picked.
Because per_route_hour is added to the fleet rate, a vehicle with "per_route_hour": 50 under the default fleet rate of 35 per hour costs 85 per hour of route span. To pay only per-vehicle hourly rates, set objective.costs.per_route_hour to 0.
number
default:"1.0"
Travel-speed multiplier, (0, 5]. Effective travel time = travel_time / speed_factor, so above 1.0 is faster.
number
default:"1.0"
Work-rate multiplier for service_duration_s only, (0, 5]: effective service time = service_duration_s / service_factor. Never applied to setup_duration_s, depot or reload handling time, waiting, or breaks. A shift may override it for its own day via shifts[].service_factor.
string
required
1 to 128 characters, unique within the vehicle. Echoed as routes[].shift.
string (ISO 8601 instant)
required
Start of the working window.
string (ISO 8601 instant)
required
End of the working window. The route, including any unload at end, must finish by to, or by to + max_overtime_s.
object
Where the shift starts: exactly one of location (an inline place) or depot (a depots[].id), plus an optional service_duration_s overriding the handling time there. Omit start for a route that is open on its start side.
object
Where the shift ends, in the same shape as start. Omit end for a route that is open on its end side.
boolean
default:"false"
When true, the leg from start to the first task costs nothing and takes no time, while the start stop is still reported. Use it when the vehicle’s commute should not count.
boolean
default:"false"
The same for the leg from the last task to end.
integer
Seconds past to the vehicle may run. Overtime is priced by the fleet-wide objective.costs.per_overtime_hour; while that rate is unset, overtime is allowed at no cost.
integer
Maximum stops in this shift: a job counts as one stop, a shipment as two (pickup and delivery).
array of strings
Hard scope: when set, this shift serves only tasks carrying at least one of these tags (untagged tasks remain serveable by every shift).
array
Scheduled driver breaks (floating with a window or a drive/duty trigger, or fixed). They appear in the response as break stops. See Constraints.
array of strings
Depot ids (from depots[]) the vehicle may return to mid-shift to empty its load and start a fresh trip. Each visit takes that depot’s service_duration_s and is charged its cost_per_visit, and appears in the response as a reload stop. Cap the number of trips with max_trips. See Constraints.
object
Load already on the vehicle at shift start, per named dimension. It occupies capacity on the first trip only.

Depots

problem.depots[] lists the places a shift can start from, end at, or reload at, referenced by id from shifts[].start, shifts[].end, and shifts[].reloads:
Each depot has an id, a location, an optional service_duration_s (handling time per visit: the load before a shift that starts there, the unload after a shift that ends there, and each reload) and an optional cost_per_visit (charged on reload visits only).

Relations

A single relations array expresses every ordering, grouping, and timing rule between tasks. Each relation is a tagged object with a type, and every relation is hard.
same_resource, same_day and time_lag constrain the members that end up assigned: an unassigned member imposes nothing on the others. On evaluate, a plan that breaks a relation reports a violation named after its type; the first-stop pin of a single-entry ordered relation reports as first_job.
These five are the complete set. Any other type value is a 400 with an invalid_type item. See Constraints for the selection rules and the combinations that are rejected.

Relations and shipments

Relations act on jobs. What happens when a relation names a shipment depends on the type:
A shipment id in job_ids or tasks, or a shipment carrying the group tag of a same_resource or same_day relation, returns 200 with that shipment left out of the rule. Do not rely on a relation to order, group, or synchronise a shipment. A shipment’s own pickup-before-delivery order on one route always holds.

Objective

The whole block is optional. Omit it and the solver serves every task it can, then minimises travel cost at the default rates.
  • costs: fleet-wide rates.
    • per_travel_km is charged per kilometre driven. Default 0.20.
    • per_route_hour is charged per hour of each route’s span from start to finish (travel, service, waiting, and breaks; routes[].shift_duration_s). Default 35.
    • per_wait_hour and per_overtime_hour are extra rates for waiting time and for time past a shift’s to. Default 0.
    • per_late_hour prices lateness on every time window that does not carry its own time_windows[].per_late_hour, making those windows soft on the late side. Omit it to keep windows hard; an explicit 0 is a 400.
    • per_vehicle is a flat charge for each route that serves at least one task (a vehicle has one route per shift). Default 0.
    • currency is an informational label echoed as score.currency. It does not convert anything.
    The two defaults apply per rate: setting one rate leaves the other at its default.
  • non_preferred_date_cost: flat charge for serving a task on a day other than its preferred_date.
  • grouping: keeps tasks that lie within radius_m of each other consecutive in a route by charging cost_per_reentry (at most 1.0) each time a route leaves such a group and returns. Present = enabled. radius_m defaults to 180. distance selects how the radius is measured: straight_line (default) or road.
  • minimize_vehicles: a boolean that is accepted but does not change the plan on its own. With or without it, the number of vehicles used is whatever minimises total cost. To consolidate work onto fewer vehicles, put a price on using one: costs.per_vehicle for the whole fleet, or vehicles[].cost.fixed per vehicle. Sending minimize_vehicles: true together with costs.per_vehicle is a 400; omit the flag or send false.
Soft penalties that belong to a specific task live on that object (drop_fee, preferences[].violation_cost, target.per_late_hour, time_windows[].per_late_hour), not in this block. See Objective function for how plans are ranked, and Constraints for every hard and soft rule.

Options and travel

options sits next to problem at the top level of a solve request:
integer
default:"1"
Search time budget in whole seconds. Maximum 300; a larger value is a 400. 0 is raised to a 100 ms minimum. This is search time only: the travel-matrix fetch and any wait for capacity come on top.
integer
default:"42"
Random seed for the search. Repeated runs with the same seed on the same hardware usually return the same plan. Omitting seed is the same as sending 42, so to explore other plans you have to send different values.
string
simplified or full. When set, each route carries its road geometry as routes[].polyline (an encoded polyline, precision 5). Omitted = no geometry.
/v3/routing/suggest has its own options (the tasks to place and how many suggestions to return). See Suggest. /v3/routing/evaluate takes no options. See Limits for request size and count caps, and the Performance guide for choosing a time budget. problem.travel controls where travel times and distances come from:
string
default:"tomtom"
tomtom uses typical traffic for the departure time. osm uses free-flow times with no traffic. tomtom_real_time is not enabled and returns 403 feature_not_enabled.
string (ISO 8601 instant)
The instant the traffic lookup is made for, such as tomorrow’s 08:00 to plan with morning-rush travel times. Omitted = traffic at the time of the request. Rejected with 400 alongside engine: osm.
See Distance matrices for how travel data is looked up and what it drives in the plan.

Response

A solve returns a summary, per-stop routes, the same routes as a plan, and unassigned tasks with structured reasons. The example below serves two jobs on one route with a driver break and a mid-shift reload, and leaves a third job unassigned. It is a standalone illustration, priced at the default rates (35 per hour, 0.20 per km) with no per-vehicle or depot cost:
The sections below list every field of a solve response. Solution quality explains how to judge a plan from them.

Summary

string
solved when unassigned[] is empty, infeasible when jobs_assigned is 0, and partial otherwise. For a request with jobs only that reads: every job is on a route, none is, or some are. All three are HTTP 200; check status and unassigned[] rather than the status code. With shipments, see Counting with shipments.
integer
The number of entries in routes[]. A route is one vehicle on one shift, so a vehicle that works two shifts counts twice.
integer
For a request with jobs only, the number of jobs on a route.
integer
The number of entries in unassigned[]: one per unassigned job or shipment.
integer
The sum of routes[].distance_m, in metres.
integer
The sum of routes[].duration_s, in seconds. This is driving time only: it excludes service, waiting, and breaks, and it is not what the time cost component is charged on.
object
violations, unserved_priority, cost, currency, and components. violations is always 0 on a solve. unserved_priority is the summed priority of must-serve tasks left unassigned (a task that priced a drop_fee counts in components.drop_fees instead). Every component is always present and the components sum to cost. currency appears only when you sent objective.costs.currency. See Objective function.
integer
Wall-clock time the request took, in milliseconds.
integer
The number of search iterations completed within the time budget.

Counting with shipments

jobs_assigned counts stops, not tasks. It is the number of jobs plus twice the number of shipments in the request, minus jobs_unassigned. A served shipment therefore adds 2 (its pickup and its delivery), and an unassigned shipment still adds 1. For example, a request with 3 jobs and 2 shipments that leaves one shipment unassigned returns jobs_unassigned: 1 and jobs_assigned: 6. When the request contains shipments:
  • jobs_assigned + jobs_unassigned is not the number of tasks you sent. To count the tasks served, subtract the length of unassigned[] from the number of jobs and shipments in the request.
  • status is partial, not infeasible, when nothing could be served, because jobs_assigned is never 0. An empty routes[] tells you that nothing was served.
  • solved keeps its meaning: unassigned[] is empty.
The summary of an evaluate response counts the same way.

Routes

routes[] has one entry per route that serves at least one task. A shift with no work has no entry.
string
The vehicle id from the request.
string
The id of the shift this route runs on.
integer
Total distance driven on the route, in metres.
integer
Driving time only, in seconds: the sum of the stops’ travel_time_s.
integer
The route’s span in seconds, from the start stop’s arrival to the end stop’s departure. It includes service, waiting, and breaks, and is what per_route_hour is charged on.
integer
Seconds the route runs past the shift’s to; 0 when it finishes within the shift.
object
The highest load reached on the route, as a named map. Absent when the request names no load dimension.
array
The stops in order, including the start and end stops. See Stops.
string
The route’s road geometry as an encoded polyline (precision 5). Present only when you set options.polylines, and absent for a route whose geometry could not be produced. distance_m and duration_s come from the travel data the plan was built on, so rely on them if they differ from the geometry.

Stops

string
One of start, job, pickup, delivery, break, reload, end. Every route has one start and one end. pickup and delivery are the two legs of a shipment.
string
On a job stop, the job id. On a pickup or delivery stop, that leg’s own id, or "{shipment}:pickup" / "{shipment}:delivery" when it has none.
string
The depots[].id of the stop: on every reload stop, and on a start or end stop when the shift names a depot there.
array
The stop’s coordinate as [longitude, latitude]. Absent on break stops and on the start or end stop of a route that is open on that side. A virtual job reports the coordinate of its nearest located neighbour.
array
The same point snapped to the road network, when available.
string (ISO 8601 instant)
Arrival at the stop, before any waiting. Service starts at arrival + wait_s. Always UTC with a Z suffix, whatever offset the request used.
string (ISO 8601 instant)
Departure from the stop, in UTC.
integer
Seconds spent waiting before service starts: for the time window to open, and for any hold reported in hold_s.
integer
The part of wait_s spent holding for a synchronized or time_lag partner on another vehicle. Present only when it is above 0.
integer
Time spent at the stop, in seconds. On a task stop this is the effective service time, after the vehicle’s service_factor, so it can differ from the service_duration_s you sent; setup_duration_s is not included. On a start, end, or reload stop it is the depot handling time, and on a break stop the length of the break.
integer
Driving time from the previous stop, in seconds.
integer
Distance from the previous stop, in metres.
integer
Seconds the arrival is past the latest arrival the task’s time window allows; 0 when on time. A target is reported separately, in target_lateness_s.
integer
Seconds past the task’s target.at, measured at arrival or departure as target.event says; 0 when met. Present only on a stop whose task has a target. For a shipment it is on the delivery stop.
object
The load on the vehicle after this stop, as a named map. Absent when the request names no load dimension.
Which stop types carry which fields: routes[].violations and routes[].stops[].violations appear only in an evaluate response. A solve response never has them.

Plan

object
The same routes in the request’s plan vocabulary: plan.routes[], each with vehicle, shift, and stops[] of type job, pickup, delivery, or reload. Feed it straight into evaluate, suggest, or a warm-started solve. See Warm-starting with plan.

Unassigned tasks

unassigned[] has one entry per job or shipment that is not on a route. It is an empty array when everything is assigned.
string
The id of the unassigned job, or the shipment id for a shipment.
string
Always present, one of: impossible (no vehicle can ever serve it), blocked (a vehicle could serve it alone, but not alongside the tasks the plan serves), priced_out (paying its drop_fee was cheaper than serving it), or search_limit (a worthwhile placement exists but was not found within the time budget, the one case a longer time_limit_s can help).
array
{ code, message } objects from a closed set of codes (TIME_WINDOW_VIOLATED, CAPACITY_EXCEEDED, SKILLS_MISMATCH, …). Branch on code; message is for people. See Reason codes.
array
Best-effort suggestions for a change that would let the task be served. Empty when no simple relaxation exists. Each entry has:
  • op: extend_time_window or reduce_demand.
  • amount and unit: the size of the change. unit is s for seconds on extend_time_window and load on reduce_demand.
  • field: a label for what to change, /jobs/{id}/time_windows or /jobs/{id}/demand.
  • suggestion: the same advice as a sentence for people.

Pointers and field paths

Two fields look like paths into the request. They follow different rules: Treat field as a label: look the task up by job_id and apply op and amount. See Errors for the pointer rules.

Warm-starting with plan

The request-level plan seeds the search from an existing plan (re-optimisation) instead of constructing from scratch:
Each route names a vehicle and a shift id (the vehicle’s shift this plan fills), an ordered stops list, and an optional locked_count: how many leading stops the search must not move (a dispatched prefix; cannot be combined with a reload stop on the route). A task stop has a type of job, pickup, or delivery and an id, as in { "type": "job", "id": "job-1" }. A shipment leg’s id is its own id, or "{shipment}:pickup" / "{shipment}:delivery" when it has none. A reload stop is { "type": "reload", "depot": "..." }, naming a depot from that shift’s reloads and splitting the route into a new trip. Start, end, and breaks are placed by the solver and are never listed. plan is the same shape a solve response returns, so result.plan goes back in unchanged. A plan that names an unknown vehicle, shift, depot, or stop id, lists a stop twice, or is otherwise malformed is rejected with 400. The item’s pointer is /plan/routes/{i} or, for a stop-level problem, /plan/routes/{i}/stops/{k}. plan is a hint, not a guarantee on solve:
  • An unlocked stop that violates a hard constraint (capacity, skills, eligibility, a limit, or a hard time window) is taken out of its position. The search reinserts it wherever it fits, or it appears in unassigned[] with a reason and status is partial.
  • A locked stop (inside locked_count) that violates a hard constraint can be neither moved nor dropped, so the request fails: 400, item code invalid_value, pointer /plan, with a message naming the stop, the vehicle, and the rule it breaks.
  • Time windows are hard by default, so a stop the plan serves after its window closes is treated as violating. Price lateness (time_windows[].per_late_hour or objective.costs.per_late_hour) and the stop is kept.
The checks on a plan’s routes and stops run after the travel matrix has been fetched, on all three endpoints, so ?validate_only=true does not cover them. To score a plan as it stands, without searching for a better one, send it to POST /v3/routing/evaluate. It fetches the travel matrix like a solve does, then returns the plan’s timing, loads, and score, with each broken rule listed as a violations[] entry on the stop or route instead of dropping the stop. See Evaluate.

Errors

The three endpoints report errors in one JSON body. A 401 and a 429 are the exceptions: they are answered before the request reaches the routing service and can have a different body or none, so branch on the HTTP status for those two. See Errors. A request that does not parse or does not validate is HTTP 400:
  • errors[] has one item per problem found. errors[].code says what kind of problem it is (unknown_field, missing_field, invalid_type, invalid_value, not_supported, out_of_range, limit_exceeded, …).
  • errors[].pointer, when present, is a JSON Pointer into the request body you sent, such as /problem/depots/0/service_duration_s, /plan/routes/0/stops/2, or /options/time_limit_s. It usually ends at the offending key and sometimes at the object that contains it; the message always names the field. It is a different notation from relaxations[].field: see Pointers and field paths.
  • With one item, the top-level message repeats that item’s message; with several it reads "{n} problems in request".
  • Branch on code and errors[].code; the messages are for people.
See Errors for every item code, retry guidance, and authentication failures.

Constraints

Every constraint field, hard vs. soft, with examples.

Evaluate

Score an existing plan and list the rules it breaks.

Suggest

Rank insertion positions for new tasks in an existing plan.

Objective function

How plans are ranked and how score is built.

Solution quality

How to judge a plan from the response fields.

Distance matrices

Where travel times and distances come from.

Migration from V2

Field-by-field mapping from the V2 VRP API.

API reference

Full request and response schema for every endpoint.