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
numVehiclesnumberdefault 1
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
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.

For small problems (a single driver, a handful of stops) call the synchronous endpoints directly — they return in well under a second. Reserve jobs for large fleets and the best tier.

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