# Local Business Leads Finder: OpenStreetMap Open Data (`jtpalms/osm-business-finder`) Actor

Find local businesses by category and city from OpenStreetMap open data: name, address, phone, website, opening hours, brand, coordinates and OSM link. Open data for local business leads and directories, licensed under the ODbL. Flags businesses with no website. USD 0.50 per 1,000 businesses.

- **URL**: https://apify.com/jtpalms/osm-business-finder.md
- **Developed by:** [JT Palms](https://apify.com/jtpalms) (community)
- **Categories:** Lead generation, Travel, Developer tools
- **Stats:** 2 total users, 1 monthly users, 83.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 business founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Local Business Finder: OpenStreetMap data, no Google Maps scraping

Local business lists from OpenStreetMap: an open-data alternative to Google Maps scrapers, licensed under the ODbL.

Pick a category (dentists, cafes, plumbers, gyms and 26 more) or any OpenStreetMap tag, and a city, a bounding box or a point and radius. You get one row per business with its name, address, phone number, website, opening hours, brand, coordinates and a link to the object on OpenStreetMap, plus a flag for businesses that have **no website**.

**USD 0.50 per 1,000 businesses.** Filtered-out rows, failed queries and malformed input are free.

### What people use it for

- **No-website prospects for web design and local SEO agencies.** "Dentists in Brampton with no website" is a ready prospect list, with the business phone number where OpenStreetMap has one. Turn on **Only businesses without a website** and rerun monthly per city.
- **Competitor counts by city.** Count cafes, gyms or pharmacies per city or per neighbourhood box, and compare places.
- **Chain vs independent.** Chains and franchises carry a `brand` tag (Tim Hortons, Starbucks, Shoppers Drug Mart). Count them, or turn on **Exclude chains** to keep independents only.
- **Store-locator and map seeding.** Coordinates, addresses and opening hours under an open licence you can redistribute (with attribution and share-alike, see below).
- **Teams that cannot use Google Maps data.** Google's terms restrict copying and storing Maps content; OpenStreetMap data is open under the ODbL.

### Sample output

Real rows from the default input (dentists in Brampton, Ontario), fetched 2026-10-06:

| Name | Address | Phone | Website | Has website | OSM link |
|---|---|---|---|---|---|
| Main Street Dental | 9, 320 Main Street North, Brampton, ON L6V 4A3 | +19054569200 | https://mainstreetdentaloffice.com | yes | node/887581928 |
| The Tooth Corner Dental Office | 213, 164 Sandalwood Parkway East, Brampton, ON L6Z 3S4 | +19058404888 | https://toothcorner.com | yes | node/1870128251 |
| iSmile Dental Centre | 4, 23 Conservation Drive, Brampton, L6Z 4R3 | +19058469655 | https://ismiledentalcentre.com | yes | node/1918832248 |
| Dawson Dental | | | | no | node/703059174 |
| Parkplace Junior Dentistry 4 Kids | 107 Queen Street East, Brampton | +19058666606 | | no | node/814375244 |

Coverage of those 100 rows: phone 44% (all in E.164 form), website 34%, opening hours 11%, full street address 17%; 17 of the 66 businesses without a website have a phone number. Every run writes its own coverage numbers to the `SUMMARY` record, so you see what you got before you use it.

One full row:

```json
{
  "name": "Smile Solutions Orthodontists Drs. Eckler, Black & Leung",
  "category": "dentist",
  "osmTags": { "amenity": "dentist", "healthcare": "dentist" },
  "brand": null,
  "brandWikidata": null,
  "operator": null,
  "address": "Unit # 135, 150 Great Lakes Drive, Brampton, ON L6R 2K7",
  "street": "Great Lakes Drive",
  "houseNumber": "150",
  "unit": "Unit # 135",
  "city": "Brampton",
  "postcode": "L6R 2K7",
  "province": "ON",
  "country": null,
  "latitude": 43.7295524,
  "longitude": -79.7644618,
  "website": "https://www.smilenow.com",
  "hasWebsite": true,
  "phone": "+19057898888",
  "phones": null,
  "phoneRaw": null,
  "openingHours": "Mo-Th 08:00-17:00, Fr 08:00- 14:00",
  "wheelchair": null,
  "description": "Our team consists of 5 Certified Orthodontic Specialists and a team of certified workers ...",
  "osmType": "node",
  "osmId": 8741750713,
  "osmUrl": "https://www.openstreetmap.org/node/8741750713",
  "lastEditedAt": "2026-09-03T05:23:14Z",
  "searchArea": "bounding box of Brampton, Peel Region, Ontario, Canada: 43.6018018,-79.8888473,43.8480959,-79.6302939 (the area query failed; may include neighbouring places)",
  "attribution": "© OpenStreetMap contributors, ODbL 1.0 (https://www.openstreetmap.org/copyright)",
  "fetchedAt": "2026-10-06T14:19:01.968Z"
}
```

In this run the public server was busy for the boundary query, so the actor searched the place's bounding box and `searchArea` says so (one of the 100 rows is in Mississauga).

### Input

| Field | What it does |
|---|---|
| **Category** | One of the 30 presets below. Leave empty to search by custom tags only. |
| **Custom OpenStreetMap tags** | Extra tags as `key=value`, for example `shop=bakery` or `craft=carpenter`; `key=*` means any value. ORed with the category. Tag list: [OSM Map Features](https://wiki.openstreetmap.org/wiki/Map_features). |
| **Location** | A city, region or neighbourhood, for example `Brampton, Ontario`. The search covers the place's boundary in OpenStreetMap. |
| **Bounding box** | Optional `south,west,north,east`, for example `43.60,-79.89,43.85,-79.63`. Used instead of the location. |
| **Latitude, Longitude, Radius (km)** | Optional circle search (0.1 to 50 km, default 5). Used instead of the location. |
| **Only businesses without a website** / **with a website** | Website filters (one or the other). |
| **Exclude chains** | Drops businesses with a `brand` tag. |
| **Maximum businesses** | Cap per run (default 1,000; the form starts at 100). |
| **Overpass server** (advanced) | The Overpass API endpoint. Default `https://overpass-api.de/api/interpreter`; you can use your own server. |
| **Nominatim server** (advanced) | The place-lookup endpoint. Default is the public OpenStreetMap Nominatim server. |

#### Categories

Each preset is a set of OpenStreetMap tags; a business matching any of them is found.

| Preset | OSM tags |
|---|---|
| dentist | amenity=dentist, healthcare=dentist |
| doctor | amenity=doctors, healthcare=doctor, amenity=clinic, healthcare=clinic |
| pharmacy | amenity=pharmacy, healthcare=pharmacy |
| veterinary | amenity=veterinary |
| restaurant | amenity=restaurant |
| cafe | amenity=cafe |
| bar | amenity=bar, amenity=pub |
| bakery | shop=bakery |
| hairdresser | shop=hairdresser |
| beauty | shop=beauty |
| gym | leisure=fitness_centre |
| hotel | tourism=hotel, tourism=motel |
| car_repair | shop=car_repair |
| car_dealer | shop=car |
| plumber | craft=plumber |
| electrician | craft=electrician |
| hvac | craft=hvac |
| roofer | craft=roofer |
| lawyer | office=lawyer |
| accountant | office=accountant, office=tax_advisor |
| insurance | office=insurance |
| school | amenity=school |
| childcare | amenity=childcare, amenity=kindergarten |
| supermarket | shop=supermarket |
| convenience | shop=convenience |
| clothes | shop=clothes |
| hardware | shop=hardware, shop=doityourself |
| florist | shop=florist |
| pet_store | shop=pet |
| bicycle_shop | shop=bicycle |

Fast food is `amenity=fast_food` and barbers are usually `shop=hairdresser`; add custom tags for anything else. Real-estate and property tags (estate agents, buildings, land use, rentals) are not supported.

### Output fields

| Field | What it is |
|---|---|
| `name` | The business name from OpenStreetMap. Objects without a name are left out. |
| `category` | The preset name, or the custom tag that matched (for example `shop=bakery`). |
| `osmTags` | Only the category tags of this object, for example `{"amenity":"dentist","healthcare":"dentist"}`. |
| `brand`, `brandWikidata` | Chain or franchise brand and its Wikidata id, when tagged. |
| `operator` | The operating company, only when the object also has a brand or an operator Wikidata id (so it is a company, not a person). |
| `address`, `street`, `houseNumber`, `unit`, `city`, `postcode`, `province`, `country` | From the `addr:*` tags. `province` holds `addr:province` or `addr:state`. `address` is one line built from the parts. |
| `latitude`, `longitude` | The point, or the centre of a building or area outline. |
| `website`, `hasWebsite` | Website with `https://` added when missing and the host lowercased; `hasWebsite` is false when there is none. |
| `phone` | The business's phone number from the OSM tag `phone`, or `contact:phone` when there is no `phone` tag. In E.164 form (for example `+19054569200`) when it parses cleanly with the country of the address (`addr:country`) or of the searched place; otherwise exactly as written in OSM. `null` when OSM has none. |
| `phones` | All numbers, when the tag lists several separated by `;` (each in E.164 form where it parses). `phone` is the first of them. `null` when there is only one number or none. |
| `phoneRaw` | The phone tag exactly as written in OSM, only when `phone` could not be put in E.164 form (for example `905 555 0100 ext. 3`, or a number without a country code in a bounding-box search); otherwise `null`. |
| `openingHours` | The raw OSM `opening_hours` value. |
| `wheelchair`, `description` | As tagged. E-mail addresses and phone numbers written inside descriptions are replaced with `[email removed]` and `[phone removed]`. |
| `osmType`, `osmId`, `osmUrl`, `lastEditedAt` | The OpenStreetMap object, a link to it, and when it was last edited. |
| `searchArea`, `attribution`, `fetchedAt` | What was searched, the licence credit, and when. |

The `SUMMARY` record in the run's key-value store has the search area, the requests made, the number of rows output and billed, and coverage: how many rows have a website, a phone number (and how many of those are in E.164 form), opening hours, a street address and a brand.

The field `hasPhoneTag` (output until 2026-10-06) has been removed; use `phone` instead (`null` means OSM has no phone number for the business).

### Pricing

| What | Price |
|---|---|
| Business found (after your filters) | USD 0.0005 (USD 0.50 per 1,000) |
| Run start (Apify's standard start event) | USD 0.00005 per run |
| Filtered-out rows, failed queries, malformed input | Free |

Example: a monthly job for "dentists in 5 cities, no website" that finds 300 businesses costs 300 x USD 0.0005 = USD 0.15, plus 5 x USD 0.00005 for the starts. The default input (100 dentists) costs USD 0.05.

Set a maximum cost per run in the run options and the actor stops cleanly when it is reached. Unless **Only businesses with a website** is on, it also asks the server for no more businesses than that budget pays for.

### Scheduling

New businesses appear as people edit OpenStreetMap. Save your input as a task and schedule it monthly per city, then send the results to Google Sheets, Slack or a webhook with Apify integrations. A place name is looked up once and remembered for 30 days, so scheduled runs do not query the place-lookup service again.

### What this does not do (honest limits)

- **OpenStreetMap coverage is thinner than Google Maps and varies by area.** Volunteers map businesses; some cities are very complete, others have gaps. Expect fewer businesses and fewer websites than Google shows. In the Brampton dentist sample, 34% of rows had a website and 11% had opening hours. A missing website in OSM does not always mean the business has none, so check before you pitch.
- **Business phone numbers, but no e-mail addresses.** The phone number a business has published in OpenStreetMap (`phone` or `contact:phone`) is output. E-mail addresses are never output, and neither are fax numbers or the mobile tags (`mobile`, `contact:mobile`). Phone numbers and e-mail addresses written inside the free-text `description` are removed and replaced with `[phone removed]` and `[email removed]`. Not every business has a phone number in OSM (44% of the Brampton dentist sample did), and a number is only as current as the last edit of that object, so check `lastEditedAt`.
- **Phone numbers are not all in E.164 form.** A bounding-box or circle search makes no place lookup, so the actor knows no country for it: a number written without a country code (for example `905-555-0100`) stays as written, with `phoneRaw`, unless the business address has `addr:country`. Numbers with an extension or with words in the tag also stay as written.
- **No reviews, ratings, photos or popularity.** OpenStreetMap does not hold them.
- **Objects without a name are skipped**, and so are businesses tagged in ways the category does not cover. Use custom tags for other tags.
- **Place boundaries:** a place name searches its boundary in OSM. If the public server cannot answer the boundary query, the actor uses the place's bounding box instead, which can include businesses just outside the place; `searchArea` then says so.
- **The public Overpass servers are shared and often busy.** The actor waits 10 s and retries (with the place's bounding box when the boundary query failed), waits 30 s and tries the public [Private.coffee](https://private.coffee) community instance once, then waits 60 s and asks the main server one last time. If all are busy it ends with a clear error and charges nothing. Large areas (a whole country) may be too heavy for the public servers; use a smaller area or your own Overpass server.
- You can fix gaps yourself by editing OpenStreetMap at https://www.openstreetmap.org; your edits appear in later runs.

### Data sources, licence and attribution

- **Data:** [OpenStreetMap](https://www.openstreetmap.org), read through the [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API): the main public instance at overpass-api.de (run by FOSSGIS), and as a fallback the [Private.coffee](https://private.coffee) community instance, whose published usage policy reads "Feel free to use our service in any project, there is no rate limit in place." and asks to be told in advance about large-scale use. The actor sends at most one request there per run, and only when the main server is busy. Place names are looked up with [Nominatim](https://nominatim.org).
- **Licence:** © OpenStreetMap contributors. The data is available under the [Open Database License (ODbL) 1.0](https://www.openstreetmap.org/copyright). Every row carries the credit `© OpenStreetMap contributors, ODbL 1.0 (https://www.openstreetmap.org/copyright)`.
- **What the ODbL asks of you:** credit OpenStreetMap and its contributors wherever you use the data, and make clear it is available under the ODbL. If you publish a database you built from this data, you must share that database under the same licence (share-alike); using the data internally, or publishing works such as maps or reports made from it, follows the attribution rules on the [OSM copyright page](https://www.openstreetmap.org/copyright).
- **Fair use of the free servers:** the actor sends one Overpass query per run, one request at a time, and waits before retrying after a busy answer, identifies itself with the user agent `osm-business-finder/1.0 (+https://apify.com/JTPalms/osm-business-finder)`, and calls Nominatim at most once per place every 30 days, following the [Overpass usage guidelines](https://dev.overpass-api.de/overpass-doc/en/preface/commons.html) and the [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/). It uses no proxies.

### Related actors

- [Company Enrichment From Domain: Tech Stack, Socials, Hiring](https://apify.com/JTPalms/company-profile): Company enrichment from a list of website domains: company name, description, official social links, tech stack (CMS, ecommerce,...
- [Tech Stack Detector: Website Technology Lookup in Bulk](https://apify.com/JTPalms/tech-stack-detector): Website technology lookup in bulk: the CMS, ecommerce platform, analytics, frameworks, CDN, hosting, payment, email and marketing tools...
- [Bulk DMARC, SPF & DKIM Checker + Email Provider & SaaS Finder](https://apify.com/JTPalms/email-security-checker): Check thousands of domains for email security in one run: MX records and mail provider (Google Workspace, Microsoft 365...), SPF with...
- [Website Screenshot API: Full Page, Mobile & URL to PDF](https://apify.com/JTPalms/website-screenshot): Screenshot a list of URLs as PNG, JPEG, WebP or PDF: full page, first screen, one element, or an exact clip rectangle, on desktop,...

### FAQ

**Why fewer results than Google Maps?** OpenStreetMap is mapped by volunteers; coverage differs by city and category. The `SUMMARY` coverage numbers show what you got.

**Is this a Google Maps scraper?** No. It never touches Google. It reads open OpenStreetMap data through its public APIs.

**Can I resell the data?** The ODbL allows commercial use with attribution; a database you publish from it must stay under the ODbL. Read the licence, and ask a lawyer if you are unsure.

**Something missing or wrong?** Open an issue with your input.

# Actor input Schema

## `category` (type: `string`):

Type of business. Each category maps to OpenStreetMap tags (listed in the README), for example Dentists = amenity=dentist or healthcare=dentist. Leave empty to search only by custom tags.

## `customTags` (type: `array`):

Extra tags in key=value form, for example shop=bakery or craft=carpenter. Use key=\* for any value. They are ORed with the category (a business matching any of them is found). Tag list: https://wiki.openstreetmap.org/wiki/Map_features

## `location` (type: `string`):

City, region or neighbourhood, for example Brampton, Ontario. It is looked up once on OpenStreetMap Nominatim and remembered for 30 days, so reruns do not look it up again. The search covers the place boundary. Not used when a bounding box or latitude/longitude is given.

## `boundingBox` (type: `string`):

Search a rectangle instead of a place: south,west,north,east in decimal degrees, for example 43.60,-79.89,43.85,-79.63. No place lookup is made.

## `latitude` (type: `number`):

Centre of a circle search in decimal degrees, for example 43.6858. Use with longitude and radius. No place lookup is made.

## `longitude` (type: `number`):

Centre of a circle search in decimal degrees, for example -79.7599.

## `radiusKm` (type: `number`):

Radius of the circle search, 0.1 to 50 km. Used only with latitude and longitude.

## `onlyWithoutWebsite` (type: `boolean`):

Keep only businesses that have no website in OpenStreetMap: a prospect list for web design and local SEO agencies. Note that OpenStreetMap often lacks websites that do exist.

## `onlyWithWebsite` (type: `boolean`):

Keep only businesses that have a website in OpenStreetMap. Cannot be combined with the option above.

## `excludeChains` (type: `boolean`):

Drop businesses that carry a brand tag in OpenStreetMap (chains and franchises such as Tim Hortons or Starbucks), keeping independents.

## `maxResults` (type: `integer`):

Stop after this many businesses. Each business is billed once.

## `overpassUrl` (type: `string`):

OpenStreetMap Overpass API endpoint. The default is the main public server (overpass-api.de). If it is busy the actor waits and retries, then tries the public Private.coffee community instance (overpass.private.coffee) once, then the main server once more. You can enter your own Overpass server here.

## `nominatimUrl` (type: `string`):

Place lookup endpoint (Nominatim search). The default is the public OpenStreetMap server, used at most once per place every 30 days. Change it only if that server asks apps to switch, or to use your own.

## Actor input object example

```json
{
  "category": "dentist",
  "location": "Brampton, Ontario",
  "radiusKm": 5,
  "onlyWithoutWebsite": false,
  "onlyWithWebsite": false,
  "excludeChains": false,
  "maxResults": 100,
  "overpassUrl": "https://overpass-api.de/api/interpreter",
  "nominatimUrl": "https://nominatim.openstreetmap.org/search"
}
```

# Actor output Schema

## `results` (type: `string`):

One row per business (JSON, CSV, Excel via the format parameter). Data © OpenStreetMap contributors, ODbL 1.0.

## `summary` (type: `string`):

Search area, requests made, rows output and billed, and coverage (share of rows with a website, a phone number, opening hours and a street address).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "category": "dentist",
    "location": "Brampton, Ontario",
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("jtpalms/osm-business-finder").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "category": "dentist",
    "location": "Brampton, Ontario",
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("jtpalms/osm-business-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "category": "dentist",
  "location": "Brampton, Ontario",
  "maxResults": 100
}' |
apify call jtpalms/osm-business-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jtpalms/osm-business-finder"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/iudHKc9fQD7tNd4Km/builds/5jhNQX2tI6AsUnrcv/openapi.json
