# Local Business Leads — Phones & Emails by City (`gary_sells/local-business-leads`) Actor

Local businesses by category + city via OpenStreetMap (no Google API). Phones, websites, addresses; optional email enrich. PPE $0.004/business-found. Companions: gary\_sells/domain-contact-finder · gary\_sells/domain-emails-fast · gary\_sells/maps-business-contact-enricher.

- **URL**: https://apify.com/gary\_sells/local-business-leads.md
- **Developed by:** [Industrial AI](https://apify.com/gary_sells) (community)
- **Categories:** Lead generation, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 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.

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 — Phones, Websites & Emails by City

### When to call this Actor

Use when a human or agent needs **local business leads by category + place** (phones / websites / optional emails):

- "Find plumbers in Austin with phone numbers"
- "List cafes near Brisbane CBD"
- "Get dentist leads in Prague with websites"
- "Local restaurant leads in Berlin with emails"

**Do not use** for: Google Maps unofficial scrapes (this Actor uses **OpenStreetMap** public data — no Google API key, no Maps CAPTCHA), LinkedIn / Sales Nav, inbox scraping, login walls, or people-search APIs.

**Prefer Domain Contact Finder / Domain Emails Fast** when you already have a domain list and only need site crawl enrichment.

### Why OSM (not Google Maps)

- Ships on **Cheerio + public HTTP** — no Playwright CAPTCHA farm
- **No Tim Google API key** required for v1
- Phones / websites / addresses often present on OSM tags
- Optional Cheerio email enrich from public business websites
- Anonymous Store–safe, no LinkedIn ToS landmines

### Agent quickstart

**Input (prefer):** `{ "category": "plumber", "location": "Austin, TX", "maxResults": 25 }`\
**Optional:** `"enrichEmails": true` to crawl websites for public emails\
**Output:** one dataset item per business → `name`, `phone`, `website`, `emails`, `address`, `lat`, `lon`, `osmId`

**Example prompts → input**

| User says | Pass |
| --- | --- |
| Find cafes in Austin | `category: "cafe", location: "Austin, TX"` |
| Plumbers in Brisbane with emails | `category: "plumber", location: "Brisbane, Australia", enrichEmails: true` |
| 10 dentists near Prague | `category: "dentist", location: "Prague", maxResults: 10` |
| Custom OSM tag in a bbox | `category: "shop=bicycle", bbox: "50.0,14.3,50.1,14.5"` |

**Pricing (PPE):** `business-found` once per business pushed; `email-enriched` once per business that has ≥1 email (from OSM tags or website enrich). **Do not** charge `apify-default-dataset-item`.

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange)](https://apify.com)
[![PPE](https://img.shields.io/badge/Pricing-Pay%20per%20event-blue)](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event)

***

### What this Actor does

1. Resolves `category` to OSM tags (friendly names or raw `key=value`)
2. Geocodes `location` via public **Nominatim** (or uses `bbox`)
3. Queries **Overpass API** for matching nodes/ways in the area
4. Emits name, phone, website, address, coordinates from OSM tags
5. Optionally **Cheerio**-crawls business websites for public emails

**Stack:** Node.js + Crawlee CheerioCrawler + Apify SDK PPE. `usesStandbyMode: false`.

***

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `category` | `string` | `cafe` | Friendly name (`plumber`, `dentist`, …) or OSM filter (`amenity=restaurant`) |
| `location` | `string` | — | City/area for Nominatim geocode (required unless `bbox`) |
| `bbox` | `string` | — | Optional `south,west,north,east` — skips geocode |
| `radiusKm` | `integer` | `5` | Search radius around geocoded point (1–50) |
| `maxResults` | `integer` | `25` | Cap on businesses returned (1–500) |
| `enrichEmails` | `boolean` | `false` | Cheerio-crawl websites for public emails |
| `maxPagesPerWebsite` | `integer` | `3` | Paths per site when enriching |
| `requirePhoneOrWebsite` | `boolean` | `true` | Drop listings with neither phone nor website |
| `proxyConfiguration` | `object` | Proxy off | Used only for website email enrich |

#### Example input

```json
{
  "category": "cafe",
  "location": "Austin, TX",
  "radiusKm": 5,
  "maxResults": 10,
  "enrichEmails": false,
  "requirePhoneOrWebsite": true,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

***

### Output

One item per business:

```json
{
  "name": "Example Cafe",
  "category": "cafe",
  "phone": "+1-512-555-0100",
  "website": "https://example-cafe.com/",
  "emails": ["hello@example-cafe.com"],
  "address": "123 Main St, Austin, TX",
  "city": "Austin",
  "lat": 30.2672,
  "lon": -97.7431,
  "osmType": "node",
  "osmId": "123456",
  "status": "ok",
  "error": null
}
```

***

### Pricing (Pay-per-event)

Suggested Store pricing (configure in **Apify Console → Actor → Monetization** — Tim/Gary):

| Event | When charged | Suggested price |
| --- | --- | --- |
| **`business-found`** (primary) | Once per business pushed to the dataset | **$0.004** each (~$4.00 / 1,000) |
| **`email-enriched`** | Once per business that has ≥1 email (OSM tag or website crawl) | **$0.002** each (~$2.00 / 1,000) |

**Console checklist**

- Set the two PPE events above; **remove or zero** `apify-default-dataset-item`
- Keep **PPE only** — **PPE + usage pass-through OFF**
- **Limited** permissions
- **Standby OFF** (`usesStandbyMode: false`)

#### Cost example

- 1,000 cafes discovery-only → **~$4.00** on `business-found`
- Same run with ~40% email hit rate → +**~$0.80** on `email-enriched`

***

### For AI agents / MCP

- **Actor name:** `gary_sells/local-business-leads`
- **Prefer input fields:** `category` + `location`
- **PPE events:** `business-found`, `email-enriched`
- **Constraints:** public OSM + public HTML only — no Google Maps unofficial scrape, no LinkedIn, no login/CAPTCHA bypass
- Discoverable via Apify Store / MCP `search-actors` once published with PPE-only + Limited permissions + Standby off

### API & agent usage

```bash
curl "https://api.apify.com/v2/acts/USERNAME~local-business-leads/runs" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"category":"cafe","location":"Austin, TX","maxResults":10}'
```

Local PPE dry-run:

```bash
ACTOR_TEST_PAY_PER_EVENT=true apify run -p
```

Inspect `storage/datasets/charging_log/` for charged events (local default price $1/event).

***

### Legal & ethics disclaimer

This Actor collects **publicly available** OpenStreetMap data and (optionally) public website HTML. It does **not** call Google Places/Maps APIs, bypass logins/CAPTCHAs, or access private directories.

You are responsible for complying with applicable laws (including GDPR/CCPA where relevant), OSM usage policies, website terms, and anti-spam rules (e.g. CAN-SPAM). Use contact data only for lawful purposes. The authors provide no warranty and accept no liability for misuse.

Respect [Nominatim](https://operations.osmfoundation.org/policies/nominatim/) and [Overpass](https://wiki.openstreetmap.org/wiki/Overpass_API) usage policies (polite User-Agent, reasonable rate).

***

### How it works (technical)

- **Discovery:** Nominatim geocode → Overpass `[out:json]` for category tags in bbox
- **Enrich:** optional `CheerioCrawler` on `/`, `/contact`, `/about` paths
- **PPE:** `Actor.charge({ eventName: 'business-found' | 'email-enriched' })`; stops on `eventChargeLimitReached`
- **Permissions:** Designed for **limited** Actor permissions. No Standby mode.

***

### Local development

```bash
cd local-business-leads
npm install
npm test
npm run dry-run
```

Deploy (after `apify login`):

```bash
apify push --force --wait-for-finish
```

Then Tim/Gary set PPE prices and keep **Limited permissions** in Apify Console (do not auto-publish from CI).

***

### Changelog

- **1.0.0** — Initial Store-ready release: OSM local discovery, optional Cheerio email enrich, PPE `business-found` + `email-enriched`, agent/MCP findability.

# Actor input Schema

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

What kind of local business to find. Accepts friendly names (plumber, cafe, restaurant, dentist, gym, hairdresser, lawyer, hotel, bakery, supermarket) OR raw OSM tags (amenity=restaurant, shop=hairdresser). Example agent prompts: "find plumbers in Austin", "list cafes near Brisbane CBD", "get dentist leads in Prague".

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

Human place name geocoded via public Nominatim (OpenStreetMap). Examples: "Austin, TX", "Brisbane, Australia", "Prague, Czechia", "Berlin Mitte". Required unless bbox is set. Prefer this for agent tool calls.

## `bbox` (type: `string`):

Optional south,west,north,east decimal degrees. When set, skips geocoding and searches this box instead of location. Example: "30.20,-97.80,30.35,-97.65". Leave empty to use location.

## `radiusKm` (type: `integer`):

When using location (not bbox), expand the geocoded point into a square search area of this radius in kilometers. Default 5. Range 1–50.

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

Hard cap on businesses returned (and charged as business-found). Use when the agent only needs a short list. Default 25. Range 1–500.

## `enrichEmails` (type: `boolean`):

If true, Cheerio-crawl each business website (homepage + /contact /about) for public emails. Charges email-enriched once per business that yields ≥1 email. Default false for cheapest discovery-only runs; enable for outreach-ready leads.

## `maxPagesPerWebsite` (type: `integer`):

Only used when enrichEmails is true. How many paths to fetch per website (/, /contact, /contact-us, /about, /about-us). Default 3. Range 1–8.

## `requirePhoneOrWebsite` (type: `boolean`):

If true, drop businesses that have neither phone nor website in OSM tags (higher-quality leads). Default true.

## `proxyConfiguration` (type: `object`):

Apify Proxy for optional website email enrichment. OSM/Nominatim use direct HTTPS with a polite User-Agent. Agents can leave the default.

## Actor input object example

```json
{
  "category": "cafe",
  "location": "Austin, TX",
  "radiusKm": 5,
  "maxResults": 10,
  "enrichEmails": false,
  "maxPagesPerWebsite": 3,
  "requirePhoneOrWebsite": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `businesses` (type: `string`):

One dataset item per business: name, phone, website, emails, address, lat/lon, osmId, category.

# 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": "cafe",
    "location": "Austin, TX",
    "radiusKm": 5,
    "maxResults": 10,
    "maxPagesPerWebsite": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("gary_sells/local-business-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 = {
    "category": "cafe",
    "location": "Austin, TX",
    "radiusKm": 5,
    "maxResults": 10,
    "maxPagesPerWebsite": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("gary_sells/local-business-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 '{
  "category": "cafe",
  "location": "Austin, TX",
  "radiusKm": 5,
  "maxResults": 10,
  "maxPagesPerWebsite": 3
}' |
apify call gary_sells/local-business-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gary_sells/local-business-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/daxYEiUIUWjXj5vNO/builds/UpLcOzTsNJSp2zeWr/openapi.json
