Navvo
API Reference

Navigation

The real-time turn-by-turn navigation control plane — managed sessions, server-computed progress and off-route, live ETA, arrival detection, an event log, and spoken-guidance text.

Navigation

A server-aware navigation control plane for building a Google-Maps-class turn-by-turn experience without re-inventing the hard geometry on the client. It sits on top of Routing and Traffic: you start a session for a trip, feed it live GPS fixes, and the server computes snap-to-route progress, off-route detection, automatic re-routing, a traffic-aware ETA, arrival detection, and a rolling event log (with optional signed webhooks).

Every stateless helper (off-route, progress, arrival-check) is also available on its own if you prefer to drive the loop client-side.

Scope required: routing

Authentication. Navigation is a member service: every endpoint requires an identified caller — a signed-in Navvo user's Bearer token or your developer API key (x-api-key). Anonymous requests return 401. Sessions are owned by whoever created them; another caller reading or updating your session gets 403.
Coordinates are { lat, lng } on every Navigation body (unlike the low-level routing passthrough, which uses lon). Route geometry (routeShape) is an encoded polyline, precision 6 (a.k.a. polyline6) — decode with a factor of 1e6. All POSTs return 201 on success.

POST /navigation/sessions

Start a managed navigation session. The server plans the route (via the Navvo routing engine), parses the maneuvers with cumulative distances, and returns a session you can drive with PATCH/positions.

Request body

origin
object required
Start point { "lat": number, "lng": number }.
destination
object required
End point { "lat": number, "lng": number, "name"?: string, "placeSlug"?: string }. placeSlug lets arrival detection resolve a mapped entrance.
costing
string
Travel mode: auto (default), pedestrian, bicycle, motor_scooter, truck, wheelchair.
language
string
Instruction/voice language (e.g. en-US, ar). Default en-US.
preference
string
fastest, shortest, or balanced.
avoid
string[]
Any of tolls, highways, ferries, unpaved.
webhookUrl
string
Optional https URL to receive navigation events. Deliveries are HMAC-signed (see Webhooks).
curl -s -X POST "https://navvo.io/api/v1/navigation/sessions" \
  -H "Authorization: Bearer $NAVVO_JWT" -H "Content-Type: application/json" \
  -d '{
        "origin": { "lat": 31.9539, "lng": 35.9106 },
        "destination": { "lat": 31.9730, "lng": 35.9200, "name": "Sports City" },
        "costing": "auto", "language": "en-US"
      }'

Response (201)

{
  "sessionId": "b23f9bec-516c-4038-a2fa-c3a275ec598b",
  "status": "active",
  "costing": "auto",
  "language": "en-US",
  "destination": { "lat": 31.9730, "lng": 35.9200, "name": "Sports City" },
  "routeShape": "…encoded polyline6…",
  "maneuvers": [
    {
      "index": 0, "type": 1, "instruction": "Drive north.",
      "verbalAlert": "Drive north.", "verbalPre": "Drive north for 300 meters.",
      "verbalPost": "Continue for 300 meters.", "streetNames": ["King Abdullah St"],
      "roundaboutExit": null, "exitSign": null,
      "distanceMeters": 300, "timeSeconds": 34,
      "lat": 31.9539, "lng": 35.9106, "cumulativeMeters": 0
    }
  ],
  "distanceMeters": 6366,
  "baseSeconds": 402,
  "etaSeconds": 402,
  "distanceRemainingMeters": 6366,
  "durationRemainingSeconds": 402,
  "maneuverIndex": 0,
  "eventSeq": 0,
  "expiresAt": "2026-07-20T02:00:00.000Z",
  "route": { "shape": "…", "maneuvers": [ … ], "distanceMeters": 6366, "etaSeconds": 402 }
}
FieldMeaning
maneuvers[]Parsed turn list. type is a maneuver code — resolve it with GET /routing/maneuver-types. cumulativeMeters is the distance from the route start to that maneuver (used for progress). verbal* are ready for text-to-speech.
distanceMeters / baseSecondsTotal route length and no-traffic duration.
eventSeqMonotonic event counter — poll GET …/events?since= with the last value you saw.
expiresAtSessions auto-expire ~6 h after last activity.

Errors: 400 if no route can be planned between the points (or costing: "truck" where truck routing is not provisioned); 401 unauthenticated.


GET /navigation/sessions/{id}

Fetch the current session snapshot (route, maneuvers, progress, status). 404 if unknown, 403 if it belongs to another caller.


PATCH /navigation/sessions/{id}

Feed the latest GPS fix. The server snaps it to the route, advances the maneuver pointer, detects arrival and off-route (auto-re-planning when off-route), appends any events, and returns the fresh progress.

Request body

lat
number required
Current latitude.
lng
number required
Current longitude.
speed
number
Ground speed in m/s (tunes the "approaching maneuver" window).
heading
number
Course over ground in degrees.
capturedAt
string
ISO timestamp of the fix.

Response (201)

{
  "sessionId": "…", "status": "active", "maneuverIndex": 2, "eventSeq": 4,
  "etaSeconds": 237, "distanceRemainingMeters": 3756, "durationRemainingSeconds": 237,
  "progress": {
    "offRoute": false,
    "arrived": false,
    "shapeIndex": 128,
    "maneuverIndex": 2,
    "snapped": { "lat": 31.9601, "lng": 35.9149 },
    "distanceToRouteMeters": 6,
    "distanceRemainingMeters": 3756,
    "durationRemainingSeconds": 237,
    "distanceToNextManeuverMeters": 210,
    "nextManeuver": { "index": 2, "type": 15, "instruction": "Turn left onto …", "verbalAlert": "Turn left onto …", "cumulativeMeters": 3966, "lat": 31.96, "lng": 35.915 },
    "progressFraction": 0.41
  },
  "reroute": null
}

When the fix is off the route corridor, progress.offRoute is true, an off_route event is emitted, and the server re-plans from the current position: reroute then carries the new { shape, maneuvers, distanceMeters, etaSeconds } and a rerouted event is logged. When within ~35 m of the destination the session flips to status: "arrived" and an arrived event fires.


DELETE /navigation/sessions/{id}

End the session (status: "cancelled"). Returns { "ended": true, "sessionId": "…", "status": "cancelled" }.


POST /navigation/sessions/{id}/positions

Submit a batch of recent fixes (up to 50) — useful for a background/duty-cycled uplink. The latest fix drives the authoritative progress update; the response mirrors PATCH plus accepted (count) and newEvents (events produced by this batch).

{ "positions": [ { "lat": 31.958, "lng": 35.897, "speed": 11 }, { "lat": 31.960, "lng": 35.915, "speed": 9 } ] }

GET /navigation/sessions/{id}/events

Poll the session's event log. Pass ?since=<eventSeq> to get only newer events.

{
  "sessionId": "…", "status": "active", "eventSeq": 6,
  "events": [
    { "seq": 4, "type": "maneuver_completed", "at": "2026-07-19T18:02:11.000Z", "data": { "maneuverIndex": 1, "instruction": "…" } },
    { "seq": 5, "type": "off_route", "at": "…", "data": { "distanceToRouteMeters": 67 } },
    { "seq": 6, "type": "rerouted", "at": "…", "data": { "reroutes": 1, "distanceMeters": 4175 } }
  ]
}

Event types: maneuver_approaching, maneuver_completed, off_route, rerouted, arrived. The last ~50 events are retained per session.


GET /navigation/sessions/{id}/eta

Recompute the traffic-aware ETA for the remaining route (from the last snapped position onward). This is the cheap, session-scoped alternative to polling POST /traffic/route with a hand-built shape.

{
  "sessionId": "…", "available": true, "etaSeconds": 660,
  "arrivalTime": "2026-07-19T18:15:00.000Z",
  "delaySeconds": 120, "trafficLevel": "slow", "source": "traffic"
}

When live traffic is unavailable it returns { "available": false, "etaSeconds": <base>, "source": "base" }.


POST /navigation/off-route

Stateless. Test a single position against a route corridor — no session needed.

Request body

routeShape
string required
The active route geometry as an encoded polyline6 string.
position
object required
{ "lat": number, "lng": number }.
heading
number
Optional course over ground.
thresholdMeters
number
Corridor half-width for the off-route decision (5–500, default 40).

Response (201)

{
  "provider": "navvo-navigation",
  "offRoute": true,
  "distanceToRouteMeters": 67.2,
  "confidence": 0.91,
  "snapped": { "lat": 31.9598, "lng": 35.9141 },
  "shapeIndex": 128,
  "alongMeters": 2606
}

snapped is the nearest point on the route; alongMeters is how far along the route that snapped point is. 400 if routeShape does not decode to at least two points.


POST /navigation/recalculate

Re-plan the remaining route from the current location. Preserves the trip's preferences. If sessionId is provided the session's route is replaced in place (and reroutes incremented); otherwise it is a stateless one-off (supply destination).

Request body

sessionId
string
Existing session to update. When present, destination/costing/preference default to the session's.
current
object required
{ "lat": number, "lng": number } — the new origin.
destination
object
Required when sessionId is omitted.
costing
string
Override the travel mode.
preference
string
fastest / shortest / balanced.

Response (201)

{
  "provider": "navvo-navigation",
  "sessionId": "…or null",
  "costing": "auto", "preference": "balanced",
  "route": { "shape": "…encoded polyline6…", "maneuvers": [ … ], "distanceMeters": 4175, "etaSeconds": 260 }
}

POST /navigation/progress

Stateless. Project a position onto a known route and get remaining distance/time, the next-maneuver index, and arrival — the same math the session PATCH runs, without a session.

Request body

routeShape
string required
Encoded polyline6 route geometry.
position
object required
{ "lat": number, "lng": number }.
maneuvers
object[]
Optional maneuver anchors [{ "cumulativeMeters": number }, …] (from a session or /routing/plan) so the response can advance maneuverIndex.
totalDistanceMeters
number
Total route length; defaults to the decoded shape length.
baseSeconds
number
No-traffic route duration; enables durationRemainingSeconds.
arrivalRadiusMeters
number
Arrival radius (5–200, default 30).

Response (201)

{
  "provider": "navvo-navigation",
  "shapeIndex": 128, "maneuverIndex": 3,
  "snapped": { "lat": 31.9598, "lng": 35.9141 },
  "distanceToRouteMeters": 4.2,
  "distanceRemainingMeters": 2100,
  "durationRemainingSeconds": 280,
  "distanceToNextManeuverMeters": 120,
  "progressFraction": 0.64,
  "arrived": false
}

POST /navigation/arrival-check

Stateless. Confirm arrival at a destination (and resolve the nearest mapped entrance when a placeSlug is supplied).

Request body

destination
object required
{ "lat": number, "lng": number, "placeSlug"?: string }.
position
object required
Current { "lat": number, "lng": number }.
placeSlug
string
Overrides destination.placeSlug for the entrance lookup.
radiusMeters
number
Arrival radius (5–200, default 30).

Response (201)

{ "provider": "navvo-navigation", "arrived": true, "distanceMeters": 12.4, "radiusMeters": 30, "entranceId": "ent_3" }

entranceId is null when the place has no mapped entrances (or none is supplied).


POST /navigation/speak

Turn a maneuver instruction into speech-ready output. Always returns the normalized text, a ready-to-use ssml string (with the correct language + a calm speaking rate), and the resolved language. When a server-side text-to-speech provider is configured it also returns an audioUrl; otherwise available is false and clients synthesize on-device from text/ssml (the default, recommended path — consistent Arabic/English pronunciation with zero extra latency).

Request body

text
string required
The instruction to speak (≤ 600 chars).
language
string
BCP-47-ish tag such as en-US or ar-SA. Default en-US.

Response (201)

{
  "provider": "navvo-navigation",
  "available": false,
  "reason": "on_device",
  "language": "en-US",
  "text": "In 200 meters, turn right onto King Abdullah Street.",
  "ssml": "<speak xml:lang=\"en-US\"><prosody rate=\"0.94\">In 200 meters, turn right onto King Abdullah Street.</prosody></speak>",
  "audioUrl": null
}

If a session was created with a webhookUrl, each batch of new events is POSTed to it as { "sessionId": "…", "events": [ … ] }. Deliveries carry:

  • X-Navvo-Signature: sha256=<hex> — HMAC-SHA256 of the raw request body. Verify it with your webhook secret before trusting a delivery.
  • X-Navvo-Delivery — a short idempotency token.

The URL must be https and must resolve to a public address — deliveries to loopback/private ranges are refused (SSRF protection). Delivery is best-effort (5 s timeout, no retries); poll GET …/events as the source of truth.


Copyright © 2026