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_count | What it means | Realistic precision |
|---|---|---|
| 3 | Consensus across all three feeds | ~75% — this is the safe bucket for analytics |
| 2 | Cross-validated by two of the three | ~45% — solid for general use |
| 1 | Present 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_label | What it means |
|---|---|
register_verified | Backed by an authoritative source: a government register (gov:* in sources), the TravelTime transport network, or a category whose register is the consensus signal. |
cross_validated | At least two datasets independently agree this place exists (independent_source_count >= 2 — echoes don't count). |
unverified | A 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 snapshotname— best-available consolidated namebrand— the canonical chain name when we know the POI belongs to one (Tesco,Morrisons),nullwhen it doesn't. See Filtering by brandcategory— the POI's primary category (see the categories endpoint)categories— every category the POI belongs tocategories_detail— the fine-grained detail types beneath those categories (e.g.sushi_restaurantunderrestaurant)category_groups— the coarse group labels (e.g.food_and_drink,education)source_count— 0-3 as described above (public-transport POIs report an explicit0: TravelTime's network is the authority, not a counted dataset)independent_source_count— the echo-aware count the quality floor keys onconfidence_label—register_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
- Public transport — how we build the transport graph (not from POIs).
- Freshness and versions — how often POIs refresh and how to detect a change.
- API reference — full parameter and response shapes for the POI endpoints.