Places
Places
List places and fetch rich detail for a single place, including reviews, photos, Q&A, and arrival intelligence (the recommended entrance and last-50-meters guidance).
Scope required: places
center and each entrance's location are WKT strings, POINT(<lng> <lat>) — longitude first. Inside arrivalIntelligence, parsed coordinates are returned as { "lng": …, "lat": … } objects. The rating field on a place object is a string (e.g. "4.60"); on /places/thumb it is a number.GET /places
List places, paginated, sponsored-first.
curl -s "https://navvo.io/api/v1/places?q=cafe&page=1&limit=20" \
-H "x-api-key: nvvo_YOUR_KEY"
Query parameters
nameEn or nameAr.1 (minimum 1).50, maximum 100.canonicalCategorySlugor any entry in its categories array. Discover slugs via GET /places/categories. When set, total reflects the filtered set and the response echoes category.wheelchair-accessible-entrance,free-parking-lot). A place matches only if all requested amenities are present and enabled. A slug may be an option (wi-fi) or a whole group (parking = has any parking). When set, total reflects the filtered set and the response echoes the normalized amenities array. Discover available slugs via GET /places/facets.true, return only places that are currently open (or closing soon), evaluated by the opening-hours engine in Jordan time. Places with no hours data are excluded. total reflects the filtered set and the response echoes openNow: true. Combine with amenities (e.g. "open cafés with parking right now").relevance (default — sponsored, then rating, then newest), rating (highest-rated first), name (A→Z), newest, or oldest. The response echoes the resolved sort.0–5). total reflects the filtered set and the response echoes minRating. Combine with the other filters — e.g. ?minRating=4.5&openNow=true&sort=rating.lat,lng origin (e.g. 31.9539,35.9106). Each result is annotated with distanceKm and the list is sorted nearest-first (this overrides sort). Combine with radiusKm and any filter.near, return only places within this many kilometres. The response echoes near and radiusKm.Response
{
"items": [
{
"id": "31c5a4e2-9b1f-4c0a-bb10-2f0c2b8e7a11",
"slug": "jordan-beans-cafe",
"nameEn": "Jordan Beans Cafe",
"nameAr": "مقهى جوردن بينز",
"logoUrl": "https://navvo.io/uploads/abc123.webp",
"categories": ["cafe", "food"],
"canonicalCategorySlug": "cafe",
"center": "POINT(35.913 31.953)",
"contact": { "phone": "+962790000000", "website": "https://example.com" },
"openingHours": { "weekly": { "sunday": "08:00-23:00" } },
"sponsored": true,
"claimStatus": "verified",
"rating": "4.60",
"entrances": [
{ "id": "a1b2c3d4-…", "type": "main", "label": "Main Entrance", "location": "POINT(35.9131 31.9529)" }
]
}
],
"total": 137,
"page": 1,
"limit": 50
}
List items include the entrances relation but not reviews/photos/questions. Ordering is always sponsored DESC, rating DESC, createdAt DESC.
GET /places/thumb
Lightweight thumbnail + open/closed status for a place — ideal for map cards.
curl -s "https://navvo.io/api/v1/places/thumb?slug=jordan-beans-cafe" \
-H "x-api-key: nvvo_YOUR_KEY"
Query parameters
Response
{
"url": "https://navvo.io/uploads/abc123.webp",
"source": "logo",
"rating": 4.6,
"openState": "open",
"openText": "Open · until 23:00"
}
| Field | Type | Description |
|---|---|---|
url | string | null | Best image — the logo, else a featured photo, else null. |
source | string | null | "logo", "photo", or null. |
rating | number | null | Rating if > 0, else null. |
openState | string | null | "open", "closing", "closed", or null (computed in Asia/Amman time). |
openText | string | null | Human-readable status. |
An unknown slug returns 200 with all fields null (not a 404).
GET /places/facets
The available amenity facets across places — grouped (Accessibility, Parking, Payments, Amenities, Service options, …) with per-option counts. Use it to build a "filter by amenity" UI, then pass the chosen slugs to GET /places?amenities=. Only enabled options are counted.
| Query | Type | Notes |
|---|---|---|
q | string | optional — narrow the facet counts to places whose name matches |
curl -s "https://navvo.io/api/v1/places/facets" \
-H "x-api-key: $NAVVO_API_KEY"
{
"scanned": 735,
"capped": false,
"groups": [
{ "id": "accessibility", "name": "Accessibility", "total": 201, "options": [
{ "slug": "wheelchair-accessible-entrance", "label": "Wheelchair-accessible entrance", "count": 72 },
{ "slug": "wheelchair-accessible-car-park", "label": "Wheelchair-accessible car park", "count": 63 }
]},
{ "id": "parking", "name": "Parking", "total": 90, "options": [
{ "slug": "free-parking-lot", "label": "Free parking lot", "count": 27 }
]}
]
}
| Field | Type | Notes |
|---|---|---|
scanned | number | places examined |
capped | boolean | true if the scan hit its cap (more places exist than were aggregated) |
groups[].id / name | string | facet group (e.g. accessibility) |
groups[].total | number | total enabled options counted in the group |
groups[].options[] | array | { slug, label, count }, sorted by count — the slug is what you pass to ?amenities= |
GET /places/categories
The full category taxonomy — a hierarchical tree (parent → children) with each category's display name, icon, color, and place counts (placeCount = directly in that category, totalPlaces = rolled up across its subtree). Use it to build a category browse/filter UI; the slug of any category is what places carry as canonicalCategorySlug.
curl -s "https://navvo.io/api/v1/places/categories" \
-H "x-api-key: $NAVVO_API_KEY"
{
"provider": "navvo-places", "count": 500, "rootCount": 38,
"categories": [
{ "slug": "restaurants", "nameEn": "Restaurants", "nameAr": "مطاعم",
"iconName": "restaurant", "color": "#386BFF",
"placeCount": 9, "totalPlaces": 9, "children": [] },
{ "slug": "automotive", "nameEn": "Automotive", "placeCount": 0, "totalPlaces": 0,
"children": [ { "slug": "car-rental", "nameEn": "Car Rental", "placeCount": 0, "totalPlaces": 0, "children": [] } ] }
]
}
| Field | Type | Notes |
|---|---|---|
count | number | total categories in the taxonomy |
rootCount | number | number of top-level categories |
categories[] | array | top-level nodes, each recursive with children |
…placeCount | number | places directly assigned to this category |
…totalPlaces | number | rolled-up count including all descendant categories |
POST /places/reachable
"What's reachable" — every place you can get to from an origin within a travel-time budget. Navvo computes the isochrone (the reachability polygon) for the time/mode, then returns the places that fall inside it — not just within a circle, so it respects the real road & footpath network. Optional name/category/amenity filters apply on top.
| Field | Type | Notes |
|---|---|---|
origin | {lat,lng} | required — where you're starting from |
minutes | number | travel-time budget (default 15; max 60) |
costing | string | travel mode: pedestrian (default), auto, bicycle |
q | string | optional name filter |
categories | string | optional comma-separated category keywords |
amenities | string | optional comma-separated amenity slugs (all required, enabled) |
limit | number | max results (default 100; max 500) |
curl -s -X POST "https://navvo.io/api/v1/places/reachable" \
-H "x-api-key: $NAVVO_API_KEY" -H "content-type: application/json" \
-d '{ "origin": {"lat":31.9539,"lng":35.9106}, "minutes": 15, "costing": "pedestrian", "categories": ["cafe"] }'
{
"provider": "navvo-places", "available": true,
"origin": { "lat": 31.9539, "lng": 35.9106 }, "minutes": 15, "costing": "pedestrian",
"candidatesInBbox": 12, "count": 9,
"reachable": [
{ "id": "…", "slug": "…", "name": "…", "category": "cafe", "rating": 4.6, "lat": 31.95, "lng": 35.91 }
],
"isochrone": { "type": "Polygon", "coordinates": [ /* … */ ] }
}
count is how many places are genuinely inside the reachability polygon (candidatesInBbox is the looser bounding-box prefilter — count ≤ it). The isochrone GeoJSON is returned so you can draw the reachable area on the map.
GET /places/{slug}
Full place detail: approved reviews & photos, questions + answers, and arrivalIntelligence.
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe" \
-H "x-api-key: nvvo_YOUR_KEY"
Path parameters
Response
In addition to all the fields from a list item, the detail response includes:
| Field | Type | Description |
|---|---|---|
reviews[] | array | Approved reviews (rating, body, userId, …). |
photos[] | array | Active, approved photos (url, caption, featured, …). |
questions[] | array | Questions with nested answers[]. |
reports | array | Always [] (never exposed). |
arrivalIntelligence | object | Recommended entrance and last-50-meters guidance (see below). |
{
"slug": "jordan-beans-cafe",
"nameEn": "Jordan Beans Cafe",
"center": "POINT(35.913 31.953)",
"rating": "4.60",
"entrances": [ { "id": "a1b2c3d4-…", "type": "main", "location": "POINT(35.9131 31.9529)" } ],
"reviews": [ { "id": "r1…", "rating": 5, "body": "Great coffee.", "status": "approved" } ],
"photos": [ { "id": "p1…", "url": "https://navvo.io/uploads/photo1.webp", "featured": true } ],
"questions": [
{ "id": "q1…", "question": "Is there parking?", "status": "answered",
"answers": [ { "id": "ans1…", "answer": "Yes, in the rear lot.", "ownerVerified": true } ] }
],
"reports": [],
"arrivalIntelligence": {
"version": "navvo-arrival-v1",
"placeCenter": { "lng": 35.913, "lat": 31.953 },
"recommendedEntranceId": "a1b2c3d4-…",
"candidates": [
{
"entranceId": "a1b2c3d4-…",
"type": "main",
"coordinates": { "lng": 35.9131, "lat": 31.9529 },
"distanceFromPlaceMeters": 12,
"score": 99,
"recommended": true,
"lastMetersGuidance": "12 meters from the place pin. Use this as the default arrival point for visitors.",
"routeTarget": { "lat": 31.9529, "lng": 35.9131 }
}
],
"microMap": { "radiusMeters": 50, "title": "Last 50 meters", "guidance": "…" }
}
}
arrivalIntelligence.candidates[0].routeTarget as the destination coordinate for routing instead of the place center — it points to the recommended entrance.An unknown slug (or id) returns 404 Not Found.
Errors
| Status | When |
|---|---|
400 | Validation (e.g. limit > 100). |
401 / 403 | Missing/invalid key, or key lacks the places scope. |
GET /places/{slug}/hours
The opening-hours engine: a place's normalized weekly schedule, its live open/closed state, and the next open/close transition — all evaluated in Jordan time (Asia/Amman) and holiday-aware.
Navvo accepts hours in two shapes and normalizes both: our structured { "weekly": { "monday": "09:00-17:00", … } } map, and the raw OSM-style opening_hours string that imported POIs carry (e.g. "Mo-Fr 09:00-17:00; Sa 10:00-14:00; Su off", "24/7", lunch breaks like "Mo-Fr 08:00-12:00,13:00-18:00"). The engine returns a single canonical weekly map either way.
| Query | Type | Notes |
|---|---|---|
at | ISO datetime | optional — evaluate at a future instant (trip planning); defaults to now |
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/hours" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe",
"hasHours": true,
"at": "2026-06-23T07:30:00.000Z",
"weekly": {
"monday": "09:00-17:00", "tuesday": "09:00-17:00", "wednesday": "09:00-17:00",
"thursday": "09:00-17:00", "friday": "09:00-17:00", "saturday": "10:00-14:00", "sunday": "Closed"
},
"openState": "open",
"openText": "Open · until 17:00",
"nextChange": { "type": "closes", "dayOffset": 0, "weekday": "tuesday", "atMinutes": 1020, "time": "17:00", "label": "Open · closes 17:00" },
"holiday": null
}
| Field | Type | Notes |
|---|---|---|
hasHours | boolean | false (with null fields) when the place has no hours data |
weekly | object | null | canonical schedule — per day a comma-separated HH:MM-HH:MM list, or "Closed". "00:00-24:00" = all day |
openState | string | null | "open", "closing" (≤45 min to close), "closed", or null |
openText | string | null | human label, e.g. "Open · until 17:00", "Closing soon · 17:00" |
nextChange | object | null | the next transition within 7 days: type (opens/closes), weekday, time, dayOffset (0 = today, 1 = tomorrow), and a ready label |
holiday | object | null | active public holiday (hours annotated "… hours may vary", never hard-closed) |
?at= in the future evaluates the schedule for that moment — useful for "will it be open when I arrive?" planning. Cross-midnight ranges (18:00-02:00) and Ramadan/holiday overlays are handled automatically.GET /places/{slug}/popular-times
Google-style busyness for a place: the weekly hour-by-hour grid, the live "now" level, today's curve with its peak, and the quietest open hour ("best time to visit"). All evaluated in Jordan time. Busyness is 0–100.
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/popular-times" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe", "hasData": true, "name": "Jordan Beans Cafe",
"now": { "day": "Saturday", "hour": 19, "busyness": 50, "label": "A bit busy", "relative": "typical" },
"today": {
"day": "Saturday", "peakHour": 10,
"curve": [18,31,31,43,50,50,43,31,37,43,43,37,31,37,50,75,81,93,75,62,62,43,31,25],
"bestTimeToVisit": { "hour": 5, "busyness": 25, "label": "Not too busy" }
},
"weekly": { "Sunday": [/* 24 values */], "Monday": [/* … */] }
}
| Field | Type | Notes |
|---|---|---|
hasData | boolean | false (with null fields) when no popular-times exist for the place |
now | object | live busyness (0–100) + human label + relative (near peak/typical/quieter than usual) |
today.curve | number24 | busyness per hour 0–23 for today; peakHour is its busiest hour |
today.bestTimeToVisit | object | the quietest open hour today — { hour, busyness, label } |
weekly | object | per-day 24-value busyness arrays (Sunday…Saturday) |
Labels: Not busy (<25), Not too busy (<50), A bit busy (<70), Busy (<85), As busy as it gets (≥85).
GET /places/{slug}/attributes
The full structured "About" attributes for one place — every group (Accessibility, Parking, Payments, Service options, Amenities, …) with each option and whether it's enabled. This is the per-place detail; for catalogue-wide facets use GET /places/facets, and to filter places by amenity use ?amenities=.
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/attributes" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe", "hasData": true, "name": "Jordan Beans Cafe",
"groupCount": 2, "enabledCount": 4,
"groups": [
{ "id": "payments", "name": "Payments", "options": [
{ "slug": "credit-cards", "label": "Credit cards", "enabled": true },
{ "slug": "nfc-mobile-payments", "label": "NFC mobile payments", "enabled": true }
]},
{ "id": "parking", "name": "Parking", "options": [
{ "slug": "on-site-parking", "label": "On-site parking", "enabled": true }
]}
]
}
| Field | Type | Notes |
|---|---|---|
hasData | boolean | false (with empty groups) when the place has no structured attributes |
groups[].id / name | string | attribute group (e.g. accessibility) |
groups[].options[] | array | { slug, label, enabled } — enabled:false means the place explicitly lacks that feature (e.g. "Wheelchair-accessible entrance: no") |
enabledCount | number | total enabled options across all groups |
GET /places/{slug}/reviews-summary
The rating breakdown for a place — average, total review count, the most common star level, and the full 5★→1★ distribution with counts and percentages. Ideal for a ratings bar chart without fetching every review.
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/reviews-summary" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe", "hasData": true, "name": "Jordan Beans Cafe",
"average": 4.6, "totalReviews": 128, "mostCommonStars": 5,
"distribution": [
{ "stars": 5, "count": 96, "percent": 75 },
{ "stars": 4, "count": 20, "percent": 15.6 },
{ "stars": 3, "count": 6, "percent": 4.7 },
{ "stars": 2, "count": 3, "percent": 2.3 },
{ "stars": 1, "count": 3, "percent": 2.3 }
]
}
| Field | Type | Notes |
|---|---|---|
hasData | boolean | false when the place has no rating/reviews |
average | number | null | the place's average rating (0–5) |
totalReviews | number | total number of reviews counted |
mostCommonStars | number | the star level with the most reviews |
distribution[] | array | { stars, count, percent } for 5★ down to 1★ (percent of total) |
GET /places/{slug}/photos
The photo gallery for a place — approved, active photos, featured first, paginated. Use it to render a place's image carousel.
| Query | Type | Notes |
|---|---|---|
page | number | 1-based page (default 1) |
limit | number | page size (default 30, max 100) |
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/photos?limit=20" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe", "name": "Jordan Beans Cafe",
"total": 6, "page": 1, "limit": 20, "count": 6,
"photos": [
{ "id": "…", "url": "/uploads/places/…/a59e624b.webp", "caption": "Latest", "featured": true, "mediaType": "image", "createdAt": "2026-05-…" }
]
}
| Field | Type | Notes |
|---|---|---|
total | number | total approved photos for the place |
count | number | photos on this page |
photos[].url | string | image URL (relative to the Navvo host) |
photos[].featured | boolean | featured photos are returned first |
photos[].caption | string | null | optional caption |
Only approved, active photos are returned (moderation applies). An unknown slug returns total: 0 with an empty photos array.
GET /places/{slug}/nearby
Other places near this one — a "similar places nearby" / "more like this" list, sorted by distance and excluding the place itself. Optionally restrict to the same category.
| Query | Type | Notes |
|---|---|---|
radiusKm | number | search radius (default 3, max 50) |
sameCategory | boolean | 1/true → only places sharing the anchor's category |
limit | number | max results (default 20, max 100) |
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/nearby?radiusKm=3&sameCategory=1" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe", "center": { "lat": 31.95, "lng": 35.91 },
"radiusKm": 3, "sameCategory": true, "anchorCategory": "cafe", "count": 2,
"places": [
{ "slug": "…", "name": "…", "category": "cafe", "rating": 4.5, "distanceKm": 0.4 }
]
}
| Field | Type | Notes |
|---|---|---|
anchorCategory | string | null | the category of the place you asked about |
places[].distanceKm | number | straight-line distance from the anchor place |
GET /places/{slug}/entrances
The place's entrances/gates as a lightweight standalone read — the companion to the entrances[] embedded on the place detail, so a navigation client can refresh gates without refetching the whole place.
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/entrances" \
-H "x-api-key: $NAVVO_API_KEY"
{
"slug": "jordan-beans-cafe",
"items": [
{ "id": "a1b2c3d4-…", "type": "main", "label": "Main Entrance", "location": "POINT(35.9131 31.9529)" }
],
"total": 1
}
items is empty (total: 0) when no dedicated entrances are configured — route to the place pin in that case. An unknown slug returns 404.
GET /places/{slug}/questions
The public Q&A thread for a place — questions with their answers, newest first. Read-only for API consumers; asking and answering are signed-in user features.
curl -s "https://navvo.io/api/v1/places/jordan-beans-cafe/questions" \
-H "x-api-key: $NAVVO_API_KEY"
[
{
"id": "…", "question": "Is there outdoor seating?", "status": "answered", "createdAt": "…",
"answers": [
{ "id": "…", "answer": "Yes — a shaded terrace.", "ownerVerified": true, "createdAt": "…" }
]
}
]
The response is a plain array (empty when there are no questions). answers[].ownerVerified marks replies from the verified business owner.
POST /places/suggest
"Add a missing place" — let signed-in users submit a place that isn't on the map yet. Submissions land in Navvo's moderation queue and are reviewed by an editor; nothing appears on the map until approved.
Requires a signed-in Navvo user (Authorization: Bearer <accessToken> — see Users & Sync), not an API key.
| Field | Type | Notes |
|---|---|---|
name | string | required — place name (English) |
nameAr | string | optional Arabic name |
lat / lng | number | required — location |
category | string | optional category slug (see GET /places/categories) |
phone | string | optional |
website | string | optional |
note | string | optional — anything the reviewer should know |
curl -s -X POST "https://navvo.io/api/v1/places/suggest" \
-H "authorization: Bearer $NAVVO_USER_TOKEN" -H "content-type: application/json" \
-d '{ "name": "Abu Ahmad Falafel", "nameAr": "فلافل أبو أحمد", "lat": 31.9539, "lng": 35.9106, "category": "restaurants" }'
{ "ok": true, "id": "…", "status": "quarantined", "note": "Thanks! Your suggestion is queued for review." }
status is always quarantined on intake — the suggestion is held for review. Missing name or non-numeric lat/lng returns 400.
User-authored content
Writing reviews, photos, questions, and reports is a first-party, end-user feature — those endpoints require a logged-in user session (Bearer JWT), not an API key, and are not part of the integration API. If your product needs user-generated content, contact Navvo to discuss partner access.