Build a local business directory
A common use of the Overture Maps API is to build a local business discovery or directory product: find the businesses in a place, sort them by category, and enrich them with contact details so users (or your own pipeline) can reach out or verify them.
This guide walks through that end to end. The running example builds a directory of bakeries in France, but every step works for any country and category — swap country and categories for your own.
What you'll build
For a chosen country and category, you'll pull every matching business, keep only the ones you can actually contact, and get back their websites, phones, socials and location — ready to load into your own database.
Step 1 — Find the businesses
Start with a country code (ISO 3166-1 alpha-2) and a category. Ask for a single result first to see the shape of the data:
curl -H "x-api-key: DEMO-API-KEY" -X GET -G 'https://api.overturemapsapi.com/places' \
-d 'country=FR' -d 'categories=bakery' -d 'limit=1'
categoriesmatches the exact category. If you want a whole family of businesses (say every kind of food-and-drink venue), usetaxonomyinstead — it's hierarchy-aware, sotaxonomy=food_and_drinkmatches bakeries, cafés, restaurants and bars in one call.min_confidence(default0.5) trades precision for recall. Lower it toward0.4to catch more of the long tail, raise it toward0.8if you only want high-confidence records. It's worth tuning against a sample for your category.
Step 2 — You already get contact details
Contact details come back by default — you do not need any special parameter:
"names": { "primary": "Boulangerie du Coin" },
"categories": { "primary": "bakery" },
"websites": ["https://boulangerie-du-coin.fr"],
"phones": ["+33123456789"],
"emails": [],
"socials": ["https://www.facebook.com/boulangerieducoin"],
"addresses": [{ "freeform": "12 Rue de la Paix", "locality": "Paris", "postcode": "75002", "country": "FR" }]
enrichment_fields for websites or phonesA common early mistake is adding website or phone to enrichment_fields. That parameter is only for the Wikidata brand layer (Step 5). The websites, phones, emails and socials arrays are always in the response when Overture has them.
Coverage varies by country and category. In well-mapped markets, websites and phones are populated for the large majority of businesses; emails and socials are thinner. In smaller territories the total number of places is lower, so expect a shorter long tail.
Step 3 — Keep only businesses you can verify
If your product verifies or enriches businesses via their own website (or a social page), filter to the records that actually have something to work with, using has_contact. It's comma-separated and matched with OR:
# bakeries with a website OR a social link
curl -H "x-api-key: DEMO-API-KEY" -X GET -G 'https://api.overturemapsapi.com/places' \
-d 'country=FR' -d 'categories=bakery' -d 'has_contact=website,social'
website,social is a good default: it catches businesses that use a Facebook or Instagram page instead of a dedicated site, which in many markets is most of the ones without a website. Allowed values are website, phone, email, social.
The Pagination-Count response header reflects the filtered total, so you can see how many businesses match before you pull them all.
Step 4 — Pull the whole country
Results are paginated at up to 100 per page (limit), with page 0-indexed. The Pagination-Count header tells you the total, so you loop until you've collected them all:
const base = "https://api.overturemapsapi.com/places";
const params = new URLSearchParams({
country: "FR",
categories: "bakery",
has_contact: "website,social",
min_confidence: "0.50",
limit: "100",
format: "geojson",
});
let page = 0;
const all = [];
while (true) {
const res = await fetch(`${base}?${params}&page=${page}`, {
headers: { "x-api-key": process.env.OVERTURE_API_KEY },
});
const batch = await res.json();
all.push(...batch.features);
const total = Number(res.headers.get("Pagination-Count")); // filtered total
if (all.length >= total) break;
page++;
}
console.log(`Collected ${all.length} bakeries with a website or social`);
For a local directory (a town or neighbourhood) rather than a whole country, swap country for lat / lng / radius and the same pagination applies.
Step 5 — Add brand details for chains (optional)
For chain or franchise locations, add enrichment_fields=brand to get the brand's logo, official website, industry and parent company, sourced from Wikidata:
"ext_brand": {
"label": "Paul",
"logo_url": "http://commons.wikimedia.org/wiki/Special:FilePath/Paul%20logo.svg",
"website": "https://www.paul.fr/",
"industry": "bakery"
}
This is handy for de-duplicating or badging chain locations, and for filling in an official website where the individual location doesn't have its own.
Tips
- Confidence: sample your category at a couple of
min_confidencelevels before committing — the right threshold differs by category and country. - Category vs taxonomy: use
categoriesfor a specific type,taxonomywhen you want a whole branch of the hierarchy in one query. - Coverage: it varies by market. If you need denser contact data in a specific country, get in touch — we're continually improving coverage.
- Pagination: always drive your loop off the
Pagination-Countheader rather than assuming a fixed number of pages. See Pagination for details.
That's the whole loop: choose a country and category, filter to the businesses you can reach, page through the country, and load the results into your own product.