# Local Lead Finder - Great Reviews, Weak Website & Booking (`wakachobi/local-lead-finder`) Actor

Find local businesses that are loved offline but weak online: great Google Maps ratings with no website, no HTTPS, no mobile viewport, no online booking, an outdated site or owners who never reply to reviews. Scored leads for web, booking and AI-receptionist agencies.

- **URL**: https://apify.com/wakachobi/local-lead-finder.md
- **Developed by:** [Cobi Hilliard](https://apify.com/wakachobi) (community)
- **Categories:** Lead generation, Marketing
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 leads

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 Lead Finder: find good businesses with a sloppy online presence

Find local businesses that customers love on Google Maps but that are weak online: no website, a broken or outdated one, no online booking, not mobile-friendly, and owners who never answer reviews. Every business gets a **lead score (0-100)** and a one-sentence reason it is a lead.

Built for agencies and freelancers who sell **websites, booking systems, review management or AI receptionists** to local businesses. No emails or personal data are collected, only business listing data (name, address, business phone).

### Important: how data is sourced and who pays for what

This actor never requests Google itself. It gets places from the popular [Google Maps Scraper (`compass/crawler-google-places`)](https://apify.com/compass/crawler-google-places):

- **Search mode** (`searchQuery` + `location`): the actor starts a Google Maps Scraper run **under your Apify account, and you pay that run's usage and fees directly to Apify**. This is separate from, and in addition to, this actor's price. Keep `maxPlaces` small for a first test.
- **Dataset mode** (`datasetId`): analyse the dataset of a Google Maps Scraper run you already made. No child run, no extra Maps cost. For owner-reply data, run it with `maxReviews` of 5-10.

### What you get (one flat row per lead)

- `name`, `category`, `address`, `city`, `phone`, `website`, `mapsUrl`
- `rating`, `reviewsCount`, `ownerReplyRate` (0-1, share of fetched reviews with an owner response)
- Signals (booleans): `noWebsite`, `siteDown`, `noHttps`, `noMobileViewport`, `noBookingLink`, `outdatedSite`, `lowOwnerReplyRate`
- `leadScore` (0-100) and `leadReasons`
- Rows are sorted by `leadScore`, highest first

#### Sample result

```json
{
  "name": "Fake Pipe & Co (TEST)",
  "category": "Plumber",
  "city": "Faketown",
  "phone": "(555) 010-0101",
  "website": null,
  "rating": 4.8,
  "reviewsCount": 212,
  "ownerReplyRate": 0,
  "noWebsite": true,
  "noBookingLink": true,
  "lowOwnerReplyRate": true,
  "leadScore": 90,
  "leadReasons": "4.8★ from 212 reviews, but no website and the owner never replies to reviews."
}
```

### How scoring works

Businesses must pass `minRating` (default 4.0) and `minReviews` (default 50). Then points are added and capped at 100:

| Signal | Points | Meaning |
|---|---|---|
| noWebsite | 45 | No website, or only a Facebook/Instagram/Yelp-type page |
| siteDown | 40 | Homepage errors, times out or returns 4xx/5xx |
| noBookingLink | 25 | No link or script for booking tools (Calendly, Acuity, Square, Vagaro, Booksy, Setmore, Housecall Pro, Jobber, ServiceTitan, Mindbody, Fresha, OpenTable, Resy, Tock, "book online"...). Also set when there is no website |
| lowOwnerReplyRate | 20 | Owner replied to under 20% of the fetched reviews |
| noHttps, noMobileViewport, outdatedSite | 10 each | Homepage not on HTTPS, no meta viewport, copyright year older than 2 years or response over 5 s |

`onlyLeads` (default on) drops anything under 30.

The homepage of each business is fetched once, only if `robots.txt` allows it, with a 10 second timeout.

### Input

| Field | Default | Description |
|---|---|---|
| `searchQuery` | plumber | Business type |
| `location` | Austin, TX | City/state or country |
| `maxPlaces` | 50 | Places the Maps child run scrapes |
| `datasetId` | | Use an existing Maps scraper dataset instead of searching |
| `minRating` | 4.0 | Minimum Google rating |
| `minReviews` | 50 | Minimum review count |
| `onlyLeads` | true | Drop scores under 30 |
| `maxItems` | 100 | Max leads output |
| `concurrency` | 8 | Parallel website checks |

Examples: `{"searchQuery":"dentist","location":"Denver, CO","maxPlaces":100}` or `{"datasetId":"YOUR_MAPS_DATASET_ID","minReviews":100}`.

### Pricing

**$10 per 1,000 leads** ($0.01 per lead row saved). 100 leads cost $1.00. In search mode, add the cost of the Google Maps Scraper child run to your own account (see above).

### Use cases

Web design and SEO agencies, booking and CRM resellers, review-management and reputation services, AI receptionist and call-answering sellers, freelancers building a local prospect list.

### FAQ

**Is it legal?** Google Maps data comes through the third-party Maps Scraper that you run; website checks fetch only public homepages and obey robots.txt. No personal data (emails, names of people) is collected. You remain responsible for how you use outreach data under local rules.

**Limitations.** Owner-reply rate is based on a small sample of recent reviews (10 in search mode). The booking check looks at the homepage only. Sites behind bot protection may appear as down. Facebook/Instagram/Yelp pages count as no website.

**Integrations / API.** Use the Apify API, webhooks, Zapier, Make or n8n to pipe leads into your CRM.

### Integrations / API

#### 1. Weekly prospect list into Google Sheets (n8n or Make)

Schedule node (Mondays) → **Apify: Run Actor** `wakachobi/local-lead-finder` with
`{"searchQuery": "roofing contractor", "location": "Denver, CO", "maxPlaces": 40}` → **Apify: Get dataset items** →
**Google Sheets: Append rows**. Filter on `leadScore >= 60` to keep only the strongest prospects. Each row's
`leadReasons` makes a ready-made first line for your outreach ("Saw you have 4.8★ from 212 reviews, but no way to book
online…").

#### 2. Clay table enrichment

Start a Clay table from your own city/niche list, add an **Apify** column that runs this Actor per row with
`searchQuery` + `location` from the row, and map `website`, `phone`, `leadScore` and `leadReasons` into columns. Use
Clay's own enrichment to find a contact person at the shortlisted businesses.

#### 3. Re-score a Maps run you already have

Already scraped Google Maps with `compass/crawler-google-places`? Pass its dataset ID instead of a search:
`{"datasetId": "<your dataset id>", "minReviews": 30}`. No new Maps run, so you only pay for the scoring.

#### 4. Plain API call

```bash
curl -X POST "https://api.apify.com/v2/acts/wakachobi~local-lead-finder/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"searchQuery":"dentist","location":"Tampa, FL","maxPlaces":30}'
```

# Actor input Schema

## `searchQuery` (type: `string`):

What to look for on Google Maps, e.g. plumber, dentist, hair salon. The actor runs the Google Maps Scraper (compass/crawler-google-places) for you under YOUR account, and you pay its usage. Ignored if you give a Dataset ID.

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

City and state or country, e.g. Austin, TX. Used together with the business type.

## `maxPlaces` (type: `integer`):

How many Google Maps places the child run scrapes. Output leads are a subset of these (only places passing the rating and review filters and the lead score).

## `datasetId` (type: `string`):

Instead of searching, analyse the dataset of a previous run of compass/crawler-google-places. Include reviews (maxReviews 5-10) in that run to get owner-reply data. If set, search fields are ignored and no child run is started.

## `minRating` (type: `number`):

Only businesses with at least this Google rating (the 'good business' filter).

## `minReviews` (type: `integer`):

Only businesses with at least this many Google reviews.

## `onlyLeads` (type: `boolean`):

Drop businesses with a lead score below 30 (nothing much to sell them). Turn off to get every business that passes the filters.

## `maxItems` (type: `integer`):

Stop after saving this many leads (best scores first).

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

How many business websites to check at once.

## Actor input object example

```json
{
  "searchQuery": "plumber",
  "location": "Austin, TX",
  "maxPlaces": 50,
  "minRating": 4,
  "minReviews": 50,
  "onlyLeads": true,
  "maxItems": 100,
  "concurrency": 8
}
```

# Actor output Schema

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

No description

# 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 = {
    "searchQuery": "plumber",
    "location": "Austin, TX",
    "maxPlaces": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("wakachobi/local-lead-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 = {
    "searchQuery": "plumber",
    "location": "Austin, TX",
    "maxPlaces": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("wakachobi/local-lead-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 '{
  "searchQuery": "plumber",
  "location": "Austin, TX",
  "maxPlaces": 50
}' |
apify call wakachobi/local-lead-finder --silent --output-dataset

```

## MCP server setup

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