Python SDK
Python SDK
The official navvo Python client is a server-side wrapper over the Navvo
Web Service. It is typed, retry-aware, and dependency-free — by default it uses
only the Python standard library, so it installs and runs anywhere; if
httpx is installed it is used automatically for
connection pooling. There is no map UI or tile fetching — for rendering a map
in the browser use Navvo Maps.
Requires: Python 3.8+ · Base URL: https://navvo.io/api/v1
Install
pip install navvo
# optional pooled HTTP transport:
pip install "navvo[httpx]"
Authenticate
Every call is sent with the x-api-key header. The key is read from the
NAVVO_API_KEY environment variable by default, or pass it explicitly:
from navvo import NavvoClient
navvo = NavvoClient() # uses $NAVVO_API_KEY
navvo = NavvoClient("nvvo_your_key_here") # explicit
The SDK never puts the key in a URL — except style_url(), which builds a
browser-fetchable map style URL where the key must
ride as ?api_key= (browsers cannot set headers on tile/style requests).
Quickstart
from navvo import NavvoClient, LatLng, BBox
with NavvoClient() as navvo:
# Forward geocode — string coordinates are parsed to floats for you
hits = navvo.geocode("Abdali Hospital, Amman", limit=5)
print(hits[0].lat, hits[0].lng, hits[0].display_name)
# Plan a route — you pass lat/lng; the SDK serialises lon/lat on the wire
plan = navvo.route(locations=[
{"lat": 31.95, "lng": 35.91, "name": "A"},
{"lat": 31.99, "lng": 35.95, "name": "B"},
], costing="auto")
print("ETA seconds:", plan.trip["summary"]["time"])
path = plan.geometry() # decoded polyline -> list[LatLng]
# Live, condition-aware extras
wx = navvo.weather_point(31.95, 35.91)
inc = navvo.incidents(bbox=BBox(35.85, 31.9, 35.99, 31.99), limit=10)
Coordinates — never backwards
You always supply points as LatLng(lat=..., lng=...) (or a
{"lat":.., "lng":..} dict / (lat, lng) tuple). The SDK owns every wire-format
difference: {lon, lat} for route()/traffic_route(), {lat, lon} for the
routing passthrough payloads, separate lat/lon params for reverse geocode,
west,south,east,north for BBox, [lng, lat] for GeoJSON, and WKT
POINT(lng lat) for place center (parse it with place.center_latlng).
Retries & errors
Transient failures (5xx, 429/quota, network blips) are retried with
exponential backoff and full jitter, honouring Retry-After; client errors
(400/401/403/404) are never retried. All errors subclass NavvoError:
from navvo.errors import (
NavvoRateLimitError, NavvoForbiddenError, NavvoServiceUnavailableError,
)
try:
r = navvo.geocode("Abdali Hospital, Amman")
except NavvoRateLimitError as e:
print("rate limited, retry after", e.retry_after, "s")
except NavvoForbiddenError as e:
print("scope/quota problem:", e.reason or e.message)
except NavvoServiceUnavailableError:
print("geocoder upstream is down — fall back")
The geocoder/routing envelope ({provider, available, data}) is unwrapped
for you, and an unknown place slug returns None (a soft-null, not an error).
Search
search() returns a typed SearchResponse for GET /search, including the query_id that later click events refer to. An option you don't pass is left out of the request:
from navvo import NavvoClient, LatLng
navvo = NavvoClient()
res = navvo.search(
"pharmacy",
near=LatLng(lat=31.9539, lng=35.9106), # rank nearer places higher (or lat=…, lon=…)
radius_km=3, sort="distance", # both need an origin
open_now=True, min_rating=4,
amenities=["wheelchair-accessible-entrance"],
facets=["category", "amenities"], # res.facets.category / res.facets.amenities
highlight=True, # hit.highlight.name_en / name_ar
)
res.query_id # for send_search_event()
for hit in res.places.items: # typed: distance_km, image_url, highlight, promoted
print(hit.position, hit.name_en, hit.distance_km)
res.did_you_mean, res.suggestions # zero results only: a spelling fix, popular places
navvo.search("rumi caf", mode="typeahead") # as the user types
navvo.search("quiet place to work with good coffee", semantic="on") # meaning-based matching
navvo.search("pharmacy", analytics=False) # keep this call out of analytics
navvo.search_suggestions("pha", limit=6) # GET /search/suggestions
navvo.similar_places(slug="jordan-museum-994700", limit=6) # GET /search/similar → items, source
SearchResponseis still the responsedict, so code written for 0.1 (res["places"]["items"]) keeps working. The documented fields are also typed attributes, and every object keeps the untouched payload on.raw.- With an API key, meaning-based matching runs only when you pass
semantic="on", and never withmode="typeahead".res.index.modeis"hybrid"when it contributed. search_suggestions()with an empty query returns a starter list of everyday categories and well-known places.similar_places()returnsitemsandsource("vectors", compared by meaning, or the"nearby"fallback); an unknown place raisesNavvoNotFoundError.- The SDK checks arguments before sending: a blank query, an unknown
mode,sort,semanticor facet name, coordinates off the globe or an unusable session id raiseNavvoValidationErrorwithout a request.
Highlights arrive HTML-escaped, with matched words wrapped in <em>. parse_highlight() splits one into (text, match) segments, so you can render them as text instead of trusting the string as markup:
from navvo import parse_highlight
for hit in res.places.items:
if hit.highlight and hit.highlight.name_en:
print([(seg.text, seg.match) for seg in parse_highlight(hit.highlight.name_en)])
Price filter and result details
From 0.3.0, price keeps places at any of the given price levels: 1 up to JOD 5 per person, 2 JOD 5–10, 3 JOD 10–20, 4 more. Every place item can also carry what a results list shows. Each of these fields is None when the place has no data:
res = navvo.search("cafe", near=LatLng(lat=31.9539, lng=35.9106), price=[1, 2])
for hit in res.places.items:
print(hit.name_en, hit.category_name, hit.price_range, hit.price_level, hit.review_count)
print(hit.address, hit.area, hit.highlights)
if hit.hours: # today, in Jordan time
print(hit.hours.state, hit.hours.closes_at, hit.hours.opens_at, hit.hours.opens_day)
navvo.similar_places(slug="jordan-museum-994700", locale="ar") # category_name and address in Arabic
hours.state is "open", "closing" (closes within 45 minutes) or "closed". hours.always_open marks a place open around the clock, and hours.hours_may_vary a public holiday. A level outside 1–4 raises NavvoValidationError before anything is sent. See Result details.
Clicks and conversions
To see click-through and conversions for your project in the developer portal, send a session id with the search, one per end-user session and only if your user agreed to it. Then report what the user opened:
from navvo import SearchEvent, create_search_session_id
session = create_search_session_id() # one per end-user session, only with the user's consent
res = navvo.search("rumi cafe", session=session)
hit = res.places.items[0] # the result the user opened
navvo.send_search_event(SearchEvent(query_id=res.query_id, type="click",
place_id=hit.id, position=hit.position))
navvo.send_search_event(SearchEvent(query_id=res.query_id, type="conversion", name="directions",
place_id=hit.id, position=hit.position))
- Events are accepted for 7 days after the search, only when it carried a session id, and only with the same API key.
send_search_events()sends several at once, in requests of at most 20 events and 16 KB. Conversion names aredirections,navigate,save,call,websiteandshare.search()raisesNavvoValidationErrorfor a session id that doesn't match^[A-Za-z0-9_-]{16,64}$or contains 6 digits in a row;create_search_session_id()always makes a valid one.NavvoClient(search_analytics=False)keeps everything the client searches out of analytics. Events sent by such a client are rejected, so report clicks from a client created without it.
See Search events for what is accepted and why an event can be rejected, and Search analytics & privacy for consent and what Navvo records.
Method map
| Area | Methods |
|---|---|
| Tiles & config | styles, style_url, map_config |
| Search & places | search, search_suggestions, similar_places, send_search_event, send_search_events, geocode, reverse_geocode, geocode_batch, reverse_geocode_batch, places, iter_places, place, place_thumb, place_categories, place_facets, place_hours, place_popular_times, place_attributes, place_reviews_summary, place_photos, place_nearby, places_reachable |
| Routing | route, simple_route, matrix, isochrone, map_match, optimize, routing_profiles |
| Traffic & incidents | traffic_route, traffic_summary, incidents, incident_types, report_incident, road_edits, route_risk |
| Weather & air quality | weather_point, weather_forecast, weather_cities, air_quality_cities |
| EV & transit | ev_stations, transit_stops, gbfs |
Each method maps to the endpoint documented in the
API reference. geocode, places, place, route, and
matrix return typed objects with the common fields parsed and the full payload
preserved on .raw.