Navvo
API Reference

Itinerary, Geofencing & Static Maps

Multi-stop trip optimization, location geofences with webhooks, and embeddable static map images.

Itinerary, Geofencing & Static Maps

Three building blocks for higher-order location features. Itinerary powers Atlas-style "Ask Maps" trip planning — hand Navvo a list of stops and get back an optimized visiting order with leg-by-leg distances and times, plus authenticated endpoints to save, share, and reload a user's trips. Geofencing lets B2B teams define circular or polygon zones, evaluate a device position against them, and fire webhooks the moment a device enters or leaves a zone. Static Maps renders a ready-to-embed map image you can drop straight into an <img> — perfect for emails, link previews, and server-rendered cards.

Everything is served and branded by Navvo from a single API. There are no outside services to wire up.

Scope required: routing for itinerary planning; geo for geofences; tiles for static maps. Saved-itinerary endpoints use a user session token (see below).

Coordinates are decimal degrees (lat, lng). Distances are in kilometres (distanceKm) or metres (radiusM) as labelled, and durations are in minutes. Polygon rings are arrays of [lng, lat] pairs. Endpoints degrade gracefully — a planning request that can't be routed returns available:false rather than an error.

POST /itinerary/plan

Plan a multi-stop trip. Give Navvo an ordered (or unordered) list of stops and it returns an optimized visiting order with the route stitched between them — leg distances, leg times, and trip totals including dwell time at each stop. This is the engine behind Atlas "Ask Maps" trip suggestions.

When you pass 3 or more stops, Navvo optimizes the visiting order to minimize total travel while keeping the first and last stops fixed (your start and end anchors); the intermediate stops are reordered. With 2 stops it simply routes between them. Add a dwellMin to any stop to budget time spent there.

curl -s "https://navvo.io/api/v1/itinerary/plan" \
  -H "x-api-key: nvvo_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "stops": [
      { "name": "Amman Citadel", "lat": 31.9579, "lng": 35.9344, "dwellMin": 60 },
      { "name": "Rainbow Street", "lat": 31.9510, "lng": 35.9302, "dwellMin": 45 },
      { "name": "The Boulevard Abdali", "lat": 31.9707, "lng": 35.9106, "dwellMin": 90 }
    ],
    "costing": "auto"
  }'

Scope required: routing

Request body

stops
array required
The stops to visit. Two or more required. Each element is a stop object (see below).
costing
string
Travel profile for the legs between stops: auto (default), bicycle, or pedestrian.

Each stops[] element:

lat
number required
Stop latitude.
lng
number required
Stop longitude.
name
string
Display name for the stop. Echoed back in the ordered result.
placeId
string
Optional Navvo place id, if the stop came from search or places.
note
string
Free-text note carried through to the plan (e.g. "lunch reservation 13:00").
dwellMin
number
Minutes to spend at this stop. Summed into the trip's dwellMinutes total. Defaults to 0.

Response

FieldTypeDescription
providerstringAlways navvo-itinerary.
availablebooleantrue when the trip could be routed. false if no route exists between the stops.
orderedStops[]arrayThe stops in optimized visiting order (first and last unchanged). Each carries the original stop fields.
legs[]arrayTravel legs between consecutive ordered stops.
totalsobjectTrip-level roll-up (see below).

Each legs[] element:

FieldTypeDescription
fromnumberIndex into orderedStops[] of the leg's start.
tonumberIndex into orderedStops[] of the leg's end.
distanceKmnumberLeg distance in kilometres.
minutesnumberLeg travel time in minutes.

The totals object:

FieldTypeDescription
distanceKmnumberTotal distance across all legs.
travelMinutesnumberTotal time moving between stops.
dwellMinutesnumberSum of every stop's dwellMin.
totalMinutesnumbertravelMinutes + dwellMinutes — the end-to-end trip duration.
{
  "provider": "navvo-itinerary",
  "available": true,
  "orderedStops": [
    { "name": "Amman Citadel", "lat": 31.9579, "lng": 35.9344, "dwellMin": 60 },
    { "name": "Rainbow Street", "lat": 31.9510, "lng": 35.9302, "dwellMin": 45 },
    { "name": "The Boulevard Abdali", "lat": 31.9707, "lng": 35.9106, "dwellMin": 90 }
  ],
  "legs": [
    { "from": 0, "to": 1, "distanceKm": 1.2, "minutes": 6 },
    { "from": 1, "to": 2, "distanceKm": 3.8, "minutes": 11 }
  ],
  "totals": {
    "distanceKm": 5.0,
    "travelMinutes": 17,
    "dwellMinutes": 195,
    "totalMinutes": 212
  }
}
The optimizer keeps your first and last stops where they are, so anchor a trip by listing the start as the first stop and the final destination (or hotel) as the last. Everything in between is reordered for the shortest run.

POST /itinerary/auto

Where /itinerary/plan orders stops you already chose, /itinerary/auto discovers and picks them: give it a starting point, a time budget, and (optionally) what you're into — it finds nearby places, then selects and orders the highest-value set that fits the time you have. It's solved as an orienteering problem (prize-collecting): every candidate's "skip cost" scales with its rating, under a route-duration cap equal to your budget, so the best stops that fit are kept and the rest dropped. Scope routing.

FieldTypeNotes
start{lat,lng}required — where the day begins
timeBudgetMinnumbertotal time available, minutes (default 240; 30–1440)
interestsstringcategory keywords to match (e.g. ["cafe","museum"]); omit for top-rated of any kind
radiusKmnumbersearch radius from start (default 8; max 50)
dwellMinnumberminutes spent at each stop (default 30)
maxCandidatesnumberhow many nearby POIs to consider (default 25; max 60)
costingstringtravel profile (default pedestrian)
curl -s -X POST "https://navvo.io/api/v1/itinerary/auto" \
  -H "x-api-key: $NAVVO_API_KEY" -H "content-type: application/json" \
  -d '{ "start": {"lat":31.9539,"lng":35.9106}, "timeBudgetMin": 180, "interests": ["cafe","museum"], "dwellMin": 30 }'
{
  "provider": "navvo-itinerary", "available": true,
  "start": { "lat": 31.9539, "lng": 35.9106 },
  "orderedStops": [
    { "order": 1, "placeId": "", "slug": "", "name": "", "category": "cafe", "rating": 4.7, "dwellMin": 30, "lat": 31.95, "lng": 35.91 }
  ],
  "skipped": [ { "name": "", "rating": 4.1 } ],
  "totals": { "stops": 4, "candidatesConsidered": 25, "travelMinutes": 49, "dwellMinutes": 120, "totalMinutes": 169, "budgetMinutes": 180 }
}

The plan never exceeds budgetMinutes (totalMinutes = travel + dwell ≤ budget). Feed orderedStops straight into POST /me/itineraries to save it.


Saved itineraries

These endpoints let a signed-in user store, update, share, and reload their trips. Unlike the rest of this module, they operate on a user account, so they require a user session token (the JWT issued when a user signs in) rather than only an API key — pass it as Authorization: Bearer <user-jwt>. The one exception is the public share endpoint, which needs no auth at all.

A typical flow: a user plans a trip with POST /itinerary/plan, then saves the result with POST /me/itineraries — store the returned plan so reopening the trip is instant. Set isPublic:true to get a shareSlug you can hand out via GET /itineraries/{shareSlug}.

GET /me/itineraries

List the signed-in user's saved itineraries.

curl -s "https://navvo.io/api/v1/me/itineraries" \
  -H "Authorization: Bearer <user-jwt>"

Scope required: user session token (not an API key).

Response

FieldTypeDescription
providerstringAlways navvo-itinerary.
countnumberNumber of itineraries returned.
itineraries[]arrayThe user's saved itineraries (see item shape).
{
  "provider": "navvo-itinerary",
  "count": 1,
  "itineraries": [
    {
      "id": "itn_8f21",
      "name": "Amman Day Trip",
      "stops": [
        { "name": "Amman Citadel", "lat": 31.9579, "lng": 35.9344 },
        { "name": "Rainbow Street", "lat": 31.9510, "lng": 35.9302 }
      ],
      "notes": "Start late morning",
      "isPublic": true,
      "shareSlug": "amman-day-trip-8f21",
      "createdAt": "2026-06-20T09:12:00Z",
      "updatedAt": "2026-06-20T09:12:00Z"
    }
  ]
}

POST /me/itineraries

Create a saved itinerary for the signed-in user.

curl -s "https://navvo.io/api/v1/me/itineraries" \
  -H "Authorization: Bearer <user-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Amman Day Trip",
    "stops": [
      { "name": "Amman Citadel", "lat": 31.9579, "lng": 35.9344, "dwellMin": 60 },
      { "name": "Rainbow Street", "lat": 31.9510, "lng": 35.9302, "dwellMin": 45 }
    ],
    "notes": "Start late morning",
    "isPublic": true
  }'

Scope required: user session token (not an API key).

Request body

name
string required
Display name for the itinerary.
stops
array required
The stops in the trip (same stop shape as /itinerary/plan).
notes
string
Free-text notes for the trip.
plan
object
An optional cached plan result (the body returned by /itinerary/plan) so the trip reopens without re-routing.
isPublic
boolean
When true, Navvo mints a shareSlug so the trip can be viewed publicly. Defaults to false.

Response

FieldTypeDescription
providerstringAlways navvo-itinerary.
idstringThe new itinerary id.
namestringThe itinerary name.
stops[]arrayThe saved stops.
notesstringThe saved notes, if any.
planobjectThe cached plan, if one was supplied.
isPublicbooleanWhether the trip is publicly shareable.
shareSlugstringThe public slug (present only when isPublic is true).
createdAt / updatedAtstringISO timestamps.
{
  "provider": "navvo-itinerary",
  "id": "itn_8f21",
  "name": "Amman Day Trip",
  "stops": [
    { "name": "Amman Citadel", "lat": 31.9579, "lng": 35.9344, "dwellMin": 60 },
    { "name": "Rainbow Street", "lat": 31.9510, "lng": 35.9302, "dwellMin": 45 }
  ],
  "notes": "Start late morning",
  "isPublic": true,
  "shareSlug": "amman-day-trip-8f21",
  "createdAt": "2026-06-20T09:12:00Z",
  "updatedAt": "2026-06-20T09:12:00Z"
}

POST /me/itineraries/{id}

Update an existing saved itinerary. Send only the fields you want to change; the same body fields as POST /me/itineraries are accepted.

curl -s "https://navvo.io/api/v1/me/itineraries/itn_8f21" \
  -H "Authorization: Bearer <user-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Amman Day Trip (revised)",
    "isPublic": false
  }'

Scope required: user session token (not an API key).

Response

Returns the updated itinerary object (same shape as POST /me/itineraries), with a refreshed updatedAt.


DELETE /me/itineraries/{id}

Delete a saved itinerary owned by the signed-in user.

curl -s -X DELETE "https://navvo.io/api/v1/me/itineraries/itn_8f21" \
  -H "Authorization: Bearer <user-jwt>"

Scope required: user session token (not an API key).

Response

FieldTypeDescription
providerstringAlways navvo-itinerary.
deletedbooleantrue when the itinerary was removed.
idstringThe id that was deleted.
{
  "provider": "navvo-itinerary",
  "deleted": true,
  "id": "itn_8f21"
}

GET /itineraries/{shareSlug}

Fetch a publicly shared itinerary by its shareSlug. This endpoint is public — no API key or user token is needed — so it's ideal for share links and read-only trip pages. Only itineraries saved with isPublic:true are reachable.

curl -s "https://navvo.io/api/v1/itineraries/amman-day-trip-8f21"

Scope required: none (public).

Response

FieldTypeDescription
providerstringAlways navvo-itinerary.
namestringThe itinerary name.
stops[]arrayThe trip's stops.
notesstringThe trip notes, if any.
planobjectThe cached plan, if one was saved.
shareSlugstringThe public slug.
{
  "provider": "navvo-itinerary",
  "name": "Amman Day Trip",
  "stops": [
    { "name": "Amman Citadel", "lat": 31.9579, "lng": 35.9344, "dwellMin": 60 },
    { "name": "Rainbow Street", "lat": 31.9510, "lng": 35.9302, "dwellMin": 45 }
  ],
  "notes": "Start late morning",
  "plan": {
    "totals": { "distanceKm": 1.2, "travelMinutes": 6, "dwellMinutes": 105, "totalMinutes": 111 }
  },
  "shareSlug": "amman-day-trip-8f21"
}

GET /geofences

List geofences, optionally filtered to a single owner. Use it to render the zones a B2B account has defined — circles and polygons you can draw on a map or feed into /geofences/evaluate.

curl -s "https://navvo.io/api/v1/geofences?ownerId=fleet-acme" \
  -H "x-api-key: nvvo_YOUR_KEY"

Scope required: geo

Query parameters

ownerId
string
Filter to geofences owned by this id (e.g. a tenant or fleet). Omit to list all geofences visible to your key.

Response

FieldTypeDescription
providerstringAlways navvo-geofencing.
countnumberNumber of geofences returned.
geofences[]arrayThe geofences (see shape).
{
  "provider": "navvo-geofencing",
  "count": 1,
  "geofences": [
    {
      "id": "gf_warehouse",
      "name": "Amman Warehouse",
      "shape": "circle",
      "lat": 31.9870,
      "lng": 35.8650,
      "radiusM": 250,
      "webhookUrl": "https://acme.example.com/hooks/navvo",
      "ownerId": "fleet-acme"
    }
  ]
}

POST /geofences

Create a geofence — a circular or polygon zone. Attach a webhookUrl and Navvo will POST to it on enter/exit transitions detected by /geofences/evaluate.

curl -s "https://navvo.io/api/v1/geofences" \
  -H "x-api-key: nvvo_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Amman Warehouse",
    "shape": "circle",
    "lat": 31.9870,
    "lng": 35.8650,
    "radiusM": 250,
    "webhookUrl": "https://acme.example.com/hooks/navvo",
    "ownerId": "fleet-acme"
  }'

Scope required: geo

Request body

name
string required
Display name for the geofence.
shape
string required
Geometry type: circle or polygon.
lat
number
Centre latitude. Required when shape is circle.
lng
number
Centre longitude. Required when shape is circle.
radiusM
number
Circle radius in metres. Required when shape is circle.
polygon
array
Ring of [lng, lat] pairs. Required when shape is polygon.
webhookUrl
string
URL Navvo POSTs to on enter/exit transitions (see /geofences/evaluate).
ownerId
string
Owner/tenant id used to scope and filter the fence.

Response

Returns the created geofence.

FieldTypeDescription
providerstringAlways navvo-geofencing.
idstringThe new geofence id.
namestringThe geofence name.
shapestringcircle or polygon.
lat / lng / radiusMnumberCircle geometry (present for circle fences).
polygonarrayPolygon ring of [lng, lat] pairs (present for polygon fences).
webhookUrlstringThe webhook target, if set.
ownerIdstringThe owner id, if set.
{
  "provider": "navvo-geofencing",
  "id": "gf_warehouse",
  "name": "Amman Warehouse",
  "shape": "circle",
  "lat": 31.9870,
  "lng": 35.8650,
  "radiusM": 250,
  "webhookUrl": "https://acme.example.com/hooks/navvo",
  "ownerId": "fleet-acme"
}
For a polygon fence, pass "shape": "polygon" with a polygon ring instead of lat/lng/radiusM, e.g. "polygon": [[35.86,31.98],[35.87,31.98],[35.87,31.99],[35.86,31.99]]. Coordinate order is [lng, lat].

POST /geofences/evaluate

Evaluate a device position against your geofences. Returns which fences the point is inside right now and — when you provide a deviceId — the enter/exit transitions since that device was last seen. With emit:true, Navvo also POSTs a webhook to each crossed fence's webhookUrl.

When you pass deviceId and emit, Navvo diffs the new position against the device's last recorded state to compute transitions, persists the new state, and fires a webhook for every fence that was entered or exited. Without a deviceId it performs a stateless point-in-fence check and transitions is empty.

curl -s "https://navvo.io/api/v1/geofences/evaluate" \
  -H "x-api-key: nvvo_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "lat": 31.9869,
    "lng": 35.8651,
    "deviceId": "truck-17",
    "ownerId": "fleet-acme",
    "emit": true
  }'

Scope required: geo

Request body

lat
number required
Current device latitude.
lng
number required
Current device longitude.
deviceId
string
Stable device identifier. Required to compute enter/exit transitions; Navvo remembers this device's last inside-set between calls.
ownerId
string
Restrict evaluation to fences owned by this id.
emit
boolean
When true (with a deviceId), Navvo POSTs a webhook to each crossed fence's webhookUrl. Defaults to false.

Response

FieldTypeDescription
providerstringAlways navvo-geofencing.
lat / lngnumberThe evaluated position (echoed back).
inside[]stringIds of the geofences the point is currently inside.
transitions[]arrayEnter/exit transitions since the device was last seen (empty without a deviceId).

Each transitions[] element:

FieldTypeDescription
geofenceIdstringThe fence that was crossed.
typestringenter or exit.
{
  "provider": "navvo-geofencing",
  "lat": 31.9869,
  "lng": 35.8651,
  "inside": ["gf_warehouse"],
  "transitions": [
    { "geofenceId": "gf_warehouse", "type": "enter" }
  ]
}
Webhook payload. When emit fires, Navvo POSTs JSON to the fence's webhookUrl describing the crossing — the geofenceId, the type (enter/exit), the deviceId, the lat/lng, and an ownerId. Respond 2xx to acknowledge; Navvo retries transient failures.

POST /geofences/evaluate-batch

Check many points at once against all your active fences in a single call — e.g. a whole fleet's live positions. The fences are loaded once and every point is tested against them, so it's far cheaper than one /geofences/evaluate per vehicle. It's a stateless snapshot (which fences each point is in, plus per-fence occupancy); for stateful enter/exit events, use /geofences/evaluate per device.

curl -s -X POST "https://navvo.io/api/v1/geofences/evaluate-batch" \
  -H "x-api-key: $NAVVO_API_KEY" -H "content-type: application/json" \
  -d '{ "points": [ {"deviceId":"v1","lat":31.9505,"lng":35.9105}, {"deviceId":"v2","lat":31.97,"lng":35.89} ] }'
FieldTypeNotes
pointsarrayup to 1000 { deviceId?, lat, lng }
ownerIdstringoptional — scope to one owner's fences (defaults to your project)
{
  "provider": "navvo-geofencing", "count": 2, "fencesChecked": 2,
  "occupancy": { "gf_downtown": 1, "gf_shmeisani": 1 },
  "results": [
    { "deviceId": "v1", "lat": 31.9505, "lng": 35.9105, "inside": ["gf_downtown"], "insideNamed": [ { "id": "gf_downtown", "name": "Downtown circle" } ] },
    { "deviceId": "v2", "lat": 31.97, "lng": 35.89, "inside": ["gf_shmeisani"], "insideNamed": [ { "id": "gf_shmeisani", "name": "Shmeisani box" } ] }
  ]
}

occupancy is a fence-id → count map (how many of your points are in each fence) — handy for a live "vehicles per zone" dashboard. Works for both circle and polygon fences.


GET /tiles/static

Render an embeddable static map image. Compose a Navvo basemap centred on a point at a given zoom, drop pin markers on it, and get back an image you can place directly in an <img> tag — no JavaScript map needed. Ideal for emails, link previews (Open Graph / social cards), and server-rendered cards where an interactive map can't run.

The image is returned as SVG (Content-Type: image/svg+xml), so it stays crisp at any size and renders directly in a browser, an <img>, or a CSS background-image.

curl -s "https://navvo.io/api/v1/tiles/static?center=31.9539,35.9106&zoom=13&size=600x400&markers=31.9579,35.9344;31.9510,35.9302&style=standard&api_key=nvvo_YOUR_KEY" \
  -o map.svg

Embed it straight into a page or email — set the api_key in the query string because an <img> can't send an x-api-key header:

<img
  src="https://navvo.io/api/v1/tiles/static?center=31.9539,35.9106&zoom=13&size=600x400&markers=31.9579,35.9344;31.9510,35.9302&style=standard&api_key=nvvo_YOUR_KEY"
  width="600"
  height="400"
  alt="Map of Amman"
/>

Scope required: tiles

Query parameters

center
string required
Map centre as lat,lng (e.g. 31.9539,35.9106).
zoom
number required
Zoom level (roughly 119). Higher is closer in.
size
string
Image size in pixels as WxH (e.g. 600x400). Defaults to a standard card size; capped server-side.
markers
string
Semicolon-separated pin positions, each lat,lng (e.g. 31.9579,35.9344;31.9510,35.9302). Each becomes a Navvo pin on the image.
style
string
Basemap style: standard (default), dark, satellite, or terrain.

Response

A map image. The response body is SVG with Content-Type: image/svg+xml, sized to your size parameter, showing the Navvo basemap centred on center at zoom with a pin for each markers entry.

Because an <img> can't send headers, pass your key as api_key=nvvo_YOUR_KEY in the query string for static-map URLs — the same pattern used by map tile and style URLs. See Map Tiles & Styles for details.
Copyright © 2026