Data APIdocs

Points of interest

A point of interest in Data API is a place — a café, a school, a pharmacy, a station — with a name, a location, a category, and a confidence signal. We don't operate any of these places; we consolidate three independent open-data sources into one layer that's more trustworthy than any of them alone.

This page explains where the data comes from, what cross-validation actually means, how to read the source-count buckets, and what we deliberately don't include.

Three sources, one layer

We ingest from three independent providers, on a country-by-country basis:

  • Foursquare Open Places — a freely-redistributable dataset built from Foursquare's global places graph. Strong on commercial venues (restaurants, retail, services). Updated on Foursquare's cadence; we pull a fresh snapshot per rebuild.
  • OpenStreetMap — the volunteer-edited geographic database. Strong on community-knowledge categories (schools, parks, places of worship, transit) and on countries with active mapping communities. Coverage varies by region.
  • Overture Maps — the Linux Foundation's federated map data project. Currently a blend that includes Foursquare and OSM upstream, with additional cleanup. We treat it as a third opinion rather than as a fully independent signal.

For each rebuild of a country's dataset, we pull the raw rows from all three sources, then attempt to match them against each other. A match means "these rows refer to the same real-world place." Our matcher uses name similarity (token containment + edit distance), a location proximity gate (around 15 m for unnamed proximity matches, more permissive for named ones), and category compatibility checks. When two or three sources agree, we collapse them into a single POI row and record which sources contributed.

The source-count signal

Every POI carries a source_count of 0-3 — the number of source datasets that contributed to it — and an independent_source_count, which discounts "echoes": an Overture row that is itself Foursquare- or OSM-derived is the same dataset twice, not corroboration. The independent count is the canonical confidence signal in the API — the default quality floor and the min_sources filter key on it.

source_countWhat it meansRealistic precision
3Consensus across all three feeds~75% — this is the safe bucket for analytics
2Cross-validated by two of the three~45% — solid for general use
1Present in only one source~30–40% — noisy, useful for recall

"Precision" here is measured against a ground-truth sample we maintain: places that are actually there, named correctly, in the right category, at the right location. The numbers are real-world, not theoretical — single-source POIs include duplicates, drift, and abandoned listings, which is why we tag them rather than drop them.

POI-returning endpoints accept a min_sources filter (1, 2, or 3) that lets you choose the trade-off explicitly. The default is authority-aware — it lets in single-source rows when they come from sources we trust for a particular category (see below), and requires two sources otherwise.

The confidence label

If you want one field to read, read confidence_label. It summarises the structural evidence for each POI — it states what we did, not how sure we feel:

confidence_labelWhat it means
register_verifiedBacked by an authoritative source: a government register (gov:* in sources), the TravelTime transport network, or a category whose register is the consensus signal.
cross_validatedAt least two datasets independently agree this place exists (independent_source_count >= 2 — echoes don't count).
unverifiedA single dataset, or corroborated only by echoes. Useful for recall, don't build compliance work on it.

Responses also carry a legacy confidence float. It's a match-quality heuristic from the merge step, and it is not monotonic against whether a place is real — prefer confidence_label and independent_source_count. The float remains in responses for backward compatibility only.

Authority overlays

Some categories have a single source that's so much more trustworthy than the rest that we treat it as authoritative — meaning a POI carrying that source's listing is included even at source_count=1, and we suppress competing low-confidence rows. This avoids over-tagging in categories where the open-data feeds are noisy.

  • Healthcare in the UK — the Care Quality Commission (CQC) maintains a public register of every regulated provider. We use it as the primary source for clinics, GPs, dentists, and care homes; if CQC says a clinic exists at a postcode, that POI carries through even if Foursquare and OSM disagree. Other categories follow the same pattern where a comparable register exists.
  • Schools in the UK — Get Information About Schools (GIAS) is the Department for Education's authoritative list of state and private schools. We use it as the primary source for primary, secondary, and special schools.
  • Public transport stations — we don't try to merge POI sources for transport; the transport graph is built separately from a routing-grade source. See Public transport.

When you query a category with an authority overlay active, the API resolves it for you — you don't need to know which categories are overlay-backed. confidence_label reads register_verified when one is; read that, not sources, which carries a backing tag (gov:*, or traveltime for the transport graph) for only some of them.

What we deliberately don't do

A few common questions on what's not in the dataset:

  • No private listings. We don't import paywalled or licensed-only POI feeds, and we don't accept private submissions. Every POI in Data API traces back to a publicly redistributable source.
  • No phone numbers, opening hours, or reviews. The dataset is a places layer, not a business-information service. If you need rich business attributes, pair us with a commercial places API.
  • No real-time signals. A POI is "there" or not. We don't model "open now" or "busy right now"; that's outside the dataset's scope.
  • No address-level resolution. A POI carries a single latitude/longitude (a centroid, or a representative point for polygon-defined places like parks). Apartment-level granularity isn't there.
  • No drift detection. If a coffee shop closes and a barber opens at the same address, we see what our sources tell us at the next rebuild. A POI marked as "closed" in OSM but still present in Foursquare will appear with a 2-source signal until both agree.

What the API actually returns

Calling GET /v1/pois/near for a point returns a GeoJSON FeatureCollection. Each feature carries:

  • id — identifies this POI in this country's dataset; stable only within a snapshot
  • name — best-available consolidated name
  • brand — the canonical chain name when we know the POI belongs to one (Tesco, Morrisons), null when it doesn't. See Filtering by brand
  • category — the POI's primary category (see the categories endpoint)
  • categories — every category the POI belongs to
  • categories_detail — the fine-grained detail types beneath those categories (e.g. sushi_restaurant under restaurant)
  • category_groups — the coarse group labels (e.g. food_and_drink, education)
  • source_count — 0-3 as described above (public-transport POIs report an explicit 0: TravelTime's network is the authority, not a counted dataset)
  • independent_source_count — the echo-aware count the quality floor keys on
  • confidence_labelregister_verified / cross_validated / unverified (see The confidence label)
  • sources — array of which providers contributed (Foursquare, OSM, Overture, plus any authority overlays in effect)
  • date_refreshed — ISO 8601 UTC time any contributing source last touched the row (omitted, not null, when no source carries a timestamp). High-churn categories are demoted in ranked results as this ages; use it for your own staleness rules.
  • A point geometry — single lat/lng

You don't need to know how a POI was matched to use it. The matcher logic is hidden behind the API; the source_count and sources fields are how that work surfaces to you.

Fetching one POI

GET /v1/pois/{id} returns a single POI as a GeoJSON Feature — the same shape as one element of a list response's features array, not a wrapper around it. The id is the one carried on POIs you already fetched; there is no separate lookup by name or address.

POI_ID=$(curl -s -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/pois/search?country=GB&q=British+Museum&limit=1" \
  | jq -r '.features[0].properties.id')

curl -H "Authorization: Bearer $POI_API_KEY" \
  "$POI_BASE/v1/pois/$POI_ID?country=GB&include_sources=true"

include_sources=true adds properties.sources_detail, keyed by foursquare / osm / overture — what each source claimed before we merged them.

Ids are assigned when a country's bundle is built, so they are stable for the life of a snapshot but not across rebuilds. Watch last_refreshed on GET /v1/stats?country=<cc> and re-resolve stored ids when it moves — see Freshness and versions.

Filtering by category

The category parameter on the POI and H3 endpoints accepts a comma-separated list, and each entry can name any level of the three-level taxonomy: a group (e.g. food_and_drink), a category (e.g. restaurant), or a detail type (e.g. sushi_restaurant). Broader ids expand to every detail type beneath them and the results are unioned — so category=restaurant covers chinese_restaurant, italian_restaurant, and every other cuisine, while category=food_and_drink also pulls in cafés, bars, and bakeries. Unknown ids return a 422 with did-you-mean suggestions.

There are 16 groups:

food_and_drink, shopping, health_and_wellness, finance, business_and_services, lodging, automotive, government, culture, education, places_of_worship, entertainment_and_recreation, sports, transportation, geographical_areas, uncategorised.

For the full group → category → detail tree, with live counts per country, call GET /v1/categories?country=GB.

Filtering by brand

/v1/pois/near, /v1/pois/in-bbox, and /v1/pois/search accept a brand parameter that keeps only one chain. It matches the whole canonical name, case-insensitively — so brand=morrisons and brand=Morrisons are the same query, Tesco Express is a separate brand from Tesco, and unbranded POIs never match.

There is no brand-listing endpoint: read the brand property off POIs in a region to discover which names a country carries. Attribution comes from the sources (chiefly OSM's brand:wikidata tags), so coverage is best on large multiples and thinner on regional chains.

Counting POIs in a cell

GET/POST /v1/h3/pois returns a count per cell: the number of distinct POIs whose location falls in that cell and which pass the standard confidence filter (the same authority-aware bar the POI endpoints apply, tightened by min_sources if you send it). A POI is never counted twice in a cell, however many categories it carries.

The per-cell categories breakdown is multi-label, so its values can sum past count — a place that is both a cafe and a bakery appears under both labels. Treat count as the cell's POI total and the breakdown as a composition hint, not a partition.

GET/POST /v1/h3/pois/records returns the POIs themselves rather than a count, capped at per_cell_limit per cell. A cell holding more matching POIs than the cap carries truncated: true, so you can tell a short list from a complete one.

All of this holds for /v1/geohash/pois and /v1/geohash/pois/records, with rows keyed geohash.

Where next