API Reference

The GeoContext API gives your AI agent a sense of place — ranked semantic POI results, neighbourhood profiles, and timezone in a single call.

Base URL https://api.bitemap.ai
Version v1
Pattern Async (fire & forget)
All endpoints use an async job pattern. Submit returns job_id (HTTP 202). Poll GET /jobs/{job_id} for the result.
Not available via this API: transit departures, street-level imagery, and weather are intentionally excluded for licensing reasons. transit, streetview, and weather are always null. The neighborhood and map enrichments are available.

Authentication

Every public endpoint (except GET /health) requires a Bearer token:

Authorization: Bearer <your_api_token>
StatusDetail
401Authorization: Bearer <token> required — header missing or not Bearer …
403invalid token — well-formed header, unknown token

Async Job Pattern

All submit endpoints return a job_id immediately. The request is processed asynchronously by the API backend. Client polls until the result is ready.

1

Submit

POST your request. Receive job_id in a 202 response instantly.

2

Poll

GET /jobs/{id} every second. Status moves from queuedprocessing.

3

Result

Status becomes complete. Full result in the same response body.

# Submit POST /forward → 202 { "job_id": "3f8e2a1b-...", "status": "queued" } # Poll (repeat until complete) GET /jobs/3f8e2a1b-... → { "status": "processing" } GET /jobs/3f8e2a1b-... → { "status": "complete", "result": { ... } }

Job statuses

StatusMeaning
queuedAccepted, awaiting processing
processingBeing processed by the API backend
completeResult available in result field
errorExecution failed; see error field

Recommended polling strategy

1. Submit → receive job_id
2. Wait 500 ms
3. Poll GET /jobs/{job_id} every 1 s
4. Stop after 120 s (service may be unavailable)

POST /forward

Semantic geospatial POI search. The primary search endpoint. Finds ranked points of interest matching a natural-language query near a coordinate.

Request body

{
  "query": "wheelchair-accessible Italian restaurant",
  "lat": 52.5200,
  "lon": 13.4050,
  "radius_m": 1000,
  "max_results": 10,
  "neighborhood": false,
  "map": false
}
FieldTypeRequiredDefaultDescription
querystringyesNatural language search query (1–500 chars)
latfloatrecommendedLatitude (-90 to 90). If omitted, the location is resolved from the query text.
lonfloatrecommendedLongitude (-180 to 180). Pair with lat.
regionstringnonullFree-text geographic context appended before geocoding to disambiguate place names, e.g. "New York, USA" so "Central Park" resolves to Manhattan, not Buffalo. Only applies when geocoding from the query; ignored when lat/lon are given.
viewboxfloat[4]nonullBias geocoding toward a bounding box [min_lon, min_lat, max_lon, max_lat]. Soft preference unless bounded=true. Ignored when lat/lon are given.
boundedboolnofalseWhen true and viewbox is set, restrict geocoding strictly to the box.
boundaryobjectnonullGeoJSON Polygon/MultiPolygon to restrict results to — only POIs inside the polygon are returned (no spillover). Pass the geometry from a POST /boundaries result to scope a search to an admin region. Replaces radius_m when set. See the region-scoping note below.
radius_mintno1000Search radius in metres (200–50,000). Ignored when boundary is supplied.
max_resultsintno10Max primary results (1–100). Also accepted as result_count_limit (alias). Requesting the maximum adds only ~6 ms latency vs the default.
result_count_limitintno10Alias of max_results (1–100). Use whichever name you prefer; do not pass both.
categoriesstring[]nonullRestrict to OSM category keys, e.g. ["amenity","shop"]
filtersobjectnonullHard OSM tag filters, e.g. {"cuisine":"italian"}
timestringnonull"now" or ISO 8601. Optional — open status is annotated by default (see below).
languagestringno"en"BCP-47 tag for name resolution (e.g. "de")
neighborhoodboolnofalseInclude neighbourhood POI profile
mapboolnofalseInclude static map image URL
Disambiguating place names. When you rely on query-text geocoding (no lat/lon), names that exist in several regions ("Central Park", "Chelsea") may resolve to the wrong one. Pass region (e.g. "New York, USA") and/or a viewbox box to bias resolution to the intended area, e.g. { "query": "coffee near Central Park with wifi", "region": "New York, USA" }.
Scoping a search to an administrative region (no spillover). To answer "restaurants with wifi in Manhattan that have wheelchair access" while excluding everything outside the region, chain POST /boundaries with the boundary parameter. radius_m/viewbox are circles/rectangles that spill across borders; boundary clips to the exact polygon.
  1. Resolve the region: POST /boundaries { "query": "Manhattan New York", "admin_level": 8, "region": "US-NY" } → returns its geometry + bbox.
  2. Search inside it: POST /forward { "query": "restaurants with wifi and wheelchair access", "boundary": <geometry> }.
Every returned POI lies inside the Manhattan polygon — a point in Brooklyn, Queens, or across the Hudson in New Jersey is excluded, even if it falls within Manhattan's bounding box. lat/lon are optional (proximity is scored from the region centre); radius_m is ignored.
Opening hours are annotated automatically. Every result includes is_open and opening_hours, evaluated at "now" in the venue's local timezone — no time parameter needed. is_open is null when the venue has no parseable opening_hours.

Response

The result is the full search response. Note the POI array is primary_results (not results), and identity / tags / coordinates are nested.

{
  "job_id": "3f8e2a1b-4c9d-4e5f-8a1b-2c3d4e5f6a7b",
  "status": "complete",
  "result": {
    "query_interpretation": {
      "original_query": "wheelchair-accessible Italian restaurant",
      "resolved_location": "Mitte, Berlin, Germany",
      "anchor_lat": 52.52, "anchor_lon": 13.405,
      "search_radius_m": 1000, "search_mode": "provided"
    },
    "primary_results": [
      {
        "rank": 1,
        "relevance_score": 0.91,
        "proximity_score": 0.88,
        "combined_score": 0.89,
        "identity": {
          "osm_id": 123456789, "osm_type": "node",
          "name": "Trattoria Roma", "name_translations": { "it": "Trattoria Roma" }
        },
        "classification": {
          "primary_category": "amenity", "subcategory": "restaurant",
          "tags": { "amenity": "restaurant", "cuisine": "italian", "wheelchair": "yes", "outdoor_seating": "yes" }
        },
        "location": {
          "lat": 52.5198, "lon": 13.4048,
          "distance_from_anchor_m": 45.0, "distance_human": "45 m",
          "address": "Unter den Linden 1, Berlin"
        },
        "agent_summary": "Trattoria Roma (restaurant) — 45 m away at Unter den Linden 1, Berlin",
        "osm_url": "https://www.openstreetmap.org/node/123456789",
        "is_open": true,
        "opening_hours": "Mo-Su 12:00-22:00",
        "attributes": { "cuisine": "italian", "outdoor_seating": "yes", "wheelchair": "yes" },
        "confidence_score": 0.95, "confidence_label": "high"
      }
    ],
    "neighborhood_context": null,
    "administrative_context": {
      "country": "Germany", "country_code": "de", "region": "Berlin",
      "city": "Berlin", "postcode": "10117", "timezone": "Europe/Berlin"
    },
    "agent_instructions": {
      "limitations": ["Opening hours may not reflect holidays or recent changes.", "Distances are straight-line, not walking or driving distances."],
      "suggested_follow_up_queries": ["Find parking near Trattoria Roma"]
    },
    "search_metadata": {
      "latency_ms": 312.4, "vector_search_latency_ms": 22.7, "total_candidates": 38,
      "cache_hit": false,
      "matched_categories": ["food"],
      "category_gate_relaxed": false,
      "diet_filter_relaxed": false
    },
    "comparison_matrix": null,
    "map": null
  }
}

Notable result fields

FieldDescription
primary_results[]Ranked results. Field name is primary_results, not results; name is identity.name, tags are classification.tags.
is_opentrue/false/null. Annotated by default at venue-local "now".
attributesCurated tags lifted top-level: wheelchair, outdoor_seating, cuisine, takeaway, delivery, internet_access, smoking, phone, website, brand, fee, plus a collapsed diet.
search_metadata.matched_categoriesEntity types the query matched — a query is gated to its type (e.g. "playground" won't return subway entrances).
search_metadata.category_gate_relaxedtrue if the category gate was widened for too few results.
search_metadata.diet_filter_relaxedtrue if a dietary preference (halal/kosher/vegan/…) was relaxed to semantic ranking.
Dietary & accessibility queries are detected automatically from the query text — "halal restaurant" prefers diet:halal venues; "wheelchair accessible cafe" filters on wheelchair=yes. No special parameters required.

curl example

curl -X POST https://api.bitemap.ai/forward \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "wheelchair-accessible Italian restaurant",
    "lat": 52.52, "lon": 13.405
  }'
# → { "job_id": "3f8e...", "status": "queued" }

curl https://api.bitemap.ai/jobs/3f8e2a1b-... \
  -H "Authorization: Bearer $API_TOKEN"
# → { "job_id": "...", "status": "complete", "result": { "primary_results": [ ... ], ... } }

POST /neighbourhood

Returns a POI category profile for a coordinate — useful for understanding what kind of area a location is in.

Request body

{
  "lat": 52.5200,
  "lon": 13.4050,
  "radius_m": 500
}

Response result

{
  "anchor_lat": 52.52,
  "anchor_lon": 13.405,
  "search_radius_m": 500,
  "neighborhood_context": {
    "category_summary": {
      "restaurants":   { "count": 23, "nearest_m": 45.0 },
      "transport":     { "count": 6,  "nearest_m": 210.0 },
      "supermarkets":  { "count": 2,  "nearest_m": 151.0 },
      "healthcare":    { "count": 4,  "nearest_m": 168.0 },
      "education":     { "count": 3,  "nearest_m": 181.0 },
      "accommodation": { "count": 8,  "nearest_m": 63.0 },
      "attractions":   { "count": 5,  "nearest_m": 208.0 },
      "parks_leisure": { "count": 4,  "nearest_m": 87.0 },
      "finance":       { "count": 7,  "nearest_m": 82.0 }
    },
    "walkability_profile": {
      "nearest_public_transport_m": 210.0,
      "density_score": 0.82,
      "density_label": "very dense urban"
    }
  },
  "administrative_context": {
    "country": "Germany", "country_code": "de", "region": "Berlin",
    "city": "Berlin", "postcode": "10117", "timezone": "Europe/Berlin"
  },
  "map": null
}

POST /reverse

Full street-level reverse geocoding: coordinates → address + optional context components.

Request body

{
  "lat": 52.5200,
  "lon": 13.4050,
  "neighborhood": false,
  "map": false
}

Response result

Address components are flat top-level fields (null when unavailable), not a nested object.

{
  "lat": 52.52,
  "lon": 13.405,
  "display_name": "Unter den Linden 1, Mitte, Berlin, 10117, Germany",
  "name": "Brandenburg Gate",
  "place_type": "tourism",
  "osm_type": "way",
  "osm_id": 5123456,
  "house_number": "1",
  "road": "Unter den Linden",
  "suburb": "Mitte",
  "city": "Berlin",
  "postcode": "10117",
  "region": "Berlin",
  "country": "Germany",
  "country_code": "de",
  "boundingbox": [52.5159, 52.5165, 13.3769, 13.3784],
  "latency_ms": 41.0,
  "neighborhood_context": null,
  "map": null
}

POST /context

Fat context endpoint — address + timezone + neighbourhood in a single parallel call. Designed for pre-loading LLM system prompts.

Request body

{
  "lat": 52.5200,
  "lon": 13.4050
}

Response result

Sub-objects reuse the /reverse, /timezone, and /neighbourhood schemas. weather and transit are always null.

{
  "lat": 52.52,
  "lon": 13.405,
  "address": { "display_name": "Unter den Linden 1, Mitte, Berlin, 10117, Germany", "road": "Unter den Linden", "city": "Berlin", "postcode": "10117", "country_code": "de", "latency_ms": 38.0 },
  "timezone": { "lat": 52.52, "lon": 13.405, "timezone_id": "Europe/Berlin", "utc_offset_s": 7200, "utc_offset_str": "+02:00", "dst_active": true },
  "weather": null,
  "neighbourhood": { "anchor_lat": 52.52, "anchor_lon": 13.405, "search_radius_m": 500, "neighborhood_context": { "category_summary": {}, "walkability_profile": {} } },
  "transit": null,
  "latency_ms": 410.0,
  "warnings": []
}

POST /boundaries

Semantic search over administrative boundaries (states, counties, incorporated places) — a separate capability from POI search: returns polygon geometry for a named region rather than points. Currently covers the United States (50 states + DC, counties, cities/towns/places).

Request body

{
  "query": "Brooklyn",
  "limit": 5,
  "admin_level": 8,
  "region": "US-NY"
}
FieldTypeRequiredDefaultDescription
querystringyesNatural-language region name, e.g. "Brooklyn" or "King County Washington" (1–300 chars)
limitintno5Max matches to return (1–50)
admin_levelintnonullRestrict to one level: 4 = state, 6 = county, 8 = city/town/place
regionstringnonullRestrict to one ISO 3166-2 region, e.g. "US-NY". Combine with admin_level to scope semantic search to e.g. "counties in NY state"

Response result

{
  "query": "Brooklyn",
  "count": 1,
  "results": [
    {
      "id": "abb24048-d76a-4457-95d0-6c39e7be5c62",
      "name": "Brooklyn",
      "admin_level": 8,
      "subtype": "locality",
      "gers_id": "abb24048-d76a-4457-95d0-6c39e7be5c62",
      "country": "US",
      "region": "US-NY",
      "area_km2": 92.53,
      "centroid_lat": 40.6456,
      "centroid_lon": -73.9559,
      "bbox": [-74.0419, 40.5707, -73.8334, 40.7394],
      "geometry": { "type": "MultiPolygon", "coordinates": [ "..." ] },
      "relevance_score": 0.90
    }
  ]
}
FieldDescription
admin_level4 = state, 6 = county, 8 = city/town/place
area_km2null for a real share of boundaries (self-intersecting source polygons) — treat as optional, never assume it's present
bbox[min_lon, min_lat, max_lon, max_lat] of the geometry (null if absent). Feed into /forward's viewbox, or the geometry into /forward's boundary, to scope a POI search to this region.
geometryFull GeoJSON Polygon / MultiPolygon
relevance_scoreCosine similarity to the query text

curl example

curl -X POST https://api.bitemap.ai/boundaries \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "Brooklyn", "limit": 5}'
# → { "job_id": "3f8e...", "status": "queued" }

curl https://api.bitemap.ai/jobs/3f8e2a1b-... \
  -H "Authorization: Bearer $API_TOKEN"
# → { "job_id": "...", "status": "complete", "result": { "results": [ ... ], ... } }

POST /boundaries/reverse

Point-in-polygon lookup: given a coordinate, returns every administrative boundary containing it, ordered coarse → fine (state → county → place).

Request body

{ "lat": 40.7580, "lon": -73.9855 }
FieldTypeRequiredDescription
latfloatyesLatitude (-90 to 90)
lonfloatyesLongitude (-180 to 180)

Response result

{
  "lat": 40.758,
  "lon": -73.9855,
  "count": 4,
  "containing": [
    { "name": "New York",        "admin_level": 4, "region": "US-NY", "geometry": { "...": "..." }, "relevance_score": null },
    { "name": "New York County", "admin_level": 6, "region": "US-NY", "geometry": { "...": "..." }, "relevance_score": null },
    { "name": "New York",        "admin_level": 8, "region": "US-NY", "geometry": { "...": "..." }, "relevance_score": null },
    { "name": "Manhattan",       "admin_level": 8, "region": "US-NY", "geometry": { "...": "..." }, "relevance_score": null }
  ]
}
/boundaries/reverse vs. /reverse: /reverse returns a street-level address for a point. /boundaries/reverse returns the administrative region polygons containing that point — use it when an agent needs to know, or constrain results to, the exact boundary of a state, county, or city, not just its name. Results are always ordered state → county → place, so the last entry in containing is the most specific region.

curl example

curl -X POST https://api.bitemap.ai/boundaries/reverse \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"lat": 40.7580, "lon": -73.9855}'
# → { "job_id": "3f8e...", "status": "queued" }

curl https://api.bitemap.ai/jobs/3f8e2a1b-... \
  -H "Authorization: Bearer $API_TOKEN"
# → { "job_id": "...", "status": "complete", "result": { "containing": [ ... ], ... } }

GET /boundaries/children

Every administrative boundary geometrically within a parent boundary — e.g. every county inside a specific state, or every place inside a specific county. A structured listing, not a semantic search: there's no natural-language query for "every county in NY," so this returns all matches directly rather than ranking by similarity to query text. Use it to drill down from a /boundaries result (state → its counties → a county's places).

Query parameters

GET /boundaries/children?parent_id=abb24048-d76a-4457-95d0-6c39e7be5c62&admin_level=6&limit=100
ParameterTypeRequiredDefaultDescription
parent_idstringyesThe id field from a /boundaries or /boundaries/reverse result — identifies the exact parent boundary to search within
admin_levelintnonullRestrict to one level: 4 = state, 6 = county, 8 = place. Omit to list every level within the parent.
limitintno100Max results (1–500)
Why parent_id and not a region code: filtering by state alone would be wrong for county→place lookups — every place in California shares the same state-level region code, so "children of Orange County" would incorrectly include places from every other CA county too. parent_id resolves the parent's actual geometry and only returns candidates whose centroid falls inside it, which is correct at any level.

Response result

{
  "parent_id": "d4532322-0808-4d95-9516-af840ae84f27",
  "admin_level": 6,
  "count": 2,
  "children": [
    { "name": "Albany County", "admin_level": 6, "region": "US-NY", "geometry": { "...": "..." }, "relevance_score": null },
    { "name": "Erie County",   "admin_level": 6, "region": "US-NY", "geometry": { "...": "..." }, "relevance_score": null }
  ]
}
FieldDescription
children[]Same BoundaryResult shape as /boundaries and /boundaries/reverse. relevance_score is always null here — there's no query to score against.

curl example

curl "https://api.bitemap.ai/boundaries/children?parent_id=abb24048-d76a-4457-95d0-6c39e7be5c62&admin_level=6&limit=100" \
  -H "Authorization: Bearer $API_TOKEN"
# → { "job_id": "3f8e...", "status": "queued" }

curl https://api.bitemap.ai/jobs/3f8e2a1b-... \
  -H "Authorization: Bearer $API_TOKEN"
# → { "job_id": "...", "status": "complete", "result": { "children": [ ... ], ... } }

GET /timezone

IANA timezone, UTC offset, and DST status for a coordinate.

Query parameters

GET /timezone?lat=52.52&lon=13.405

Response result

The timezone_id and offset fields are null over open ocean.

{
  "lat": 52.52,
  "lon": 13.405,
  "timezone_id": "Europe/Berlin",
  "utc_offset_s": 7200,
  "utc_offset_str": "+02:00",
  "dst_active": true
}

GET /changes

Returns entities modified since a given timestamp within a bounding box. Useful for cache invalidation or change detection.

Query parameters

ParameterTypeRequiredDescription
sincestringyesISO 8601 datetime, e.g. 2024-01-01T00:00:00Z
bboxstringyesminLat,minLon,maxLat,maxLon, e.g. 52.50,13.39,52.54,13.42
limitintnoMax results (default 100)

Example

curl "https://api.bitemap.ai/changes?since=2024-01-01T00:00:00Z&bbox=52.50,13.39,52.54,13.42" \
  -H "Authorization: Bearer $API_TOKEN"

Response result

{
  "since": "2024-01-01T00:00:00Z",
  "bbox": [52.50, 13.39, 52.54, 13.42],
  "count": 1,
  "entities": [
    {
      "osm_id": 123,
      "osm_type": "node",
      "name": "Café Berlino",
      "category": "amenity",
      "subcategory": "cafe",
      "lat": 52.5201,
      "lon": 13.4011,
      "last_modified": "2024-03-14T09:12:00+00:00",
      "tags": { "amenity": "cafe", "name": "Café Berlino" }
    }
  ]
}

GET /jobs/{job_id}

Poll for job status and result. Authentication required.

Responses

{ "job_id": "3f8e...", "status": "queued" }
{ "job_id": "3f8e...", "status": "processing" }
{ "job_id": "3f8e...", "status": "complete", "result": { ... } }
{ "job_id": "3f8e...", "status": "error", "error": "upstream error" }
Jobs are retained for 24 hours after completion. After that, GET /jobs/{id} returns 404.

GET /health

Liveness check. No authentication required.
{ "status": "ok" }

Rate Limits

Each Bearer token is subject to a sliding-window rate limit:

LimitValue
Requests per token30 per 60 seconds
Window typeSliding (rolling 60-second window)

When the limit is exceeded the server responds with HTTP 429:

{"detail": "rate limit exceeded"}

If the global job queue exceeds 500 pending jobs, new submissions also return HTTP 429:

{"detail": "queue full, try again later"}
No Retry-After or X-RateLimit-* headers are sent. On a 429, back off 1–5 seconds and retry — repeated 429s will not unblock faster.

Error Codes

HTTPCause
401Missing or malformed Authorization header
403Invalid Bearer token
404GET /jobs/{id} — job not found or expired
422Request body/query validation error (missing or out-of-range fields)
429Rate limit exceeded or queue full — back off and retry
A job that fails during processing returns HTTP 200 on the poll with status: "error" and an error message — not an HTTP 5xx.

Polling Guide

The recommended client polling loop:

import httpx, time

BASE  = "https://api.bitemap.ai"
HDRS  = {"Authorization": "Bearer YOUR_TOKEN", "Content-Type": "application/json"}

def search(query: str, lat: float, lon: float) -> dict:
    # 1. Submit
    r = httpx.post(f"{BASE}/forward",
                   json={"query": query, "lat": lat, "lon": lon},
                   headers=HDRS)
    job_id = r.json()["job_id"]

    # 2. Poll
    time.sleep(0.5)
    for _ in range(120):
        r = httpx.get(f"{BASE}/jobs/{job_id}", headers=HDRS)
        data = r.json()
        if data["status"] == "complete":
            return data["result"]   # → result["primary_results"], result["search_metadata"], ...
        if data["status"] == "error":
            raise RuntimeError(data.get("error", "unknown error"))
        time.sleep(1.0)

    raise TimeoutError("job did not complete within 120 s")

MCP Tool Example

Wrap POST /context as a Model Context Protocol tool to give any LLM a sense of place before inference:

# MCP tool definition
{
  "name": "get_location_context",
  "description": "Get rich geospatial context for a coordinate: address, timezone, POI profile.",
  "input_schema": {
    "type": "object",
    "properties": {
      "lat": { "type": "number", "description": "Latitude" },
      "lon": { "type": "number", "description": "Longitude" }
    },
    "required": ["lat", "lon"]
  }
}

# Handler: POST /context → poll /jobs/{id} → return result as tool output
The /context endpoint runs address, timezone, and neighbourhood lookups in parallel — a single tool call gives the LLM full environmental awareness.

Pricing

TierPriceIncluded requestsOverage
Free$0 / mo5,000 / mo (first 3 months, then 3,000 / mo)
Starter$5.90 / mo10,000$0.85 / 1,000
Growth$36 / mo100,000$0.37 / 1,000
Pro$120 / mo600,000$0.20 / 1,000
EnterpriseCustomCustom volume + SLASelf-hosted option available

Straightforward usage-based pricing with a generous free tier — no credit card required to start prototyping.

Data Sources

Place (POI) results are conflated from OpenStreetMap and Overture Maps Places. Overture Places is itself an open, multi-provider dataset; the individual contributing providers and their licenses are listed below.

ComponentSourceLicense
POI data (base)OpenStreetMapODbL 1.0
POI data (coverage + brand/chain)Overture Maps Places — aggregated from the providers belowPer-provider
↳ via OvertureMetaCDLA Permissive 2.0
↳ via OvertureMicrosoftCDLA Permissive 2.0
↳ via OvertureFoursquareApache 2.0
↳ via OverturePinMeTo, RenderSEO, DAC, BrightQuery, KrickCDLA Permissive 2.0
↳ via OvertureAllThePlacesCC0 1.0 (public domain)
Administrative boundary geometry (/boundaries, /boundaries/reverse)Overture Maps DivisionsOverture Maps Foundation (includes ODbL-derived components)
TimezoneIANA timezone databasePublic domain

All of the above licenses permit commercial use and redistribution; none are share-alike except OpenStreetMap's ODbL.

Attribution

Results returned by this API are derived/computational works ("Produced Works"), so you are not required to embed license text in each response. You are asked to credit the underlying data wherever results are displayed to end users:

© OpenStreetMap contributors · Includes data © Overture Maps Foundation (overturemaps.org); place data sourced from Meta, Microsoft, Foursquare and others. Foursquare-sourced records: Copyright 2024 Foursquare Labs, Inc. Administrative boundary geometry (/boundaries) is © Overture Maps Foundation (Divisions theme).

This is a summary of the data licenses, not legal advice.