Navvo
API Reference

Route Optimization

Self-hosted OR-Tools route optimization — VRP/VRPTW with capacity, time windows, pickup-delivery and multi-depot, plus bin-packing, driver rostering, live re-optimization, and async optimization-as-a-job.

Route Optimization

Navvo Optimize solves the hard combinatorial planning problems behind delivery, field-service and logistics operations: in what order, and on which vehicle, should a set of stops be visited to minimize total drive time — subject to vehicle capacity, customer time windows, pickup-before-delivery precedence, and multiple depots.

It is powered by a self-hosted OR-Tools solver fronted by Navvo. Travel-time matrices are built from the Navvo routing engine, so the optimizer reasons over real road network drive times — not straight-line distance. Because it is self-hosted there is no per-call optimization fee.

Scope required: optimize

The optimizer never touches the road network directly. Navvo builds an N×N travel-time matrix from the routing engine, then hands the pure combinatorial problem to the solver. All coordinates are { lat, lng }.

GET /optimize/health

Liveness probe for the optimization service. Use it before submitting large jobs.

{ "provider": "navvo-optimize", "available": true, "ok": true,
  "service": "navvo-optimize", "solvers": ["routing", "cp-sat"] }

When available is false, submit work as an async job (see Optimization-as-a-job) and retry, or fall back to your own ordering.


POST /optimize/routes

The core Vehicle Routing Problem solver (CVRP / VRPTW). stops[0] is the depot; the remaining stops are the visits to sequence across numVehicles.

FieldTypeNotes
stops{lat,lng}[]required — index 0 is the depot, 2–500 stops
numVehiclesnumberdefault 1, max 200
costingstringmatrix profile: auto (default), truck, bicycle, pedestrian
tierstringfast · balanced (default) · best — more search time = better routes
demandsnumberper-stop load (same length as stops)
vehicleCapacitiesnumberper-vehicle capacity (enables CVRP)
timeWindows[earliest,latest][]per-stop service window, seconds (enables VRPTW)
serviceTimesnumberper-stop dwell time, seconds
pickupsDeliveries[pickupIdx,deliveryIdx][]enforces pickup-before-delivery on the same vehicle
dropPenaltynumberallow the solver to skip a stop at this cost (handles infeasible loads)
maxRouteDurationnumberper-vehicle cap, seconds
openEndedbooleanvehicles finish at their last stop instead of returning to the depot
curl -X POST https://api.navvo.io/v1/optimize/routes \
  -H 'authorization: Bearer <API_KEY>' -H 'content-type: application/json' \
  -d '{
    "stops": [
      {"lat":31.9539,"lng":35.9106},
      {"lat":31.9700,"lng":35.9200},
      {"lat":31.9400,"lng":35.8800},
      {"lat":31.9800,"lng":35.9300}
    ],
    "numVehicles": 1, "tier": "balanced"
  }'
{
  "provider": "navvo-optimize",
  "feasible": true,
  "objective": { "totalDurationSec": 1840, "vehiclesUsed": 1 },
  "dropped": [],
  "routes": [
    { "vehicle": 0, "durationSec": 1840,
      "order": [0, 2, 1, 3],
      "stops": [ {"lat":31.9539,"lng":35.9106}, {"lat":31.94,"lng":35.88}, "…" ] }
  ]
}

order is the visit sequence as indices into your stops array; stops is the same sequence resolved back to coordinates. Any stop the solver chose to skip (only possible when dropPenalty is set) appears in dropped.


POST /optimize/reoptimize

Live mid-day re-planning. Given each vehicle's current position and the stops still to be served, it re-plans the remainder as open-ended routes (vehicles finish at their last drop).

FieldTypeNotes
vehiclePositions{lat,lng}[]required — one current position per active vehicle
remainingStops{lat,lng}[]required — stops not yet served, combined with vehiclePositions up to 500
costingstringmatrix profile, default auto
tierstringdefault balanced
demandsnumberper-remaining-stop load
vehicleCapacitiesnumberper-vehicle remaining capacity
dropPenaltynumberallow skipping a stop
{
  "provider": "navvo-optimize", "feasible": true,
  "objective": { "totalDurationSec": 920, "vehiclesUsed": 2 },
  "dropped": [],
  "routes": [ { "vehicle": 0, "durationSec": 540, "stops": [ "…remaining stops in order…" ] } ]
}

The vehicle's current position is stripped from the returned stops — you only get the remaining stops in their new order.


POST /optimize/binpack

CP-SAT bin packing — assign weighted items to capacity-limited containers (which parcels fit which vehicle), minimizing the number of containers used.

FieldTypeNotes
itemWeightsnumberrequired — weight of each item
binCapacitiesnumberrequired — capacity of each available bin
curl -X POST https://api.navvo.io/v1/optimize/binpack \
  -H 'authorization: Bearer <API_KEY>' -H 'content-type: application/json' \
  -d '{ "itemWeights": [3,5,2,8], "binCapacities": [10,10] }'
{ "feasible": true, "assignment": [1, 1, 1, 0], "binsUsed": 2 }

assignment[i] is the bin index chosen for item i. feasible:false means the items cannot fit in the given bins (e.g. total weight exceeds total capacity).


POST /optimize/roster

CP-SAT driver rostering / shift scheduling. Assigns drivers to day/shift slots so every shift's minimum staffing is met, while balancing load fairly and respecting availability and rest rules.

FieldTypeNotes
numDriversnumberrequired
numDaysnumberrequired — planning horizon, e.g. 7
shiftsPerDaynumberdefault 2 (e.g. morning / evening)
demandnumber[][]required — demand[day][shift] = min drivers needed
maxShiftsPerDrivernumbercap on shifts per driver in the window
minRestDaysnumberminimum days off per driver
forbidden[driver,day][]unavailable slots (leave / weekly rest)
{
  "feasible": true,
  "shifts": [ { "driver": 0, "day": 0, "shift": 0 }, "…" ],
  "perDriver": [5, 4, 5, 4],
  "peakLoad": 5
}

peakLoad is the busiest driver's total shifts — the solver minimizes it to keep the roster fair.


Optimization-as-a-job

Large problems (best tier, many stops) can take 20s or more — too long for a synchronous request. Submit them as a job: you get a jobId back instantly, then poll for the result.

POST /optimize/jobs

Enqueue any solve as a background job. The body is the same as the corresponding synchronous endpoint, plus a kind selector.

FieldTypeNotes
kindstringroutes (default) · reoptimize · roster · binpack
…—all other fields are the body of the matching endpoint above
curl -X POST https://api.navvo.io/v1/optimize/jobs \
  -H 'authorization: Bearer <API_KEY>' -H 'content-type: application/json' \
  -d '{ "kind": "routes", "tier": "best", "numVehicles": 5, "stops": [ "…" ] }'
{ "jobId": "71005767-63e7-4dbc-af7a-95c10aa18864", "status": "running", "kind": "routes" }

GET /optimize/jobs/:id

Poll a job. While running, status is running; on completion it is completed with a result (the same payload the synchronous endpoint returns), or failed with an error.

{
  "id": "71005767-63e7-4dbc-af7a-95c10aa18864",
  "kind": "routes", "status": "completed",
  "createdAt": 1782572676434, "finishedAt": 1782572678901,
  "result": { "feasible": true, "routes": [ "…" ], "objective": { "totalDurationSec": 1840 } }
}

An unknown id returns { "error": "job not found", "status": "unknown" }. Jobs are transient (in-memory) — poll until done and persist the result on your side; do not rely on a job surviving indefinitely.

Measured on production: 200 stops at fast tier with one vehicle returns in about 5 seconds; the same 200 stops at balanced tier across 5 vehicles takes about 9 seconds. Both are fine to call synchronously — an HTTP client just needs a timeout well above a few seconds, not milliseconds. Reserve jobs for the best tier or the upper end of the stop range, where solve time is dominated by search quality rather than by building the travel-time matrix.

Quality tiers

TierSearch timeUse for
fast~2slive re-planning, small problems, interactive UIs
balanced~6sthe default — good routes for daily planning
best~20sovernight / batch planning of large fleets via a job
Copyright © 2026