Routing
Routing
Directions and route analysis, powered by Navvo's routing engine. The API offers a low-level passthrough to the routing engine (/route, /matrix, /isochrone, /map-match, /optimize) and a high-level Navvo planner (/plan) that builds the request for you and adds AI labels, constraint parsing, and logistics-zone risk.
Scope required: routing
- The passthrough endpoints (
/route,/matrix,/isochrone,/map-match,/optimize) forward a raw routing-enginepayload, so locations use the routing engine's native{ "lat": …, "lon": … }objects (notelon). /planuses a Navvo body with{ "lon": …, "lat": …, "name"? }location objects (keys are named, so order doesn't matter).
201 with available: false. Always check available.The response envelope
Passthrough endpoints wrap the routing engine's reply:
{ "provider": "navvo", "available": true, "path": "route", "data": { /* raw routing-engine response */ } }
On engine failure:
{ "provider": "navvo", "available": false, "path": "route", "request": { /* your payload, echoed */ } }
POST /routing/*, and POST /traffic/route) requires an identified caller — a signed-in Navvo user's Bearer token or your developer API key (x-api-key). Anonymous requests return 401. GET /routing/profiles stays public.POST /routing/route
Turn-by-turn directions between locations. Proxies the routing engine's /route and attaches an AI-generated labels block (a short label + tip for the route).
Request body
Common payload fields: locations (array of {lat, lon, type?}), costing (auto, pedestrian, bicycle, truck, motor_scooter, bus, taxi, …), costing_options, directions_options (units: kilometers/miles, language), alternates, date_time.
{
"payload": {
"locations": [
{ "lat": 31.95, "lon": 35.91 },
{ "lat": 31.99, "lon": 35.95 }
],
"costing": "auto",
"directions_options": { "units": "kilometers", "language": "en-US" },
"alternates": 2
}
}
Response (201)
data.trip is the standard routing-engine trip — legs[].maneuvers[] (turn-by-turn), legs[].shape (encoded polyline, precision 1e6), and summary (time in seconds, length in the requested units, has_toll/has_highway/has_ferry). The added labels object contains a best-effort parsed: { label, tip }.
{
"provider": "navvo",
"available": true,
"path": "route",
"data": {
"trip": {
"legs": [ { "maneuvers": [ { "type": 1, "instruction": "Drive northeast.", "time": 120.5, "length": 1.23 } ], "summary": { "time": 540.2, "length": 5.6 }, "shape": "ityp_AdpjpC…" } ],
"summary": { "has_toll": false, "has_highway": true, "time": 540.2, "length": 5.6 },
"status": 0, "units": "kilometers", "language": "en-US"
}
},
"labels": { "purpose": "route_labels", "parsed": { "label": "Fast city route", "tip": "Light traffic, mostly highways" } }
}
POST /traffic/route instead — it samples Navvo's live traffic along the route.GET /routing/profiles
The catalog of routing profiles (costing modes) plus the supported preferences, avoid options, and corridor constraints — for building a travel-mode picker. Each profile carries an available flag reflecting what this deployment can actually route.
curl -s "https://navvo.io/api/v1/routing/profiles" -H "x-api-key: $NAVVO_API_KEY"
{
"provider": "navvo-routing",
"profiles": [
{ "costing": "auto", "label": "Driving", "available": true, "description": "Car routing (default)." },
{ "costing": "bicycle", "label": "Cycling", "available": true },
{ "costing": "pedestrian", "label": "Walking", "available": true },
{ "costing": "wheelchair", "label": "Wheelchair", "available": true, "description": "Pedestrian profile with step/grade penalties." },
{ "costing": "truck", "label": "Truck", "available": false, "description": "Accepted by the API but requires truck-enabled routing tiles, not provisioned on this deployment." }
],
"preferences": ["fastest", "shortest", "balanced"],
"avoid": ["tolls", "highways", "ferries", "unpaved"],
"constraints": ["avoidIncidents", "avoidReported", "disregard"]
}
| Field | Notes |
|---|---|
profiles[].costing | the value to pass as costing to /routing/plan |
profiles[].available | whether this deployment can route the profile (truck needs truck-enabled tiles) |
preferences / avoid | accepted preference and avoid[] values |
constraints | corridor flags (avoidIncidents, avoidReported, disregard) |
POST /routing/plan
The recommended high-level endpoint. You give it stops and preferences; it builds the routing-engine request, picks route vs optimized_route automatically, and enriches the result with AI labels, optional natural-language constraint parsing, and logistics-zone risk analysis.
Request body
{ "lon": number, "lat": number, "name"?: string }. At least 2 required.auto (default), pedestrian, bicycle, motor_scooter, truck.fastest, shortest, or balanced (default). Tunes the costing options.tolls, highways, ferries, unpaved.true, reorders intermediate stops for the shortest tour (the routing engine's optimized_route). Optimization is also used automatically when you pass more than 2 locations.true. When enabled, active community road-blocking incidents (closures, accidents, construction, lane closures) within ~30 m of the planned corridor are automatically routed around. The response's incidentConstraints block reports whether any were applied. Set false to ignore live incidents.true. The close-the-loop override: blocking feedback reports (road closed / missing / illegal maneuver / hazard) that an editor has confirmed (status: ack) and that sit within ~30 m of the corridor are routed around. Unconfirmed reports never affect routing, and confirmed ones auto-expire after 60 days. The response's reportedConstraints block reports whether any were applied. Set false to ignore them.featureKeys to ignore — those no-entry signs will not constrain this route.GET /road-edits/geojson) — within ~70 m of the corridor are routed around (line closures become avoided points; area closures become avoided polygons). The response's roadEditConstraints block reports whether any were applied.constraints block.true. Set to false for an instant response that returns route geometry and alternates only, skipping the AI route label (which adds ~1–2s). Recommended for interactive/latency-sensitive clients.en-US. When set to English, road names in the
maneuver instruction and street_names are also localized to English
(name:en → int_name, then a phonetic Latin transliteration of the local name as a
last resort) so the on-screen and spoken guidance always read in Latin script even
where the map's default names are non-Latin (e.g. Arabic).{
"locations": [
{ "lon": 35.91, "lat": 31.95, "name": "Warehouse" },
{ "lon": 35.95, "lat": 31.99, "name": "Customer A" },
{ "lon": 35.88, "lat": 31.97, "name": "Customer B" }
],
"costing": "truck",
"preference": "fastest",
"avoid": ["tolls", "unpaved"],
"optimize": true,
"constraintsText": "Deliver to Customer A before 11:00, avoid downtown."
}
Response (201)
{
"provider": "navvo-routing",
"engine": { "provider": "navvo", "available": true, "path": "optimized_route", "data": { "trip": { } } },
"labels": { "parsed": { "label": "…", "tip": "…" } },
"constraints": { "purpose": "route_constraints", "content": "{\"orderedStops\":[…],\"timeWindows\":[…]}" },
"logistics": {
"applied": true,
"matchedZones": [
{ "slug": "amman-restricted", "name": "Amman Restricted Access", "zoneType": "restricted_access", "riskScore": 0.8, "costMultiplier": 1.25, "routingPolicy": "Permit required 06:00-09:00" }
],
"maxRiskScore": 0.8,
"totalCostMultiplier": 1.25,
"policySummary": "Permit required 06:00-09:00"
},
"routeProfile": { "costing": "truck", "preference": "fastest", "avoid": ["tolls", "unpaved"] }
}
| Field | Description |
|---|---|
engine | The routing route envelope with the raw data.trip. path is route or optimized_route. When alternate routes exist they are returned under engine.data.alternates[] (each item has its own trip), with data.trip being the fastest. |
labels | AI route label/tip. Empty when includeLabels is false. |
constraints | Present only if you sent constraintsText; otherwise null. |
logistics | Which Navvo logistics/SLA zones the route crosses, with risk and cost multipliers. applied: false when none intersect. |
routeProfile | Echo of the resolved costing/preference/avoid. |
signConstraints | { applied, noEntryCount, source } — no-entry signs routed around. |
incidentConstraints | { applied, blockingCount, source } — live road-blocking incidents routed around. |
reportedConstraints | { applied, blockingCount, source } — admin-confirmed blocking feedback reports routed around (close-the-loop). |
roadEditConstraints | { applied, blockingCount, source } — platform road edits (closures/restrictions) routed around. |
/plan always uses kilometers. Distances/durations live under the engine.data.trip.summary field.POST /routing/matrix
Travel-time / distance matrix between sources and targets. Proxies the routing engine's sources_to_targets endpoint.
Request body
{ "payload": <raw routing-engine matrix request> } — typically sources and targets (arrays of {lat, lon}), plus costing and units.
{
"payload": {
"sources": [ { "lat": 31.95, "lon": 35.91 }, { "lat": 31.99, "lon": 35.95 } ],
"targets": [ { "lat": 31.90, "lon": 35.93 } ],
"costing": "auto",
"units": "kilometers"
}
}
Response (201)
data.sources_to_targets[i][j] holds the element from source i to target j: time (seconds), distance (in units), and from_index/to_index. Unreachable pairs have null time/distance.
{
"provider": "navvo", "available": true, "path": "sources_to_targets",
"data": {
"sources_to_targets": [
[ { "from_index": 0, "to_index": 0, "distance": 6.43, "time": 612 } ],
[ { "from_index": 1, "to_index": 0, "distance": 8.10, "time": 740 } ]
],
"units": "kilometers"
}
}
POST /routing/isochrone
Reachability polygons (how far you can travel in N minutes). Proxies the routing engine's /isochrone.
Request body
{ "payload": <raw routing-engine isochrone request> } — locations ({lat, lon}), costing, contours (array of { time: <minutes> } and/or { distance: <km> }, optional color), polygons (bool).
{
"payload": {
"locations": [ { "lat": 31.95, "lon": 35.91 } ],
"costing": "pedestrian",
"contours": [ { "time": 5 }, { "time": 10 } ],
"polygons": true
}
}
Response (201)
data is a GeoJSON FeatureCollection. Geometry coordinates are [lng, lat]; properties.contour is the contour value (minutes for time, km for distance).
{
"provider": "navvo", "available": true, "path": "isochrone",
"data": {
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "properties": { "contour": 5, "metric": "time", "color": "#ff0000" },
"geometry": { "type": "Polygon", "coordinates": [ [ [35.90, 31.94], [35.92, 31.94] ] ] } }
]
}
}
POST /routing/map-match
Snap a raw GPS trace to the road network. Proxies the routing engine's trace_route (the envelope path reads "trace_route").
Request body
{ "payload": <raw routing-engine trace_route request> } — either shape (array of {lat, lon, time?}) or encoded_polyline, plus costing and shape_match (map_snap, edge_walk, walk_or_snap).
{
"payload": {
"shape": [ { "lat": 31.9501, "lon": 35.9101 }, { "lat": 31.9520, "lon": 35.9145 } ],
"costing": "auto",
"shape_match": "map_snap"
}
}
Response (201)
data.trip is a standard routing-engine trip representing the matched route (legs, maneuvers, encoded shape).
POST /routing/route-attributes
Speed limits along the route — per-edge road attributes for a shape. Give it the route's decoded polyline (or any GPS trace) and get back, edge by edge, the legal speed limit, typical driving speed, length, road name, and road class — everything a navigation client needs to display the changing speed limit as the driver progresses.
Request body
| Field | Type | Notes |
|---|---|---|
shape | array | required — at least 2 { "lat": …, "lon": … } points (max 2000). Use the decoded trip shape from /routing/plan. |
costing | string | optional — auto (default), truck, bicycle, pedestrian, motor_scooter |
shapeMatch | string | optional — walk_or_snap (default), map_snap, edge_walk |
curl -s -X POST "https://navvo.io/api/v1/routing/route-attributes" \
-H "x-api-key: $NAVVO_API_KEY" -H "content-type: application/json" \
-d '{ "shape": [ { "lat": 31.9501, "lon": 35.9101 }, { "lat": 31.9520, "lon": 35.9145 } ] }'
Response
{
"provider": "navvo-routing", "available": true, "costing": "auto", "count": 2,
"edges": [
{ "speedLimitKph": 60, "typicalSpeedKph": 48, "lengthKm": 0.31, "name": "شارع الملكة رانيا",
"roadClass": "primary", "beginShapeIndex": 0, "endShapeIndex": 4 },
{ "speedLimitKph": null, "typicalSpeedKph": 35, "lengthKm": 0.12, "name": null,
"roadClass": "residential", "beginShapeIndex": 4, "endShapeIndex": 7 }
]
}
| Field | Type | Notes |
|---|---|---|
edges[].speedLimitKph | number | null | posted legal limit in km/h — null where none is mapped |
edges[].typicalSpeedKph | number | null | the edge's typical driving speed |
edges[].lengthKm | number | null | edge length |
edges[].name | string | null | primary street name |
edges[].roadClass | string | null | motorway, primary, residential, … |
edges[].beginShapeIndex / endShapeIndex | number | which of your input shape points the edge spans — use them to locate each limit along the route |
To show "the limit here is X": track the driver's nearest shape index and pick the covering edge. When the engine is unreachable you get { "provider": "navvo-routing", "available": false }; fewer than 2 valid points returns 400.
Voice instructions & lane guidance
Route maneuvers already carry everything a spoken guidance engine needs — no extra call required. Alongside instruction, each maneuver in /routing/route and /routing/plan responses includes:
| Field | Speak it… |
|---|---|
verbal_transition_alert_instruction | early, as the alert ("In 400 meters, turn right onto…") |
verbal_pre_transition_instruction | just before the maneuver point |
verbal_post_transition_instruction | right after ("Continue for 2 kilometers") |
verbal_succinct_transition_instruction | as the short form when maneuvers come fast |
These honour the request's language (and /plan's English road-name localization).
For lane guidance at the approach to an intersection, call GET /junction/lanes?lat=…&lng=… with the upcoming maneuver's coordinate (scope routing):
{ "source": "navvo-lanes", "available": true, "roadName": "شارع غازي عربيات", "laneCount": 2,
"lanes": [ { "indications": ["left"] }, { "indications": ["through", "right"] } ] }
lanes is ordered left→right; each lane's indications lists its allowed turns (empty when only the lane count is known). available: false means no lane data is mapped there — hide the lane bar. Results are cached server-side, so polling per-maneuver is cheap.
POST /routing/optimize
Optimal stop ordering (TSP-style). Proxies the routing engine's optimized_route. The first and last locations are treated as fixed start/end.
Request body
{ "payload": <raw routing-engine optimized_route request> } — locations ({lat, lon}), costing, directions_options.
{
"payload": {
"locations": [
{ "lat": 31.95, "lon": 35.91 },
{ "lat": 31.99, "lon": 35.95 },
{ "lat": 31.97, "lon": 35.88 },
{ "lat": 31.93, "lon": 35.90 }
],
"costing": "auto"
}
}
Response (201)
data.trip.locations[].original_index maps the optimized order back to your input order; the rest is a standard trip.
Summary
| Endpoint | Engine op | Body | Coordinates | Navvo extras |
|---|---|---|---|---|
POST /routing/route | route | { payload } | {lat, lon} | AI labels |
POST /routing/plan | auto | Navvo RoutePlanDto | {lon, lat} objects | labels, constraints, logistics |
POST /routing/matrix | matrix | { payload } | {lat, lon} | — |
POST /routing/isochrone | isochrone | { payload } | {lat, lon} | — |
POST /routing/map-match | trace_route | { payload } | {lat, lon} | — |
POST /routing/route-attributes | trace_attributes | { shape, costing?, shapeMatch? } | {lat, lon} | per-edge speed limits |
POST /routing/optimize | optimized_route | { payload } | {lat, lon} | — |