Navvo
Guides

Node.js SDK

Install and use the official @navvo/sdk Node.js client for server-side geocoding, routing, places, optimization, traffic, and more.

Node.js SDK

The official @navvo/sdk package is a server-side TypeScript/JavaScript client for the Navvo Web Service. It is fully typed, retry-aware, and has zero runtime dependencies — it uses the built-in global fetch (Node 18+). It ships a dual ESM + CJS build with complete .d.ts types. There is no map UI or tile rendering — for rendering a map in the browser use Navvo Maps.

Requires: Node 18+ · Base URL: https://navvo.io/api/v1

Install

npm install @navvo/sdk
# or: pnpm add @navvo/sdk / yarn add @navvo/sdk

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:

import { NavvoClient } from "@navvo/sdk";

const navvo = NavvoClient.fromEnv();              // uses $NAVVO_API_KEY
const navvo2 = new NavvoClient({ apiKey: "nvvo_your_key_here" });

The SDK never puts the key in a URL — except styleUrl(), 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

import { NavvoClient } from "@navvo/sdk";

const navvo = new NavvoClient();

// Forward geocode — string coordinates are parsed to numbers for you
const hits = await navvo.geocode("Abdali Hospital, Amman", { limit: 5 });
console.log(hits[0].lat, hits[0].lng, hits[0].displayName);

// Plan a route — you pass { lat, lng }; the SDK serialises lon/lat on the wire
const plan = await navvo.route({
  locations: [
    { lat: 31.95, lng: 35.91, name: "A" },
    { lat: 31.99, lng: 35.95, name: "B" },
  ],
  costing: "auto",
});
const trip = plan.engine?.data?.trip;
console.log("ETA seconds:", trip?.summary?.time);

// Decode the leg polyline (polyline6) into points
const line = NavvoClient.decodePolyline(trip?.legs?.[0]?.shape ?? "");

// Live, condition-aware extras
const wx = await navvo.weatherPoint(31.95, 35.91);
const inc = await navvo.incidents({ bbox: { west: 35.85, south: 31.9, east: 35.99, north: 31.99 }, limit: 10 });

Coordinates — never backwards

You always supply points as { lat, lng } (a LatLng). The SDK owns every wire-format difference: { lon, lat } for route()/trafficRoute(), { 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 parseWktPoint(place.center) or NavvoClient.routeTargetFor(place)).

Retries & errors

Transient failures (5xx, 408, 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:

import {
  NavvoRateLimitError,
  NavvoForbiddenError,
  NavvoServiceUnavailableError,
} from "@navvo/sdk";

try {
  const r = await navvo.geocode("Abdali Hospital, Amman");
} catch (e) {
  if (e instanceof NavvoRateLimitError) {
    console.warn("rate limited, retry after", e.retryAfter, "s");
  } else if (e instanceof NavvoForbiddenError) {
    console.error("scope/quota problem:", e.reason ?? e.message);
  } else if (e instanceof NavvoServiceUnavailableError) {
    console.error("geocoder upstream is down — fall back");
  } else {
    throw e;
  }
}

The geocoder/routing envelope ({ provider, available, data }) is unwrapped for you — an available:false body becomes a NavvoServiceUnavailableError — and an unknown place slug returns null (a soft-null, not an error).

Cancel any call with an AbortSignal:

const ac = new AbortController();
const p = navvo.list({ category: "cafe" }, { signal: ac.signal });
setTimeout(() => ac.abort(), 1000);

search() returns the typed /search response, including the queryId that later click events refer to:

const res = await navvo.search("pharmacy", {
  near: { lat: 31.95, lng: 35.91 },   // rank nearer places higher
  originType: "device",               // "map" when `near` is a map centre (weaker distance bias)
  radiusKm: 3, sort: "distance",      // both need `near`
  openNow: true, minRating: 4,
  amenities: ["wheelchair-accessible-entrance"],
  facets: ["category", "amenities"],  // res.facets.category / res.facets.amenities
  highlight: true,                    // items[].highlight.nameEn / nameAr
});
res.queryId;                          // for sendSearchEvent()
res.places?.items;                    // may carry imageUrl, distanceKm, highlight, promoted
res.didYouMean; res.suggestions;      // zero results only: a spelling fix, popular places

await navvo.search("rumi caf", { mode: "typeahead" });                          // as the user types
await navvo.search("quiet place to work with good coffee", { semantic: "on" }); // meaning-based matching
await navvo.search("pharmacy", { analytics: false });                            // keep this call out of analytics
await navvo.suggestions("pha", { limit: 6 });                                    // GET /search/suggestions
await navvo.similar({ slug: "jordan-museum-994700", limit: 6 });                 // GET /search/similar → { items, source }

With an API key, meaning-based matching runs only with semantic: "on", and never with mode: "typeahead"; res.index.mode is "hybrid" when it contributed. From 0.2.1 the SDK sends X-Navvo-Source: sdk with every search, suggestions, similar-places and event request, so search analytics can tell SDK traffic apart. See Search.

Highlights arrive HTML-escaped, with matched words wrapped in <em>. parseHighlight() splits one into { text, match } segments, so you can render them as text in any output format instead of trusting the string as markup.

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 absent when the place has no data:

const res = await navvo.search("cafe", { near: { lat: 31.95, lng: 35.91 }, price: [1, 2] });
for (const p of res.places?.items ?? []) {
  console.log(p.nameEn, p.categoryName, p.priceRange, p.priceLevel, p.reviewCount);
  console.log(p.address, p.area, p.highlights);
  if (p.hours) console.log(p.hours.state, p.hours.closesAt, p.hours.opensAt, p.hours.opensDay); // today, in Jordan time
}
await navvo.similar({ slug: "jordan-museum-994700", locale: "ar" }); // categoryName and address in Arabic

hours.state is "open", "closing" (closes within 45 minutes) or "closed". hours.alwaysOpen marks a place open around the clock, and hours.hoursMayVary a public holiday. A level outside 1–4 throws NavvoConfigError before anything is sent. The types are exported as SearchPriceLevel and SearchPlaceHours. 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:

import { createSearchSessionId } from "@navvo/sdk";

const session = createSearchSessionId();
const res = await navvo.search("rumi cafe", { session, near: userLocation });
const place = res.places!.items[0]!;
const position = (res.places!.page - 1) * res.places!.limit + 1; // 1-based, across pages

await navvo.sendSearchEvent({ queryId: res.queryId!, type: "click", placeId: place.id, position });
await navvo.sendSearchEvent({ queryId: res.queryId!, type: "conversion", name: "directions", placeId: place.id, position });
  • Events are accepted for 7 days after the search, only when it carried a session id, and only with the same API key.
  • sendSearchEvents() sends several at once, in batches of 20. Conversion names are directions, navigate, save, call, website and share.
  • search() throws NavvoConfigError for a session id that doesn't match ^[A-Za-z0-9_-]{16,64}$ or contains 6 digits in a row; createSearchSessionId() always makes a valid one.

See Search events for what is accepted and why an event can be rejected.

Method map

AreaMethods
Tiles & configstyles, styleUrl, mapConfig
Search & geocodingsearch, suggestions, similar, sendSearchEvent, sendSearchEvents, geocode, reverseGeocode, geocodeBatch, reverseGeocodeBatch
Placeslist, iterate, place, thumb, placeCategories, facets, hours, popularTimes, attributes, reviewsSummary, photos, nearby, reachable
Routingroute, simpleRoute, matrix, isochrone, mapMatch, routingOptimize, routingProfiles
OptimizationoptimizeHealth, optimize, reoptimize, binpack, roster, submitJob, job, waitForJob
Traffic & incidentstrafficRoute, trafficSummary, incidents, incidentTypes, reportIncident, roadEdits, routeRisk
Weather & air qualityweatherPoint, weatherForecast, weatherCities, airQualityCities
EV & transitevStations, transitStops, gbfs

Each method maps to the endpoint documented in the API reference. geocode returns typed objects with the common fields parsed and the full geocoder payload preserved on .raw. Route optimization (optimize, submitJob, …) requires the opt-in optimize scope; see Route Optimization.

Copyright © 2026