Route Optimization
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
{ 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.
| Field | Type | Notes |
|---|---|---|
stops | {lat,lng}[] | required — index 0 is the depot |
numVehicles | number | default 1 |
costing | string | matrix profile: auto (default), truck, bicycle, pedestrian |
tier | string | fast · balanced (default) · best — more search time = better routes |
demands | number | per-stop load (same length as stops) |
vehicleCapacities | number | per-vehicle capacity (enables CVRP) |
timeWindows | [earliest,latest][] | per-stop service window, seconds (enables VRPTW) |
serviceTimes | number | per-stop dwell time, seconds |
pickupsDeliveries | [pickupIdx,deliveryIdx][] | enforces pickup-before-delivery on the same vehicle |
dropPenalty | number | allow the solver to skip a stop at this cost (handles infeasible loads) |
maxRouteDuration | number | per-vehicle cap, seconds |
openEnded | boolean | vehicles 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).
| Field | Type | Notes |
|---|---|---|
vehiclePositions | {lat,lng}[] | required — one current position per active vehicle |
remainingStops | {lat,lng}[] | required — stops not yet served |
costing | string | matrix profile, default auto |
tier | string | default balanced |
demands | number | per-remaining-stop load |
vehicleCapacities | number | per-vehicle remaining capacity |
dropPenalty | number | allow 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.
| Field | Type | Notes |
|---|---|---|
itemWeights | number | required — weight of each item |
binCapacities | number | required — 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.
| Field | Type | Notes |
|---|---|---|
numDrivers | number | required |
numDays | number | required — planning horizon, e.g. 7 |
shiftsPerDay | number | default 2 (e.g. morning / evening) |
demand | number[][] | required — demand[day][shift] = min drivers needed |
maxShiftsPerDriver | number | cap on shifts per driver in the window |
minRestDays | number | minimum 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.
| Field | Type | Notes |
|---|---|---|
kind | string | routes (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.
best tier.Quality tiers
| Tier | Search time | Use for |
|---|---|---|
fast | ~2s | live re-planning, small problems, interactive UIs |
balanced | ~6s | the default — good routes for daily planning |
best | ~20s | overnight / batch planning of large fleets via a job |
Related
- Fleet & Logistics guide — end-to-end delivery operations using the optimizer.
- Routing — the engine that powers the travel-time matrices.