Skip to main content
The V3 Routing API is a ground-up redesign of how you describe a vehicle routing problem. Instead of dozens of overlapping special-case fields, it exposes a few orthogonal, composable primitives. Everything optional defaults to sensible behaviour, so a minimal request is two objects and a location.
The V3 model is rolling out in phases. The request schema and the core optimisation (named capacity, hard time windows, skills, vehicle eligibility, sequencing, pickup & delivery shipments, the priorities objective) are available now. Advanced capabilities — driver breaks, depot reloads, soft time windows, cross-vehicle synchronisation — are reserved in the schema and currently return a clear 400 naming the field. See Availability below.

Design principles

Few orthogonal primitives

One concept, one mechanism. Vehicle eligibility, skills, and sequencing are each a single field — not five overlapping ones.

Everything soft is money

No abstract weight vector. Lateness costs a rate per hour; a non-preferred vehicle costs an amount. One unit you trade against, and an itemised cost breakdown in every response.

Penalties live on the thing

A preference is a property of a job, so its violation cost sits on the job — read one job and you see exactly what it costs to bend each rule.

Pit of success

Every advanced field is optional with a no-op default. You never see eligible_vehicles or objective until you need them.

A request at a glance

The smallest valid request — minimise total time, serve everything:
A request has up to seven top-level fields: jobs, vehicles, depots, locations, relations, objective, and options. Only jobs and vehicles are required.

Jobs

A job is a unit of work — a delivery, pickup, service call, or inspection. A single-visit job has its visit fields (location, service_duration_s, time_windows) directly on the object. Shipments use pickup + delivery instead (Coming soon).
Place
Where the job is performed. A Place is one of:
  • { "coordinate": [lon, lat] } — inline GeoJSON coordinate (Available)
  • { "location_id": "..." } — reference into locations[] (Coming soon)
  • { "depot": "..." } — reference into depots[] (Coming soon)
array
One or more time windows. Each window is { "from": "...", "to": "..." } (ISO 8601). Hard by default — arriving outside the window makes the job unassignable.Soft windows with earliness_cost_per_hour / lateness_cost_per_hour are Coming soon.
object
Named multi-dimensional load, e.g. { "weight": 30, "volume": 2 }. Vehicle capacity uses the same dimension names — matched by name, so order never matters and errors name the overflowing dimension exactly. Available.
boolean
default:"false"
true = the job must be served; the solve is infeasible if it cannot be. false (default) = droppable. Use unassigned_cost to price the drop. Available.
number
Cost of leaving this job unassigned. The solver drops the job only when the saving exceeds this price. Ignored when mandatory: true. Available.
array
Capability requirements: [{ "name": "fridge" }]. The serving vehicle must have all named skills. Hard (no violation_cost) is Available. Soft skills with violation_cost are Coming soon.
object
Hard vehicle filter. allowed is a whitelist (omit = all vehicles eligible); excluded is a blacklist. Available.Soft vehicle preferences (preferences: [{ "vehicle": "v2", "violation_cost": 800 }]) are Coming soon.

Vehicles & shifts

A vehicle carries capacity, skills, a cost model, and one or more shifts. The shift is where the working window and depot locations live.
number
default:"1"
Clone this vehicle definition N times. Response carries { "type": "truck-1", "instance": 1 }{ "instance": N } so results stay traceable. Available.
object
Named dimensions, matched against job demand by name. Available.
object
Per-vehicle route caps. max_distance_m, max_drive_time_s, max_duty_time_s, and max_leg_drive_time_s are hard bounds. Available.
object
Per-vehicle cost rates. fixed is charged when the vehicle is used; per_travel_hour is the travel rate. per_km, per_stop, per_service_hour, per_wait_hour, and per_overtime_hour are Coming soon for the multi-term cost model.
Place
Per-shift depot location. start is where the vehicle departs; end is where it must return. Omit end for an open route (no return leg). Depot references by ID are Coming soon — use inline coordinate for now. Available (inline coordinate).
integer
How many seconds past to the vehicle may run, priced via cost.per_overtime_hour. Available.
integer
Maximum number of job stops for this shift. Lives on the shift, not on vehicle.limits. Available.
array
Scheduled breaks inserted into the route timeline. Coming soon.
array
Mid-route depot visits to reload capacity. Coming soon.

Relations

A single relations array expresses every inter-job constraint. Each relation is a tagged-union object identified by type. violation_cost absent (or omitted) = hard; a positive value = soft (Coming soon for soft relations).
A relation references jobs either explicitly via job_ids, or by tag via the separate group field — never both. { "type": "ordered", "group": "urgent" } orders all jobs carrying the urgent tag relative to each other, leaving order within the group free.

Objective

The optimisation goal is one coherent, money-denominated model. The whole block is optional — omit it for “minimise total time, serve everything”.
  • priorities — an ordered list of lexicographic tiers. minimize_vehicles makes the solver use as few vehicles as possible before optimising cost; drop it and vehicle count becomes free.
  • costs — fleet-default rates. per_travel_hour is available now. per_travel_km, per_late_hour, per_wait_hour, and per_overtime_hour are Coming soon for the multi-term cost model.
Soft penalties that belong to a specific job live on that object (skills[].violation_cost, preferences[].violation_cost) — not in a global table.

Response

A solve returns a summary, per-stop routes, and unassigned jobs with structured reasons.
Key response fields:
  • summary.statussolved, partial, or infeasible
  • summary.estimated_cost — always present, itemised by component
  • routes[].vehicle{ "type": "...", "instance": N } for multi-count vehicles
  • routes[].stops[].typestart, task, pickup, delivery, break, reload, end
  • routes[].stops[].load_after — named load map after serving this stop
  • unassigned[].job_id — the id of the unassigned job
  • unassigned[].reasons — structured { code, message } objects (no legacy reason string)

Availability

The V3 schema is stable. Capabilities land in phases — anything not yet wired returns a 400 that names the unsupported field, so you never get a silently-wrong plan.

API Reference

Full request and response schema for POST /v3/routing/solve.
Migrating from the V2 VRP API? See the V2 → V3 migration guide.