Skip to main content
V3 is in beta. General availability (GA) is planned for January 2027.
You have a customer’s raw data and must turn it into a POST /v3/routing/solve request a dispatcher can trust. Each rule says what the API does when you get it wrong, and what to hand back.
Written for AI agents. If you are a person, start at the Quickstart instead.

At a glance

Integration adds the API to an existing app. Modelling turns raw data into a plan; every rule applies to it.

1. Validate before you solve

Send every request you build to POST /v3/routing/validate/solve (or /validate/evaluate, /validate/suggest) before the real call. It runs the same request checks and stops there: no travel lookup, no search, no charge. You get 200 {"valid": true, "warnings": []} or the 400 or 403 the real call would return for those checks. Two checks need travel data and wait for the real call: coordinates spanning more than one region, and whether the plan imports. One plan check does run here: /validate/suggest rejects a plan whose stop ids are exactly the problem’s task ids, since that leaves nothing to place. The real call checks the rest of the plan when it imports it. Deliver: the validate response, including warnings[].

2. Never guess a field name

Take every field name from the API reference. Do not carry names over from V2, another routing API or the customer’s columns. Every V3 object rejects fields it does not define. A typo, a camelCase name, a V2 field or a field on the wrong object is a 400 with item code unknown_field and a pointer to the key; nothing is ignored. Deliver: a mapping from each customer column to the V3 field it fed.

3. Treat time windows as hard unless you price them

Model “between 9 and 12” as a hard time_windows entry only when the owner confirms service outside it is unacceptable. Windows are hard by default and bound the start of service. A task that cannot start inside any window is left unassigned with constraint time_window. V3 prices lateness in three ways. objective.per_late_hour allows a late start on every window in the fleet, charged per hour late; omit it for hard windows, since an explicit 0.0 is a 400. time_windows[].per_late_hour does the same for one task, which must then have exactly one window. target is a soft deadline on one task, independent of its windows, with its own per_late_hour. Early arrival always waits; the shift end plus max_overtime_s stays hard. Each rate has a maximum: a deadline that must hold is a hard window. Deliver: each window type, hard or priced, and who confirmed it.

4. Choose drop_cost or priority deliberately

Decide per task: may the plan leave it out, or must it be served? drop_cost is the only way to make a task optional: the solver serves it while that costs less than the fee, otherwise pays the fee and reports it as priced_out. priority (default 1) ranks must-serve tasks: when not all fit, the solver maximises the summed priority of the served tasks before it looks at cost. It never makes a task droppable. Both on one task is a 400 conflicting_fields; priority: 0 and a negative drop_cost are out_of_range. Deliver: the rule you applied per task class, and where it came from.

5. Never fabricate or round a coordinate

Use the coordinates in the data, as [longitude, latitude], at full precision. If a stop has no geocode, do not derive one from a town name or a neighbour: leave the task out and list it in the report. A coordinate in [latitude, longitude] order lands in the wrong country: a request spanning more than one region is a 400 invalid_value at /problem/locations; otherwise every task comes back unassigned with constraint unreachable, as does a stop rounded off the road. A plausible but wrong coordinate gives no error at all: the solver plans a correct route to the wrong place. Deliver: stops with a real geocode, and stops left out for lack of one.

6. Read unassigned[].reasons before you tune

When summary.tasks.unassigned is above 0, read unassigned[] before you change a field or raise options.time_limit_s. A 200 is not a feasibility verdict. Start with category: impossible means no vehicle can ever serve the task, so fix the input; blocked means it fits alone but not alongside the planned tasks; priced_out means its drop_cost was cheaper than serving it; only time_limit is fixed by more search time. Then read reasons[].constraint for the rule that refused it, and relaxations[] for the smallest change that would let it in. Report patterns, not tasks: “14 unassigned tasks all require skill fridge, which no vehicle has” is a finding. Before relaxing a rule, check capacity against demand and name the business rule you weaken. Deliver: the unassigned list grouped by category and constraint.

7. Use straight_line for smoke tests only

Set problem.travel.engine to straight_line while you check that your mapping produces a valid request and a plan. Remove it before a dispatcher drives the plan. straight_line uses great-circle distance at travel.speed_kmh (default 50, from 1 to 1000), with no road network. It needs no travel provider, so it suits CI, but rivers and motorways do not exist. The default engine, osm, uses free-flow road times (tomtom becomes the default when travel.departure_time is set); speed_kmh with any other engine is a 400. Deliver: the engine the delivered request uses.

8. Ask the operation owner on irreducible ambiguity

When the data does not say and the API has no safe default, ask the person who runs the operation instead of picking a value. Typical cases: is a contractual window hard or priced, and which tasks may be dropped. Each is a business rule. If you default it, the plan encodes a rule nobody agreed to, and the API cannot warn you: the request is valid. Ask the questions in one round; while an answer is pending, state your assumption and keep it out of the request. The requirements briefing lists the questions. Deliver: each question with its answer and who gave it, or marked open.

9. Send no placeholder values

Send a field only when you have its value. Never fill one with 0, 9999, today’s date or a made-up id to get past validation. The API cannot tell a placeholder from a measurement. A service_duration_s of 0 makes every stop instantaneous; a capacity of 9999 switches the capacity rule off; neither returns an error. Most fields are optional: a job without time_windows can be served at any time in a shift, and a vehicle with no capacity field has no load limit. Omitting an unknown field is honest; inventing a value is a silent modelling decision. Deliver: every omitted field, with the reason.

10. Deliver a modelling report

Hand back the request with a report; nobody can review a 2,000-line JSON body. First, a field-status table, one row per V3 field you set or deliberately omitted: Source is data export (name the column), owner answer (name who) or default (name who agreed). Second, the unassigned list from the last solve, grouped by category and reasons[].constraint, plus the open questions from rule 8. Close with two or three sentences about what this plan showed, not a generic next-steps list.

Reading order

  1. Integration paths: which endpoints to call, in what order, for an integration versus a modelling job.
  2. API design, Constraints and Errors for the full detail.