Navvo
API Reference

Routing

Directions, travel-time matrices, isochrones, map-matching, and stop optimization.

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

Read this first — two coordinate conventions.
  • The passthrough endpoints (/route, /matrix, /isochrone, /map-match, /optimize) forward a raw routing-engine payload, so locations use the routing engine's native { "lat": …, "lon": … } objects (note lon).
  • /plan uses a Navvo body with { "lon": …, "lat": …, "name"? } location objects (keys are named, so order doesn't matter).
All routing endpoints return HTTP 201 on success. The routing engine being unreachable does not error — you get a 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 */ } }
Authentication: every routing/planning endpoint (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

payload
object required
A raw routing-engine route request. Forwarded verbatim (and not field-validated by Navvo). Must be non-empty.

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.

Skip the AI label for a faster response. The AI labels block adds ~1–2 s. Set includeLabels: false inside payload to return route geometry and alternates immediately — labels then comes back as { "unavailable": false, "purpose": "route_labels", "content": "" }. Defaults to true.
{
  "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" } }
}
For an ETA that reflects current congestion, use 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"]
}
FieldNotes
profiles[].costingthe value to pass as costing to /routing/plan
profiles[].availablewhether this deployment can route the profile (truck needs truck-enabled tiles)
preferences / avoidaccepted preference and avoid[] values
constraintscorridor flags (avoidIncidents, avoidReported, disregard)

GET /routing/maneuver-types

The canonical maneuver-code catalog — the mapping from every routing-engine maneuver type (the integer on legs[].maneuvers[].type) to a stable name, an icon key, and a human description. Load this once at startup instead of hardcoding the codes, so your turn-by-turn UI renders the correct arrow for every maneuver. Public (scope routing).

curl -s "https://navvo.io/api/v1/routing/maneuver-types" -H "x-api-key: $NAVVO_API_KEY"
{
  "provider": "navvo-routing",
  "types": [
    { "code": 0,  "name": "none",              "icon": "straight",    "description": "No maneuver." },
    { "code": 1,  "name": "start",             "icon": "depart",      "description": "Start of the route." },
    { "code": 4,  "name": "destination",       "icon": "arrive",      "description": "Arrive at the destination." },
    { "code": 10, "name": "right",             "icon": "right",       "description": "Turn right." },
    { "code": 15, "name": "left",              "icon": "left",        "description": "Turn left." },
    { "code": 26, "name": "roundabout-enter",  "icon": "roundabout",  "description": "Enter a roundabout." }
  ],
  "junctionWindowTypes": [ 10, 11, 15, 16 ],
  "decisionExcludedTypes": [ 0, 1, 4 ]
}
FieldDescription
types[]The full table — 39 entries, codes 0–38 — each { code, name, icon, description }.
types[].iconOne of depart, arrive, straight, slight-right, right, uturn, left, slight-left, merge, roundabout, ferry, transit — map it to your own maneuver-arrow asset.
junctionWindowTypesCodes that a junction-view window is relevant for.
decisionExcludedTypesCodes that are not user-facing "decision" turns (route start/end, none) — skip them when counting upcoming maneuvers.
Fetch this once and cache it. Every Navvo SDK and native navigation client should resolve maneuvers[].type through this table rather than embedding its own copy, so a future maneuver code is picked up without a client release.

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

locations
array required
Array of { "lon": number, "lat": number, "name"?: string }. At least 2 required.
costing
string
One of auto (default), pedestrian, bicycle, motor_scooter. truck returns 400 "Truck routing is not available yet on this deployment" unless truck routing is enabled — check available per profile via GET /routing/profiles.
preference
string
fastest, shortest, or balanced (default). Tunes the costing options.
avoid
array
Any of tolls, highways, ferries, unpaved. Any other value returns 400 with a validation array.
optimize
boolean
If 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.
avoidIncidents
boolean
Defaults to 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.
avoidReported
boolean
Defaults to 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.
disregard
array
Per-user sign featureKeys to ignore — those no-entry signs will not constrain this route.
(road edits)
—
Always applied (no flag): active road edits authored on the Navvo platform — closures and access restrictions (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.
constraintsText
string
Free-text delivery constraints (e.g. time windows). Parsed by AI into a structured constraints block.
includeLabels
boolean
Defaults to 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.
includeLanes
boolean
Defaults to false. When true, attaches a lanes object ({ roadName, laneCount, lanes: [{ indications }] }) to each decision maneuver in the trip, so a navigation client doesn't need a separate GET /junction/lanes call per approach. Adds latency (bounded upstream lookups, capped at 40 per trip).
includeAttributes
boolean
Defaults to false. When true, inlines legal speed limits along the route: a routeSpeedLimits summary at the top level (per-edge speedLimitKph + shape indices — the same feed as POST /routing/route-attributes) plus a speedLimitKph field on each maneuver that falls on a limited edge. One extra upstream call. (Where the underlying road data has no maxspeed, values are null.)
language
string
Narrative language. Defaults to 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": "auto",
  "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": "auto", "preference": "fastest", "avoid": ["tolls", "unpaved"] }
}
FieldDescription
engineThe 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.
labelsAI route label/tip. Empty when includeLabels is false.
constraintsPresent only if you sent constraintsText; otherwise null.
logisticsWhich Navvo logistics/SLA zones the route crosses, with risk and cost multipliers. applied: false when none intersect.
routeProfileEcho 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"
  }
}

GET /routing/matrix/limits

Every matrix limit, with the reason for each. Public — call it rather than hard-coding numbers, because they are resolved from configuration on each deploy.

{
  "provider": "navvo-routing", "version": "navvo-matrix-v1",
  "limits": {
    "maxOrigins": 1000, "maxDestinations": 1000,
    "maxSyncPairs": 2500, "maxAsyncPairs": 250000,
    "syncTimeoutMs": 15000, "maxPairDistanceKm": 400,
    "cacheTtlSeconds": 120, "resultRetentionSeconds": 3600
  }
}

maxSyncPairs is the routing engine's own ceiling on this deployment, not an arbitrary cap: a synchronous matrix inside it never needs tiling or a second engine round-trip. Anything larger belongs on a job.


POST /routing/matrix/jobs

Asynchronous matrix, for request sizes that should not be held open on a connection. Same request body as POST /routing/matrix; returns a job id immediately.

Use this above 2,500 pairs (origins × destinations). Up to 250,000 pairs is accepted.

Response (202)

{ "jobId": "9c1f…", "status": "queued", "pairs": 40000, "createdAt": "2026-08-23T09:14:02.113Z" }

Polling

EndpointPurpose
GET /routing/matrix/jobs/{id}Status and progress: queued → running → completed / failed / cancelled
GET /routing/matrix/jobs/{id}/resultThe finished matrix. Available for resultRetentionSeconds after completion
GET /routing/matrix/jobsYour recent jobs
DELETE /routing/matrix/jobs/{id}Cancel a queued or running job

Results are retained for one hour by default (resultRetentionSeconds); fetch them and store them yourself if you need them longer.


Out-of-coverage destinations

The routing graph covers Jordan. A coordinate outside it used to be snapped silently to the nearest reachable node, and the response then described a completed journey — an Amman → Jerusalem request returned a route with "You have arrived at your destination" whose shape ended tens of kilometres short. For a dispatch integration that is a driver sent to the wrong governorate.

POST /routing/plan now compares every requested location against the point the engine actually used. If one snaps beyond tolerance the request fails:

{
  "error": {
    "code": "DESTINATION_OUT_OF_COVERAGE",
    "message": "The destination is outside the routable network.",
    "details": {
      "role": "destination",
      "requested": { "lat": 31.7683, "lon": 35.2137 },
      "snapped":   { "lat": 31.9204, "lon": 35.4790 },
      "snapDistanceMeters": 29500,
      "toleranceMeters": 250
    },
    "requestId": "…"
  }
}

Tolerance is resolved per location from the city/country routing configuration — tighter in dense urban areas, looser on rural roads — and is echoed as toleranceMeters so the decision is inspectable.

Set allowOutOfCoverage: true only if you knowingly want the partial route. It then returns normally with snapValidation.withinCoverage: false and a warning, and is never presented as an arrival.


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

FieldTypeNotes
shapearrayrequired — at least 2 { "lat": …, "lon": … } points (max 2000). Use the decoded trip shape from /routing/plan.
costingstringoptional — auto (default), truck, bicycle, pedestrian, motor_scooter
shapeMatchstringoptional — 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 }
  ]
}
FieldTypeNotes
edges[].speedLimitKphnumber | nullposted legal limit in km/h — null where none is mapped
edges[].typicalSpeedKphnumber | nullthe edge's typical driving speed
edges[].lengthKmnumber | nulledge length
edges[].namestring | nullprimary street name
edges[].roadClassstring | nullmotorway, primary, residential, …
edges[].beginShapeIndex / endShapeIndexnumberwhich 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:

FieldSpeak it…
verbal_transition_alert_instructionearly, as the alert ("In 400 meters, turn right onto…")
verbal_pre_transition_instructionjust before the maneuver point
verbal_post_transition_instructionright after ("Continue for 2 kilometers")
verbal_succinct_transition_instructionas 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

EndpointEngine opBodyCoordinatesNavvo extras
POST /routing/routeroute{ payload }{lat, lon}AI labels
POST /routing/planautoNavvo RoutePlanDto{lon, lat} objectslabels, constraints, logistics
POST /routing/matrixmatrix{ payload }{lat, lon}—
POST /routing/isochroneisochrone{ payload }{lat, lon}—
POST /routing/map-matchtrace_route{ payload }{lat, lon}—
POST /routing/route-attributestrace_attributes{ shape, costing?, shapeMatch? }{lat, lon}per-edge speed limits
POST /routing/optimizeoptimized_route{ payload }{lat, lon}—
Copyright © 2026