Navvo
Guides

Python SDK

Install and use the official navvo Python client for server-side geocoding, routing, places, traffic, and more.

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() 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
  • SearchResponse is still the response dict, 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 with mode="typeahead". res.index.mode is "hybrid" when it contributed.
  • search_suggestions() with an empty query returns a starter list of everyday categories and well-known places. similar_places() returns items and source ("vectors", compared by meaning, or the "nearby" fallback); an unknown place raises NavvoNotFoundError.
  • The SDK checks arguments before sending: a blank query, an unknown mode, sort, semantic or facet name, coordinates off the globe or an unusable session id raise NavvoValidationError without 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 are directions, navigate, save, call, website and share.
  • search() raises NavvoValidationError for 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

AreaMethods
Tiles & configstyles, style_url, map_config
Search & placessearch, 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
Routingroute, simple_route, matrix, isochrone, map_match, optimize, routing_profiles
Traffic & incidentstraffic_route, traffic_summary, incidents, incident_types, report_incident, road_edits, route_risk
Weather & air qualityweather_point, weather_forecast, weather_cities, air_quality_cities
EV & transitev_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.

Copyright © 2026