Places endpoints

Search places, fetch place detail and KPIs, visit time series with benchmarks, hourly heatmaps, trade areas, cross-shopping, CSV export, nearby places and multi-place comparison.

Places are the core of MapDove’s foot-traffic data. All endpoints below require authentication and are relative to https://api.mapdove.com/api/v1. Endpoints that take from/to follow the date-range rules — default last 90 days, clamped to your plan’s history.

Method Path Purpose
GET /places Search and list places
GET /places/geojson All matching places as GeoJSON points
GET /places/:id Place detail and KPIs
GET /places/:id/visits Visit series with category benchmark
GET /places/:id/hourly 7 × 24 hourly heatmap
GET /places/:id/trade-area Modeled trade area
GET /places/:id/cross-shopping Places with overlapping visitor bases
GET /places/:id/export.csv Daily data as CSV
GET /places/:id/nearby Places within a radius
GET /compare Up to six places side by side

The PlaceSummary object

Most endpoints return places as PlaceSummary:

{
  "id": 42, "name": "Bean Republic · Downtown", "address": "1408 Congress Ave, Downtown", "city": "austin",
  "category": { "slug": "coffee", "name": "Coffee & Cafés", "color": "#92400e" },
  "brand": { "slug": "bean-republic", "name": "Bean Republic" },
  "neighborhood": "Downtown", "lng": -97.7412, "lat": 30.2689, "sqft": 1900,
  "visits": 34567, "visits_prev": 33012, "change_pct": 4.7,
  "avg_daily_visits": 384, "rank_in_category": 3
}

brand is null for independents. visits covers the requested range; visits_prev the previous equal-length range. distance_km is added by /nearby.

List places

GET /places

Param Description
city required city slug
category category slug
brand brand slug
q case-insensitive name search
bbox minLng,minLat,maxLng,maxLat
sort visits (default), change or name
limit default 100, max 500
offset default 0
from, to date range
curl "https://api.mapdove.com/api/v1/places?city=austin&category=coffee&sort=visits&limit=2" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
const params = new URLSearchParams({ city: 'austin', category: 'coffee', limit: '2' });
const { total, items } = await fetch(`https://api.mapdove.com/api/v1/places?${params}`, {
  headers: { Authorization: `Bearer ${key}` },
}).then((r) => r.json());
r = requests.get(f"{BASE}/places", headers=H,
                 params={"city": "austin", "category": "coffee", "limit": 2}, timeout=30)
data = r.json()
print(data["total"], [p["name"] for p in data["items"]])
{
  "total": 34,
  "items": [
    { "id": 42, "name": "Bean Republic · Downtown", "visits": 34567, "change_pct": 4.7, "avg_daily_visits": 384, "rank_in_category": 1, "…": "…" },
    { "id": 57, "name": "Copperline Coffee · The Domain", "visits": 31204, "change_pct": -1.3, "avg_daily_visits": 347, "rank_in_category": 2, "…": "…" }
  ]
}

Places as GeoJSON

GET /places/geojson — every matching place as a point, for maps. Params: city (required), category, brand.

curl "https://api.mapdove.com/api/v1/places/geojson?city=phoenix&category=grocery" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [-112.0281, 33.5104] },
      "properties": { "id": 611, "name": "FreshCart Market · Biltmore", "category": "grocery", "color": "#16a34a", "brand": "freshcart", "visits_30d": 51230 }
    }
  ]
}

Place detail

GET /places/:id — PlaceSummary plus opening date, footprint polygon, the resolved range and KPIs. Params: from, to.

curl "https://api.mapdove.com/api/v1/places/42?from=2026-08-01&to=2026-10-31" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
place = requests.get(f"{BASE}/places/42", headers=H,
                     params={"from": "2026-08-01", "to": "2026-10-31"}, timeout=30).json()
print(place["kpis"]["frequency"], place["kpis"]["percentile_in_city"])
{
  "id": 42, "name": "Bean Republic · Downtown", "…": "…PlaceSummary fields…",
  "opened_on": "2016-03-02",
  "footprint": { "type": "Polygon", "coordinates": [["…"]] },
  "range": { "from": "2026-08-01", "to": "2026-10-31" },
  "kpis": {
    "visits": 35321, "visitors": 16014, "frequency": 2.21, "avg_dwell_min": 19.5,
    "avg_daily_visits": 384, "change_pct": 2.1, "yoy_trend_pct": 6.0,
    "category_avg_daily_visits": 300, "percentile_in_city": 81,
    "busiest_dow": 2, "busiest_hour": 8
  }
}

busiest_dow uses 0 = Sunday. KPI definitions: Core concepts.

Visits

GET /places/:id/visits — a time series and the category benchmark (average per place for the same category and city).

Param Description
from, to date range
granularity day (default), week or month
curl "https://api.mapdove.com/api/v1/places/42/visits?granularity=week" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
{
  "granularity": "week",
  "series": [
    { "date": "2026-08-03", "visits": 2710, "visitors": 2105, "dwell_min": 19.2 },
    { "date": "2026-08-10", "visits": 2754, "visitors": 2139, "dwell_min": 19.4 }
  ],
  "benchmark": [
    { "date": "2026-08-03", "visits": 2090, "visitors": 1630, "dwell_min": 18.1 },
    { "date": "2026-08-10", "visits": 2112, "visitors": 1647, "dwell_min": 18.0 }
  ]
}

Hourly

GET /places/:id/hourly — average visits by weekday and hour. Params: from, to.

curl https://api.mapdove.com/api/v1/places/42/hourly \
  -H "Authorization: Bearer $MAPDOVE_KEY"
{
  "matrix": [
    [0, 0, 0, 0, 0, 3.1, 12.4, 24.8, 27.9, "…24 values…"],
    "…7 rows, 0 = Sunday…"
  ],
  "peak": { "dow": 2, "hour": 8 }
}

matrix[dow][hour] is average visits in that hour. Hourly shape comes from a category profile in the demo dataset (Demo data).

Trade area

GET /places/:id/trade-area — the hexes supplying the first 85% of modeled visitors, with a demographic summary. No params. See Trade areas.

curl https://api.mapdove.com/api/v1/places/42/trade-area \
  -H "Authorization: Bearer $MAPDOVE_KEY"
{
  "summary": {
    "hexes": 52, "area_km2": 41.2, "population": 96310, "households": 44820,
    "median_income": 98200, "median_age": 33.4, "pct_college": 51.2, "pct_family": 27.9,
    "avg_distance_km": 3.1, "p50_distance_km": 2.4,
    "segments": [
      { "slug": "urban-professionals", "name": "Urban Professionals", "color": "#14b8a6", "share": 0.46 },
      { "slug": "young-renters", "name": "Young Renters", "color": "#ec4899", "share": 0.21 }
    ],
    "neighborhoods": [ { "name": "Downtown", "share": 0.31 }, { "name": "East Austin", "share": 0.18 } ]
  },
  "geojson": {
    "type": "FeatureCollection",
    "features": [
      { "type": "Feature", "id": 812, "geometry": { "type": "Polygon", "coordinates": ["…"] },
        "properties": { "hex_id": 812, "share": 0.06412, "cum_share": 0.0641, "population": 2210, "median_income": 121000, "segment": "urban-professionals" } }
    ]
  }
}

Cross-shopping

GET /places/:id/cross-shopping — places whose modeled visitor-origin footprint overlaps most, ranked by affinity. See Cross-shopping.

Param Description
limit default 10, max 50
curl "https://api.mapdove.com/api/v1/places/42/cross-shopping?limit=3" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
[
  { "place": { "id": 133, "name": "IronPulse Fitness · Downtown", "category": { "slug": "fitness", "…": "…" }, "…": "…" }, "overlap": 0.66, "affinity": 0.89 },
  { "place": { "id": 201, "name": "Urban Loom · South Congress", "…": "…" }, "overlap": 0.58, "affinity": 0.78 },
  { "place": { "id": 77, "name": "Downtown Fresh Market", "…": "…" }, "overlap": 0.71, "affinity": 0.71 }
]

Export CSV

GET /places/:id/export.csv — daily rows for the range. Params: from, to. Requires a plan with exports (Pro and above); otherwise 402 plan_limit. Each successful download counts as one export.

curl "https://api.mapdove.com/api/v1/places/42/export.csv?from=2026-10-01&to=2026-10-31" \
  -H "Authorization: Bearer $MAPDOVE_KEY" -o place-42.csv
day,visits,visitors,dwell_min
2026-10-01,402,318,19.6
2026-10-02,397,311,18.9

Nearby

GET /places/:id/nearby — other places within a radius, closest first, with distance_km.

Param Description
radius_km default 2
category optional category slug
curl "https://api.mapdove.com/api/v1/places/42/nearby?radius_km=1&category=coffee" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
[
  { "id": 63, "name": "Downtown Espresso Bar", "avg_daily_visits": 262, "change_pct": 0.8, "distance_km": 0.42, "…": "…" }
]

Compare

GET /compare — up to six places on a common basis.

Param Description
ids required, comma-separated, up to 6
from, to date range
granularity day, week or month
curl "https://api.mapdove.com/api/v1/compare?ids=42,57,63&granularity=month" \
  -H "Authorization: Bearer $MAPDOVE_KEY"
const cmp = await fetch('https://api.mapdove.com/api/v1/compare?ids=42,57,63&granularity=month', {
  headers: { Authorization: `Bearer ${key}` },
}).then((r) => r.json());
for (const p of cmp.places) console.log(p.name, cmp.series[p.id].length);
{
  "places": [ { "id": 42, "name": "Bean Republic · Downtown", "…": "…" }, { "id": 57, "…": "…" }, { "id": 63, "…": "…" } ],
  "series": {
    "42": [ { "date": "2026-08-01", "visits": 11902, "visitors": 6120, "dwell_min": 19.3 } ],
    "57": [ { "date": "2026-08-01", "visits": 10811, "visitors": 5502, "dwell_min": 18.4 } ]
  },
  "hourly": {
    "42": [0, 0, 0, 0, 0, 3.5, 13.9, 27.8, 31.3, 24.3, "…24 values…"]
  }
}

hourly[id] is the average visits per hour of day across the range.