# Local Business Leads with Emails from OpenStreetMap (No Key) (`lyfe_tools/osm-business-email-leads`) Actor

Local business leads with emails: find businesses in any city by category from OpenStreetMap, with the role-based business address (info@, contact@, sales@) from their own website. Role-based business addresses only, GDPR-minded. A keyless Google Maps scraper alternative.

- **URL**: https://apify.com/lyfe_tools/osm-business-email-leads.md
- **Developed by:** [LYFE Offshore](https://apify.com/lyfe_tools) (community)
- **Categories:** Lead generation, Automation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 business with email 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 Leads with Emails from OpenStreetMap (No Key)

Local business leads by city and category, with business emails from each business's own website: the businesses come from [OpenStreetMap](https://www.openstreetmap.org), no API key needed.
A Google Maps scraper alternative that does not touch Google or LinkedIn.

**Role-based business addresses only, GDPR-minded.** It returns addresses such as `info@`, `contact@`, `sales@`,
`office@` or `reservations@`. Addresses that look like a person's (`jan@`, `j.devries@`, initials) are left out and never charged.

### One run, before and after

Input: `places: ["Haarlem, Netherlands"]`, `categories: ["dentist"]`, `maxResults: 10`.

| | A plain OpenStreetMap export | This Actor |
|---|---|---|
| Rows | 39 named dentists with a website or email tag, most without an email | 10 businesses, **all with a role address** |
| Email | mostly missing | 3 from OpenStreetMap tags, 7 read from the practice's own website |

Real output of that run (6 October 2026, 17 businesses checked, about two minutes):

| Business | Email | Found on | Phone | Address |
|---|---|---|---|---|
| Tandartspraktijk Koning | info@tandenkoning.nl | OpenStreetMap | +31 23 532 9002 | Lorentzkade 300, 2014CH Haarlem |
| Tandheelkundig Centrum Nederland | info@tcn.nl | website | +31 23 534 3178 | Spaarne 72, 2011CL Haarlem |
| Geurst Tandartsen | info@geursttandartsen.nl | website | +31 23 5262581 | Kraaienhorst 54-56, 2011NX Haarlem |
| De Vijfhoek tandartsen | info@tandartsenweb.nl | website | +31 23 5428336 | Sophiastraat 71, 2011VV Haarlem |
| Mandana Mondzorg | info@mandanamondzorg.nl | website | +31 23 5254222 | Schouwtjeslaan 23A, 2012KD Haarlem |

### How it works

1. Looks the place up in OpenStreetMap (Nominatim) and lists every named business of your categories inside it (Overpass API).
2. Uses the `email` / `contact:email` tag when the mapper filled it in.
3. Otherwise reads the business's own site: the home page, then the contact or about page. `mailto:` links, plain text,
   `[at]` spellings and Cloudflare-protected addresses are all picked up. Image names, placeholder addresses
   (`name@domain.com`) and tracking addresses are dropped, and the business's own domain ranks above webmail and web agencies.
4. Keeps **role addresses only**: the first word of the address must be a known role such as `info`, `contact`, `sales`,
   `office`, `hello`, `support`, `booking` or `reservations` (`info.haarlem@` counts). Everything else, including names,
   initials and `haarlem@`-style addresses, is left out. A business with only such addresses gets no `email`, a `note`, and no charge.
   Over all 39 Haarlem dentists: 29 with a role address, 9 left out because they only publish personal-looking addresses.
5. Returns the best address in `email` and up to four more in `otherEmails`.

It is polite by design: `robots.txt` is respected, at most a few pages per site, a small pause between requests,
a few sites at a time, and a user agent that says who we are.

### Input example

The prefilled run above:

```json
{
  "places": ["Haarlem, Netherlands"],
  "categories": ["dentist"],
  "maxResults": 10,
  "onlyWithEmail": true
}
```

| Field | |
|---|---|
| `places` | Cities or districts. Add the country: `"Haarlem, Netherlands"` |
| `categories` | `dentist`, `restaurant`, `hairdresser`, `lawyer`, `plumber`, `hotel`, `bakery`, `gym`, `accountant`, `estate_agent`, `car_repair` and more, or any OSM tag such as `craft=roofer` |
| `maxResults` | Stop after this many businesses with an email, across all places (default 50) |
| `onlyWithEmail` | Off = also return businesses with a website or phone but no email (free; they count toward `maxResults`) |
| `concurrency` | Websites read in parallel (default 5, maximum 10) |

### Output example

A real row from the same Haarlem search (run locally with this Actor's code on 5 October 2026):

```json
{
  "name": "Geurst Tandartsen",
  "category": "amenity=dentist",
  "address": "Kraaienhorst 54-56, 2011NX Haarlem",
  "city": "Haarlem",
  "phone": "+31 23 5262581",
  "website": "https://www.geursttandartsen.nl",
  "lat": 52.3844788,
  "lon": 4.6336066,
  "osmUrl": "https://www.openstreetmap.org/node/2710520821",
  "email": "info@geursttandartsen.nl",
  "otherEmails": [],
  "emailSource": "website",
  "searchPlace": "Haarlem, Netherlands"
}
```

| Field | Meaning |
|---|---|
| `name` | Business name from OpenStreetMap |
| `category` | The OSM tag it matched, e.g. `amenity=dentist` |
| `address` / `city` | Street, number, postcode and city as mapped |
| `phone` | Phone from OpenStreetMap, as mapped |
| `website` | Website from OpenStreetMap |
| `lat` / `lon` | Coordinates |
| `osmUrl` | Link to the object on openstreetmap.org |
| `email` | The best role address found (`null` if none) |
| `otherEmails` | Up to four more role addresses found |
| `emailSource` | `openstreetmap` (tag) or `website` (read from the site) |
| `searchPlace` | Which of your `places` it came from |
| `note` | Only when reading the site had a problem, e.g. `robots.txt disallows some pages` or `website unreachable` |
| `attribution` | `© OpenStreetMap contributors, ODbL 1.0`: keep it with the data if you share or publish it |

A run summary (leads, with email, businesses checked) is stored under the `SUMMARY` key.

### Price

Price per 1,000 by your Apify plan (Apify applies the right column automatically):

| | Free | Starter | Scale | Business and up |
|---|---|---|---|---|
| Business with email found, per 1,000 | $3.50 | $3.50 | $3.00 | $2.50 |

No start fee. The figures below use the Free-plan price; on a paid plan the same run costs less.

| Event | Price |
|---|---|
| Business with email found | **$0.0035** |

- 1,000 businesses with an email ≈ $3.50.
- The Haarlem run above: 10 businesses with an email = $0.035.
- Free: businesses without an email (returned only with `onlyWithEmail` off).

Pay per event, platform usage included. A run can be capped with the usual maximum-cost setting; the Actor
stops at the cap instead of doing work it cannot be paid for.

### Use it from code or an AI agent

```bash
curl -X POST "https://api.apify.com/v2/acts/lyfe_tools~osm-business-email-leads/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"places": ["Haarlem, Netherlands"], "categories": ["dentist"], "maxResults": 10}'
```

It returns the rows as a JSON array. AI agents (Claude, Cursor and other MCP clients) can call the
Actor through the Apify MCP server at https://mcp.apify.com: the agent sends `places`, `categories`
and `maxResults` and reads `name`, `email`, `phone`, `website` and `address`.

### FAQ

**How do I get a list of local businesses with email addresses in a city?**
Give the city in `places` (with the country) and a category such as `restaurant` or `plumber` in `categories`.

**Is this a Google Maps scraper?**
No. It does not use Google at all. Businesses come from OpenStreetMap and emails from the businesses' own websites or OSM tags.

**Do I need an API key?**
No Google, OpenStreetMap or other key. Only your Apify account.

**Which categories can I search?**
The friendly names listed under Input, or any OpenStreetMap tag in `key=value` form, such as `craft=roofer` or `shop=florist`.

**Why are some businesses missing?**
Only businesses mapped in OpenStreetMap with a name, and with a website or email tag, can become a lead. Sites with only a contact form are skipped.

**Am I charged for businesses without an email?**
No. Only rows with a role address are charged.

**Why do I get info@ but not the owner's address?**
On purpose: the Actor returns role-based business addresses only (GDPR-minded). Addresses that look like a person's are left out.

Next step: verify the addresses with
[Catch-All Email Verifier](https://apify.com/lyfe_tools/catchall-email-verifier).

### Honest limits

- **Coverage is what OpenStreetMap has.** Dense in Europe, thinner elsewhere. Only businesses with a website or
  email tag can be turned into a lead, which is roughly a third of them. Big cities are better covered than villages.
- **Not every site publishes an email.** Many use a contact form only; those businesses are skipped, not guessed.
- **The Overpass servers are free community servers** and are sometimes overloaded. The Actor tries three of them and
  retries; if all fail it stops with a clear message instead of returning an empty dataset. Run again a few minutes later.
  The same goes for a search that finds no business with an email: the run fails with a message and costs nothing.
- Addresses are taken as published, not checked for delivery.
- **Role addresses only, by design.** Personal-looking addresses are never returned or charged, to keep personal data
  out of the output. A role address can still reach one person at a very small business, so check the rules for
  cold outreach in the recipient's country; you are responsible for how you use the addresses.
- OpenStreetMap data is © OpenStreetMap contributors, available under the [ODbL](https://opendatacommons.org/licenses/odbl/).
  Keep that attribution if you publish the data.

Questions or problems: tools@lyfeoffshore.com

# Actor input Schema

## `places` (type: `array`):

Cities, towns or districts, as you would type them in a map search. Adding the country avoids look-alikes, e.g. "Haarlem, Netherlands".

## `categories` (type: `array`):

Friendly names (dentist, restaurant, hairdresser, lawyer, plumber, hotel, bakery, gym, accountant, estate_agent, car_repair ...) or any OpenStreetMap tag such as amenity=dentist or craft=roofer.

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

Stop after this many businesses with an email address. You are charged per business returned with an email. With onlyWithEmail off, free rows without an email also count toward this limit.

## `onlyWithEmail` (type: `boolean`):

On: return only businesses where an email address was found. Off: also return businesses with a website or phone but no email (these are free).

## `concurrency` (type: `integer`):

How many business websites are read at the same time. Each site is fetched gently (a few pages, robots.txt respected).

## Actor input object example

```json
{
  "places": [
    "Haarlem, Netherlands"
  ],
  "categories": [
    "dentist"
  ],
  "maxResults": 50,
  "onlyWithEmail": true,
  "concurrency": 5
}
```

# Actor output Schema

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

One row per business.

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

How many businesses were checked and how many had an email.

# 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 = {
    "places": [
        "Haarlem, Netherlands"
    ],
    "categories": [
        "dentist"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lyfe_tools/osm-business-email-leads").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 = {
    "places": ["Haarlem, Netherlands"],
    "categories": ["dentist"],
}

# Run the Actor and wait for it to finish
run = client.actor("lyfe_tools/osm-business-email-leads").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 '{
  "places": [
    "Haarlem, Netherlands"
  ],
  "categories": [
    "dentist"
  ]
}' |
apify call lyfe_tools/osm-business-email-leads --silent --output-dataset

```

## MCP server setup

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

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/ay6SgNwHRcctAOhev/builds/KTefoJuBbTYtb05ru/openapi.json
