# AI-Verified Local Business Lead (`rich_minds/local-business-lead`) Actor

a"Stop paying for rows — start paying for leads that answer the phone." AI audits each business (website, speed, booking, reviews), verifies decision-maker contacts, scores them against your ideal customer profile, and drafts a personalised opener. Pay only for qualifying leads — first 50 free.

- **URL**: https://apify.com/rich\_minds/local-business-lead.md
- **Developed by:** [Rich Minds](https://apify.com/rich_minds) (community)
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.90 / 1,000 results

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

## AI Local Business Lead Generator — Digital Health Audit, Verified Contacts & Decision-Makers

Turn Google Maps places — or any list of businesses — into **qualified, audited, contact-verified leads** with a personalised outreach opener for each one. Every lead comes with a digital health audit of the business's website and reviews, a verified email that reaches a person where one exists, the owner's name when the site publishes it, an AI fit score against *your* ideal customer, and a first line written for that business.

You pay only for the leads that pass your filters: **first 50 free, then $0.01 per lead, or $0.04 with AI qualification.** No API keys required.

### What does this Actor do?

For every business that passes your filters it delivers:

- **Digital health audit** — website live/dead, HTTPS, mobile viewport, homepage speed grade, CMS/site builder, *which* tracking tools are installed (Google Tag Manager, Meta Pixel, Google Ads, TikTok…), online booking, chat/WhatsApp, hiring signals, and a **digital maturity score with an A–F grade** plus plain-English findings.
- **Contacts that reach a person** — `directEmail` (a named mailbox on the business's own domain) separated from `genericEmail` (`info@`), every domain MX-checked, optional mailbox verification with your MillionVerifier or NeverBounce account; phone in E.164, WhatsApp number, and Facebook / Instagram / LinkedIn / X / YouTube / TikTok / Yelp profiles.
- **Decision-maker** — owner / founder / principal name and role when the website's about page or structured data names one. Never invented.
- **Reputation audit** — negative-review count, unanswered negative reviews, rating distribution and, with AI on, the **recurring complaints** summarised in a line.
- **AI qualification** — an ICP-fit score (0–100) against your description of your ideal customer, the reasons behind it, likely pain points, a one-line summary, an **email subject line** and a ready-to-send opener for email, SMS, WhatsApp, social DM or a cold call.
- **A lead score and label** (`hot` / `warm` / `cold`) that blends fit, contactability and how many of *your* target opportunities the business has, so the sheet is sorted by who to contact first.

It also remembers every lead you have already received (never billed twice), honours a do-not-contact list, and works as a tool for AI agents and n8n / Make / Zapier workflows because every input is a plain typed field.

### Why use it instead of a Google Maps scraper?

Google Maps scrapers all return the same rows: name, address, phone, rating. The work starts afterwards — opening each website, hunting for an email that is not `info@`, checking whether they run ads or take bookings, reading their bad reviews, deciding who is worth a call, writing a first line. This Actor does that work and charges only for the businesses that survive it.

| A typical Google Maps scraper | This Actor |
|---|---|
| Pay per *place scraped*, including closed, duplicate and irrelevant ones | Pay per *qualified lead*; everything that fails your filters costs $0 |
| Emails as a paid add-on, mostly `info@` or empty | Crawls each site, de-obfuscates emails, separates **direct (named)** from generic, MX-checks every domain, optional mailbox verification |
| No idea who runs the business | Owner / founder / principal from the about page and structured data, confirmed by the AI |
| Nothing to talk about | A **digital health audit** per business: speed grade, mobile, HTTPS, tracking pixels, booking, chat, CMS — with an A–F grade and written findings |
| Reviews are just a number | Pulls the lowest-rated Google reviews, flags unanswered ones, and the AI summarises **recurring complaints** |
| Generic "lead quality" | **AI ICP-fit score with reasons, pain points, subject line and opener** for the channel you choose |
| Same leads again next week | Cross-run memory plus a do-not-contact list |
| Scrapes only | Three input modes: search Google Maps, enrich an existing Apify dataset, or paste your own list |
| Unpredictable spend | Four hard caps: `maxQualifiedLeads`, `maxLeadsToProcess`, `maxDiscoveryChargeUsd` and Apify's own max charge |
| "Bring your OpenAI key" | No keys needed — AI runs through Apify's built-in model access, tokens land on your Apify bill at cost |

**A worked example.** 3 search terms × 100 places in one city, AI on, `minLeadScore: 60`, `requireEmail: true`:

| | Raw scraper + manual research | This Actor |
|---|---|---|
| Scraping ~300 places | ≈ $0.45–1.20 | ≈ $0.45–1.20 (same scraper, run on your account) |
| Auditing each site, finding a direct email, reading reviews, judging fit, writing a first line | 300 rows × 2–3 min ≈ **10–15 hours** | included |
| AI qualification of the ~55 leads that pass | — | 55 × $0.04 = **$2.20** + ≈ $0.40 of AI tokens |
| **Result** | 300 rows you still have to clean | **~55 audited, ranked leads with subject lines and openers** |
| **Total** | ≈ $1 + two working days | **≈ $3–4** — and $0 on this Actor for your first 50 leads |

The funnel numbers are illustrative; your pass rate depends on the niche, the city and how strict your filters are. The shape is always the same: cents for the leads that survive, nothing for the ones that don't.

### How much does it cost?

Pay-per-event: **one event per qualified lead, nothing else.**

| Event | When it is charged | Price |
|---|---|---|
| `free-tier` | Your first 50 qualified leads on this Actor, in any mode | **$0.00** |
| `qualified-lead-basic` | AI off — audit, verified contacts (direct / generic), decision-maker, socials, tech, review health, flags, rule-based scores | **$0.01** |
| `qualified-lead-ai` | AI on — everything above plus ICP score, reasons, pain points, recurring complaints, summary, subject line, opener, next step | **$0.04** |

**Never charged:** discovery, crawling, verification, leads below `minLeadScore`, leads that fail any filter, leads on your do-not-contact list, leads seen in a previous run. Platform compute is included in the event price.

**Billed separately, at cost:**

- **AI tokens** — billed to your Apify account at OpenRouter's rates as the run happens (≈ $0.005–0.01 per AI lead with the default model), or to your own key in `byok` mode. An AI lead therefore costs ≈ $0.045–0.05 all-in, and the token line is visible separately in your usage.
- **Google Maps discovery** — the Google Maps Scraper runs on your account (≈ $1.5–4 per 1,000 places; a little more when reviews are on). Not needed in `dataset` or `list` mode.
- **Mailbox verification** — your own MillionVerifier / NeverBounce credits, at most 2 per lead. Optional.

**What a run typically costs**

| Scenario | Leads delivered | On this Actor | Plus |
|---|---|---|---|
| First run, any mode | up to 50 | **$0** | tokens if AI is on (≈ $0.35 for 50) |
| Paste 20 businesses, AI on | 20 | $0.80 | ≈ $0.15 tokens |
| Enrich a 300-row dataset you already have, AI off | ~180 | ≈ $1.80 | nothing |
| One city, 3 search terms, AI on (example above) | ~55 | ≈ $2.20 | ≈ $0.40 tokens + ≈ $0.45–1.20 scraper |
| 1,000 audited, AI-qualified leads a month | 1,000 | $40 | ≈ $8 tokens + scraper |
| Weekly schedule, same city, dedupe on | only *new* leads | often < $1 | tokens for the new leads only |

On the Apify **free plan**, built-in model access is priced at 10× and capped at 2,048 tokens per response — fine for testing; a paid plan or your own key is the cheaper way to run real volumes.

### How to use it

1. **Pick a campaign preset** (or leave *Custom*). A preset fills the search terms, ideal-customer description, offer, filters and target opportunities; anything you type yourself always wins.
2. **Pick a source.** *Search Google Maps* (search terms + location), *Existing Apify dataset* (a previous scraper run), or *Paste my own list* (name / website / phone per business).
3. **Describe your ideal customer and your offer** in two sentences each — presets pre-fill this. Be specific: *"Independent dental clinics with 4+ stars and 20+ reviews that do not offer online booking"* beats *"dentists"*.
4. **Set filters and caps.** `targetFlags`, `minRating`, `minReviews`, `requireEmail` + `minEmailQuality`, then `maxQualifiedLeads` for the budget you want to spend.
5. **Run.** The status line shows *Discovering → Crawling → Verifying → AI-qualifying → Delivered*.
6. **Use the leads.** Open the **Qualified leads**, **Outreach sheet**, **Digital health audit** or **Contact sheet** table; export CSV / Excel / JSON; or push to Google Sheets, HubSpot, Slack, Make or Zapier from the Integrations tab.

#### Campaign presets

| Preset | Built for | What it looks for | Flags to filter the sheet by |
|---|---|---|---|
| **Web agency — no website** | Web design agencies, freelancers | 4★+ service businesses with no website; cold-call script | `no_website` |
| **Web agency — outdated / slow site** | Redesign and CRO agencies | Site-builder templates, no mobile viewport, no HTTPS, speed grade D/F | `site_builder_template`, `slow_website`, `no_mobile_viewport` |
| **Booking software** | Scheduling / booking SaaS | Appointment businesses with no online booking; direct email required | `no_online_booking` |
| **Paid-ads agency** | PPC and social-ads agencies | 4.3★+ with 30+ reviews and no ad pixel or analytics | `no_ads_pixel`, `no_analytics` |
| **Reputation management** | Review and reputation agencies | Rating below 4.3, unanswered negative reviews, few reviews, unclaimed profile; pulls the 10 lowest reviews | `unanswered_negative_reviews`, `low_rating` |
| **Local SEO** | Local SEO agencies | Unclaimed listings, no analytics, few reviews, weak or missing site | `unclaimed_listing`, `no_analytics` |
| **WhatsApp / messaging tools** | WhatsApp Business, chat and CRM vendors (India, LATAM, SEA) | Customer-facing businesses with no WhatsApp or chat button; WhatsApp opener | `no_whatsapp`, `no_chat_widget` |

With *Custom*, set `targetFlags` yourself: the Actor then delivers only leads that have at least one of them and ranks leads with more of them higher.

#### How the pipeline works

1. **Source** — runs the Google Maps Scraper on your account, reads a dataset from any previous scraper run, or takes the list you paste in.
2. **Pre-filter** (free) — closed businesses, duplicates, leads seen in earlier runs, your do-not-contact list, minimum rating / reviews, with or without website.
3. **Audit and enrich** (free) — crawls each website (home + up to 3 contact/about pages) for emails, phones, WhatsApp, social profiles, decision-maker names, tech stack, tracking and ad pixels, booking vendors, chat widgets, hiring signals, HTTPS, mobile viewport and homepage response time / weight / script count. MX-checks every email domain; optionally verifies mailboxes with your verifier account.
4. **Score** (free) — contactability score, digital maturity score (A–F), opportunity flags, growth signals, and which of your target opportunities each lead has.
5. **AI qualify** — scores ICP fit against your description of who you sell to and what you sell, explains why, lists pain points, summarises recurring review complaints, confirms the decision-maker and writes the subject line and opener. The most contactable candidates are assessed first, so tokens go where they matter.
6. **Rank, cap, deliver** — leads above `minLeadScore` are sorted best-first, capped at `maxQualifiedLeads`, and only then charged (or taken from your free 50) and pushed. Optional webhook per lead or per batch.

Nothing before step 6 costs anything on this Actor.

### Input

The shortest useful input — a preset and a city:

```json
{
  "campaignPreset": "booking-saas",
  "sourceMode": "googleMaps",
  "location": "Austin, TX",
  "maxPlacesPerSearch": 100,
  "maxQualifiedLeads": 50
}
```

A fully custom Google Maps search:

```json
{
  "sourceMode": "googleMaps",
  "searchTerms": ["dentist", "orthodontist"],
  "location": "Austin, TX",
  "maxPlacesPerSearch": 100,
  "maxReviewsPerPlace": 10,
  "icpDescription": "Independent dental clinics with 4+ stars and 20+ reviews that do not offer online booking.",
  "serviceOffered": "We build modern dental websites with built-in online booking, live in 14 days.",
  "minRating": 4,
  "minReviews": 20,
  "targetFlags": ["no_online_booking", "site_builder_template"],
  "requireEmail": true,
  "minEmailQuality": "verified",
  "suppressionList": ["existing-client.com", "+15125550100"],
  "minLeadScore": 60,
  "maxQualifiedLeads": 50,
  "outreachChannel": "email"
}
```

Enrich a list you already have (no Google Maps cost):

```json
{
  "sourceMode": "list",
  "leadsList": [
    { "name": "Bright Smile Dental", "website": "brightsmile.example", "phone": "(512) 555-0134", "city": "Austin", "countryCode": "US" },
    { "name": "Corner Café", "website": "cornercafe.example", "countryCode": "GB" }
  ],
  "enableAi": false
}
```

Enrich the output of any Google Maps scraper run (pick the dataset in the form):

```json
{ "sourceMode": "dataset", "datasetId": "abc123DatasetId", "minLeadScore": 50 }
```

#### Input options

| Field | What it does |
|---|---|
| `campaignPreset` | One of the 7 presets above, or `custom` (default) |
| `sourceMode` | `googleMaps`, `dataset` or `list` |
| `searchTerms`, `location`, `maxPlacesPerSearch` | Google Maps search (one term per line; a preset fills the terms if you leave them empty) |
| `language`, `skipClosedPlaces`, `discoveryActorId`, `maxDiscoveryChargeUsd` | Google Maps search details: result language, drop closed places (default on), which scraper Actor to run, and a cap on its spend |
| `maxReviewsPerPlace` | Pull the N lowest-rated Google reviews per place for the reputation audit (Google Maps mode; 0 = off) |
| `datasetId` | `dataset` mode: the Apify dataset to enrich (pick it in the form) |
| `leadsList`, `defaultCountryCode` | `list` mode: the businesses to enrich (`name`, `website`, `phone`, `city`, `countryCode`, `rating`, `reviewCount`, `reviews`…) and the country used to normalise phone numbers |
| `icpDescription`, `serviceOffered` | Drive the AI score and the opener — be specific |
| `minRating`, `minReviews`, `websiteFilter` | Cheap pre-filters; `websiteFilter: "withoutWebsite"` gives pure no-website leads |
| `targetFlags` | Deliver only leads with at least one of these audit findings; more matches rank higher |
| `requireEmail` + `minEmailQuality` | `any` / `verified` (MX or verifier OK) / `direct` (a named person on the business's own domain) |
| `minLeadScore` | Below this the lead is discarded and not charged (default 50) |
| `suppressionList` | Domains, emails, phones or exact names never to deliver — customers, competitors, opt-outs |
| `enrichWebsite`, `maxPagesPerSite`, `respectRobotsTxt` | Website crawl controls (default on, 3 pages, robots.txt respected) |
| `verifyEmails` | Free MX check on every email domain (default on) |
| `emailVerifier` + `emailVerifierApiKey` | Optional mailbox verification with your MillionVerifier or NeverBounce account |
| `excludeFreeEmailProviders` | Drop gmail / yahoo / hotmail-style addresses |
| `enableAi` | AI qualification on/off (default on) |
| `llmProvider`, `llmModel`, `llmApiKey` | `apify` (default, no keys; Claude Haiku 4.5 or any OpenRouter slug) or `byok` with your OpenAI / Anthropic / Gemini / Groq key |
| `outreachChannel`, `outreachTone` | `email` (with subject line), `sms`, `whatsapp`, `dm` or `call` × `friendly`, `professional` or `direct` |
| `aiCandidateMultiplier` | How many of the best candidates get the AI pass, as a multiple of `maxQualifiedLeads` (default 2) |
| `maxQualifiedLeads`, `maxLeadsToProcess` | Hard caps on delivered leads (and therefore on what you pay) and on sites crawled per run |
| `dedupeAcrossRuns`, `dedupeStoreName` | Never receive the same lead twice; one store per campaign |
| `webhookUrl`, `webhookHeaders`, `webhookBatchSize` | POST leads as JSON with your auth headers, one per lead or in batches |
| `proxyConfiguration` | Apify Proxy for the website crawl (recommended) |

Every field has a description in the input form.

### Output

One dataset item per qualified lead, best-scoring first. Four table views are ready in the run's **Output** tab, each exportable as CSV, Excel or JSON:

| View | Columns | Use it for |
|---|---|---|
| **Qualified leads** | Business, score, fit, digital grade, contact quality, email, phone, website, rating, target hits, opportunities, opener | The overview |
| **Outreach sheet** | Business, decision-maker, direct email, best email, phone, WhatsApp, subject, opener, next step | Importing into Smartlead / Instantly / lemlist or a dialer — `outreachOpener` becomes the first line of the sequence |
| **Digital health audit** | Maturity score and grade, speed, mobile, HTTPS, tech, tracking tools, paid ads, booking, chat, review health, findings, growth signals | The audit an agency shows the prospect |
| **Contact sheet** | Business, decision-maker, direct / generic email, quality, phone, WhatsApp, website, socials, address | CRM import |

A qualified lead with AI on:

```json
{
  "name": "Bright Smile Dental",
  "category": "Dentist",
  "city": "Austin",
  "countryCode": "US",
  "phone": "+15125550134",
  "website": "https://www.brightsmile.example/",
  "googleRating": 4.7,
  "reviewCount": 132,
  "isClaimed": false,

  "bestEmail": "dr.patel@brightsmile.example",
  "directEmail": "dr.patel@brightsmile.example",
  "genericEmail": "info@brightsmile.example",
  "contactQuality": "direct",
  "decisionMakerName": "Dr. Anjali Patel",
  "decisionMakerRole": "Principal Dentist",
  "socials": { "facebook": "https://www.facebook.com/brightsmileatx", "instagram": "https://instagram.com/brightsmile.atx" },

  "techStack": ["Wix"],
  "trackingTools": ["Google Analytics"],
  "runsPaidAds": false,
  "hasOnlineBooking": false,
  "hasChatWidget": false,
  "performanceGrade": "D",
  "digitalMaturityScore": 52,
  "digitalMaturityGrade": "C",
  "opportunityFlags": ["slow_website", "no_online_booking", "no_chat_widget", "no_whatsapp", "no_ads_pixel", "site_builder_template", "unclaimed_listing", "unanswered_negative_reviews"],
  "auditFindings": ["Homepage is slow or heavy (performance grade D/F)", "No online booking / ordering detected", "…"],
  "growthSignals": ["high_review_volume", "strong_reputation"],
  "targetFlagsMatched": ["no_online_booking", "site_builder_template"],

  "negativeReviewCount": 3,
  "unansweredNegativeReviews": 2,
  "recurringComplaints": ["Phone not answered when trying to book", "Website unusable on mobile"],

  "contactabilityScore": 100,
  "icpFitScore": 88,
  "leadScore": 91,
  "fitLabel": "hot",
  "scoreReasons": [
    "Independent dentist with 4.7★ / 132 reviews — strong reputation, matches ICP",
    "No online booking on a slow Wix site (grade D) — core pain the offer solves",
    "Negative reviews mention unanswered phones — patients already want to book online",
    "Google Business Profile unclaimed — low digital maturity"
  ],
  "painPoints": ["Phone-only appointment booking", "Slow template site limits conversions", "Unclaimed listing hurts local SEO"],
  "businessSummary": "Independent family dental clinic in downtown Austin with a strong review base and a slow template website.",
  "outreachSubject": "Bright Smile's 4.7 stars, without the phone tag",
  "outreachOpener": "Anjali, Bright Smile has a 4.7-star reputation on Google, yet two recent reviews say patients couldn't get through to book — would it be worth seeing how a 14-day site with built-in booking turns those reviews into appointments?",
  "recommendedNextStep": "Email Dr. Patel directly with a 2-minute Loom of a booking-enabled dental site.",
  "chargedEvent": "qualified-lead-ai",
  "enrichedAt": "2026-09-20T09:12:44+00:00"
}
```

#### Output fields

| Group | Fields |
|---|---|
| **Business** | `leadId`, `name`, `category`, `categories`, `address`, `street`, `city`, `state`, `postalCode`, `countryCode`, `lat`, `lng`, `placeUrl`, `placeId`, `isClaimed`, `googleRating`, `reviewCount`, `reviewsDistribution` |
| **Contacts** | `phone` (E.164), `phoneRaw`, `phonesFromWebsite`, `whatsappNumber`, `bestEmail`, `directEmail`, `genericEmail`, `contactQuality` (`direct` / `generic` / `other` / `free-provider` / `none`), `emails[]` (`email`, `type`, `domainMatchesWebsite`, `mxValid`, `verification`, `verifiedBy`, `sourceUrl`), `decisionMakerName`, `decisionMakerRole`, `peopleFound[]`, `socials{}` |
| **Website & audit** | `website`, `websiteDomain`, `websiteStatus`, `websiteTitle`, `websiteDescription`, `usesSsl`, `hasMobileViewport`, `responseMs`, `pageWeightKb`, `scriptCount`, `performanceGrade`, `techStack`, `trackingTools`, `hasAnalytics`, `hasAdsPixel`, `runsPaidAds`, `hasOnlineBooking`, `hasChatWidget`, `hasWhatsApp`, `isHiring`, `pagesCrawled`, `digitalMaturityScore`, `digitalMaturityGrade`, `opportunityFlags`, `auditFindings`, `growthSignals`, `targetFlagsMatched` |
| **Reputation** | `negativeReviewCount`, `negativeReviewSample` (text only), `unansweredNegativeReviews`, `recurringComplaints` (AI) |
| **Scores** | `contactabilityScore`, `icpFitScore` (AI), `leadScore`, `fitLabel`, `scoreReasons` (AI), `painPoints` (AI), `businessSummary` (AI) |
| **Outreach** | `outreachSubject`, `outreachOpener`, `recommendedNextStep` (all AI), `aiModel` |
| **Bookkeeping** | `chargedEvent` (`free-tier` / `qualified-lead-basic` / `qualified-lead-ai`), `source`, `dedupeKey`, `enrichedAt` |

**Opportunity flags:** `no_website`, `website_unreachable`, `no_ssl`, `no_mobile_viewport`, `slow_website`, `no_email_found`, `no_online_booking`, `no_chat_widget`, `no_whatsapp`, `no_analytics`, `no_ads_pixel`, `no_social_presence`, `weak_social_presence`, `site_builder_template`, `unclaimed_listing`, `low_rating`, `few_reviews`, `recent_negative_reviews`, `unanswered_negative_reviews`.
**Growth signals:** `hiring`, `runs_paid_ads`, `high_review_volume`, `strong_reputation`, `multi_channel`.

The run's key-value store also holds a `RUN_SUMMARY` record with the funnel (discovered → filtered → enriched → AI-assessed → qualified), per-reason skip counts, charged events, free-tier balance and webhook delivery counts.

#### What the openers look like

The AI writes for the channel you choose and must anchor on one specific, observed detail — never a placeholder, never a claim it cannot see in the data. When the site names an owner, the opener uses their first name. Leads rated *cold* get no opener at all.

- **Email** (friendly) — subject *"Bright Smile's 4.7 stars, without the phone tag"* — *"Anjali, Bright Smile has a 4.7-star reputation on Google, yet two recent reviews say patients couldn't get through to book — would it be worth seeing how a 14-day site with built-in booking turns those reviews into appointments?"*
- **WhatsApp** (friendly) — *"Hi! Found Spice Garden on Google Maps — 4.6★ from 210 reviews, but there's no WhatsApp button on your site, so evening enquiries go unanswered. We set up click-to-chat with instant replies in two days. Want a quick look?"*
- **SMS** (direct) — *"Zen Yoga has 4.9★ across 210 reviews but classes still can't be booked online. We launch booking-enabled studio sites in 14 days — worth a 10-minute look this week?"*
- **Cold call** (friendly) — *"Hi, I found Rosa's Cleaning on Google Maps — 4.9 stars from 63 reviews, but no website linked, so new customers can only reach you by phone. We put cleaning businesses online with booking in about a week. Have you got a minute for me to explain how that works?"*

### Integrations and automation

- **Schedules** — run weekly per city and category; the dedupe store guarantees only *new* leads are delivered and billed.
- **Integrations tab** — Google Sheets, HubSpot, Slack, Make, Zapier, GitHub and generic webhooks, no code.
- **Webhook** — `webhookUrl` receives `{ "event": "lead.qualified", "lead": { … } }` per lead, or `{ "event": "leads.qualified", "leads": [ … ] }` in batches (`webhookBatchSize`), with any headers you set in `webhookHeaders` (API keys, bearer tokens). Point it at an n8n / Make webhook node or straight at a cold-email tool's lead-import endpoint.
- **Cold-email tools** — export the **Outreach sheet** view as CSV; its columns map onto Smartlead / Instantly / lemlist custom fields so every email in the sequence can start with `{{outreachOpener}}`.
- **AI agents (MCP)** — add the Actor to your Apify MCP server and let Claude or ChatGPT find, audit and qualify leads on request ("find 20 Austin dentists without online booking and draft openers").

#### Using the API

Python (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<username>/local-business-lead-gen-ai").call(run_input={
    "campaignPreset": "web-agency-no-website",
    "sourceMode": "googleMaps",
    "location": "Chennai, India",
    "maxQualifiedLeads": 25,
})
for lead in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(lead["name"], lead["contactQuality"], lead["bestEmail"], lead["outreachOpener"])
```

JavaScript (`npm install apify-client`):

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('<username>/local-business-lead-gen-ai').call({
    campaignPreset: 'booking-saas',
    sourceMode: 'googleMaps',
    location: 'Austin, TX',
    maxQualifiedLeads: 25,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Use `?view=outreach&format=csv` (or `audit`, `contacts`) on the dataset items endpoint to download a view directly.

### Who is it for?

- **Web, ads, SEO and reputation agencies** — the audit *is* the pitch; the decision-maker and direct email are who to send it to.
- **Local SaaS vendors** — booking, POS, reputation, payroll, insurance, messaging: target the exact gap your product fills with `targetFlags`.
- **Sales teams and SDRs** — a clean, ranked sheet with openers instead of scraper output.
- **Agencies selling lead generation as a service** — a weekly schedule pushing to a client's Google Sheet, or a webhook into a warmed sending tool, is a productised offer: audited leads with direct contacts, subject lines and openers, deduplicated month over month.

### How the AI works

- **Grounded, not generative.** The model sees one structured business record at a time — Google Maps data, the audit, the people and reviews found — and must return a typed assessment: score, label, up to 4 reasons tied to specific fields, up to 3 pain points, up to 3 recurring complaints, a decision-maker only if the record names one, a summary, a subject line, an opener and a next step. Names the record does not contain are discarded even if the model produces them.
- **Your ICP, your offer.** The score measures fit with your `icpDescription` and how likely the business needs your `serviceOffered` — not a generic "lead quality" number.
- **Tokens where they matter.** Only the most contactable candidates get the AI pass (`aiCandidateMultiplier` × `maxQualifiedLeads`).
- **No keys required.** Default is Claude Haiku 4.5 through Apify's built-in model access. Set `llmProvider: "byok"` to use your own OpenAI, Anthropic, Gemini or Groq key, or put any OpenRouter model slug in `llmModel`.
- **Graceful fallback.** If the model cannot be reached, the run continues with the rule-based audit and scoring, and those leads are charged at the basic price.

### Data, compliance and limitations

- Only **publicly available business information** is processed: Google Maps listings, the business's own public website pages, and public Google reviews (stars, text, date and whether the owner replied — reviewer names and profiles are never kept).
- The crawler respects `robots.txt` by default, fetches at most a handful of pages per site at normal speed, and does nothing to circumvent access controls.
- **No LinkedIn or personal-profile scraping.** Decision-maker names come only from what the business publishes about itself (about / team pages, `schema.org` `founder` / `employee` data). Many small-business sites name nobody; then `decisionMakerName` is `null`.
- **Email verification is MX-level by default**, not SMTP (which gets cloud IPs blocked). For mailbox-level results, plug in your MillionVerifier or NeverBounce key.
- **The speed grade is a crawl-time heuristic** (homepage response time, HTML weight, external script count), not a Lighthouse score. A grade D/F site is slow on any device; run PageSpeed Insights on the ones you pitch.
- Built for **B2B prospecting**, not spam: free-provider addresses are labelled so you can exclude them, the `suppressionList` keeps opt-outs and customers out of every run, and every record carries `enrichedAt`. Follow the outreach laws that apply to you (CAN-SPAM, GDPR/PECR, India's DPDP Act…).
- Keys you supply (`llmApiKey`, `emailVerifierApiKey`) are stored encrypted by Apify and never logged.

### FAQ

**Which Google Maps scraper does it use?** By default the most-used one on Apify Store (`compass/crawler-google-places`), started on your account with contacts and images switched off to keep it cheap. Reviews are fetched only when `maxReviewsPerPlace` > 0. Change `discoveryActorId` to use another.

**Can I use it without Google Maps?** Yes — `list` mode audits and enriches any businesses you paste in; `dataset` mode enriches any previous scraper run. Reviews in a pasted list (`reviews: [{ "stars": 2, "text": "…" }]`) are used too.

**How does the free tier work?** The first 50 qualified leads delivered to your Apify account are pushed with `chargedEvent: "free-tier"` and cost nothing on this Actor. The counter lives in a key-value store on your account (`local-business-lead-gen-ai-account`); `RUN_SUMMARY` shows what is left. AI tokens for those leads are still billed by Apify as usual.

**What is a "direct" email?** A named mailbox on the business's own domain — `anjali@brightsmile.com`, not `info@brightsmile.com` and not `brightsmile@gmail.com`. `contactQuality` tells you which kind each lead has; `minEmailQuality: "direct"` delivers only leads that have one.

**Do I need an OpenAI or Anthropic account?** No. By default the AI runs through Apify's built-in model access and the tokens appear on your Apify usage. Switch `llmProvider` to `byok` and add `llmApiKey` if you would rather use your own account.

**Which model?** Default is Claude Haiku 4.5. Any OpenRouter slug works in `llmModel`, e.g. `openai/gpt-4.1-mini`, `google/gemini-3.6-flash`, `anthropic/claude-sonnet-4.5` — bigger models cost more tokens, nothing else changes.

**Will the AI make things up?** It only sees the record it is given and must ground every reason in a specific field; decision-maker names it produces are checked against the crawled text and dropped if absent; cold leads get an empty opener rather than an invented pitch. Treat the opener as a strong first draft that already knows the business — read it before you send it.

**Why did I get fewer leads than `maxQualifiedLeads`?** Fewer passed your filters and score. Loosen `targetFlags`, `minLeadScore`, `minEmailQuality` or `minReviews`, or raise `maxPlacesPerSearch`.

**How does dedupe work across runs?** Every lead you were charged for (or received free) is remembered in a key-value store on your account (`dedupeStoreName`, default `local-leads-seen`). Use a different store name per campaign. Leads that were filtered out are *not* remembered, so they can qualify later when, say, their review count grows.

**Can I cap what a run can cost?** Four ways: `maxQualifiedLeads` (max events), `maxLeadsToProcess` (max sites crawled), `maxDiscoveryChargeUsd` (max Google Maps scraper spend) and Apify's own *Max total charge* on the run.

### Support

Found a bug, or want a new audit check, preset or data source? Open an issue on the **Issues** tab of this Actor — requests from real campaigns drive the roadmap.

### Changelog

- **0.2** — Digital health audit (speed grade, named tracking tools, maturity score A–F, written findings, hiring and growth signals); direct vs generic email with `contactQuality`; decision-maker extraction from about pages and structured data, AI-confirmed; optional MillionVerifier / NeverBounce mailbox verification; Google-review reputation audit with recurring-complaint summary; 7 campaign presets; `targetFlags` filter and score boost; `minEmailQuality`; do-not-contact `suppressionList`; WhatsApp detection and outreach channel; email subject lines; webhook headers and batching; Outreach and Audit dataset views; free tier (first 50 leads) and lower prices ($0.01 basic / $0.04 AI).
- **0.1** — Initial release: 3 source modes, website enrichment, MX verification, opportunity flags, AI qualification with openers (no API keys needed), cross-run dedupe, webhook, pay-per-qualified-lead pricing.

# Actor input Schema

## `campaignPreset` (type: `string`):

Start from a ready-made campaign: it fills the search terms, ideal customer, offer, filters and target opportunities below - anything you type yourself always wins. <b>Custom</b> = use only what you enter.

## `sourceMode` (type: `string`):

<b>googleMaps</b> — search Google Maps for you (runs the Google Maps Scraper on your account, its usage is billed separately). <b>dataset</b> — enrich an existing Apify dataset from any Google Maps scraper run. <b>list</b> — enrich a list of businesses you paste in (name / website / phone).

## `searchTerms` (type: `array`):

Business types to search on Google Maps, one per line. Example: <code>dentist</code>, <code>roofing contractor</code>, <code>yoga studio</code>. Left empty with a preset selected = the preset's terms.

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

City, region or country to search in. Example: <code>Austin, TX</code>, <code>Chennai, India</code>, <code>Greater Manchester, UK</code>.

## `maxPlacesPerSearch` (type: `integer`):

How many places to pull from Google Maps for each search term before filtering. More places = more discovery cost, more candidates.

## `language` (type: `string`):

Language code for Google Maps results (affects category names).

## `skipClosedPlaces` (type: `boolean`):

Drop permanently or temporarily closed places.

## `maxReviewsPerPlace` (type: `integer`):

Pull this many <b>lowest-rated</b> Google reviews per place (text, stars, date, whether the owner replied - no reviewer data). Enables the <code>recent\_negative\_reviews</code> / <code>unanswered\_negative\_reviews</code> flags and, with AI on, a summary of recurring complaints per lead. Needs the place detail page, so the Google Maps scraper run costs a little more. 0 = off.

## `discoveryActorId` (type: `string`):

Actor used for discovery in <b>googleMaps</b> mode. Default is the most-used Google Maps Scraper on Apify. Any Actor that outputs Google Maps places with <code>title</code>, <code>website</code>, <code>phone</code>, <code>address</code> fields works.

## `maxDiscoveryChargeUsd` (type: `number`):

Hard cap on what the Google Maps scraper run may charge your account in <b>googleMaps</b> mode. Leave empty for no cap. Typical: 1,000 places ≈ $1.5–4.

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

Only for <b>dataset</b> mode. An Apify dataset on your account containing Google Maps places (from any scraper run). Pick it here so the Actor is granted read access to it.

## `leadsList` (type: `array`):

Only for <b>list</b> mode. JSON array of objects. Recognised keys: <code>name</code>, <code>website</code>, <code>phone</code>, <code>address</code>, <code>city</code>, <code>countryCode</code>, <code>category</code>, <code>rating</code>, <code>reviewCount</code>.

## `defaultCountryCode` (type: `string`):

Two-letter country code used to normalise phone numbers when a lead has no country (dataset / list modes).

## `icpDescription` (type: `string`):

Describe who you want to sell to. The AI scores every lead against this. Example: <i>Independent dental clinics with 4+ stars and 20+ reviews that do not offer online booking, owner-operated, not part of a chain.</i>

## `serviceOffered` (type: `string`):

One or two sentences on your offer. Used to judge fit and to write the outreach opener. Example: <i>We build conversion-focused websites with online booking for local service businesses, live in 14 days.</i>

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

Drop places rated below this (0 = no filter).

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

Drop places with fewer reviews than this (0 = no filter).

## `websiteFilter` (type: `string`):

Keep all places, only those with a website, or only those <b>without</b> one (great for web agencies).

## `targetFlags` (type: `array`):

Only deliver leads that have at least one of these audit findings, and rank leads with more of them higher. Leave empty to keep every lead. Available: <code>no\_website</code>, <code>website\_unreachable</code>, <code>no\_ssl</code>, <code>no\_mobile\_viewport</code>, <code>slow\_website</code>, <code>no\_email\_found</code>, <code>no\_online\_booking</code>, <code>no\_chat\_widget</code>, <code>no\_whatsapp</code>, <code>no\_analytics</code>, <code>no\_ads\_pixel</code>, <code>no\_social\_presence</code>, <code>weak\_social\_presence</code>, <code>site\_builder\_template</code>, <code>low\_rating</code>, <code>few\_reviews</code>, <code>unclaimed\_listing</code>, <code>recent\_negative\_reviews</code>, <code>unanswered\_negative\_reviews</code>.

## `requireEmail` (type: `boolean`):

Only output leads with an email address (see the quality level below). Leads without a website can never pass this filter.

## `minEmailQuality` (type: `string`):

Applies when <b>Require an email address</b> is on. <b>Any</b> - any address found. <b>Verified</b> - domain accepts mail (MX) or the mailbox verifier said valid / catch-all. <b>Direct</b> - a named person on the business's own domain (e.g. <code>jane@brightsmile.com</code>, never <code>info@</code>), verified - it reaches a person rather than a shared inbox.

## `minLeadScore` (type: `integer`):

Leads scoring below this are discarded and <b>not charged</b>.

## `suppressionList` (type: `array`):

Domains, emails, phone numbers or exact business names to never output - existing customers, competitors, opt-outs. One per line. Suppressed businesses are skipped before any crawling and never charged.

## `enrichWebsite` (type: `boolean`):

Visit the business website (home + contact/about pages) for emails, phones, WhatsApp, social profiles, decision-maker names, tech stack, tracking & ad pixels, online booking, chat widgets, hiring signals and a homepage speed check. This is the digital health audit.

## `maxPagesPerSite` (type: `integer`):

Homepage plus up to this many contact/about pages.

## `respectRobotsTxt` (type: `boolean`):

Skip pages the site's robots.txt disallows.

## `verifyEmails` (type: `boolean`):

Check that each email's domain can actually receive mail. Cheap and removes obviously dead addresses.

## `emailVerifier` (type: `string`):

Mailbox-level verification with <b>your own</b> MillionVerifier or NeverBounce account, on top of the free MX check. At most 2 addresses per lead (best direct + best generic) are checked, so a lead costs you at most 2 verifier credits. Results land in <code>emails\[].verification</code> (valid / invalid / catch\_all / disposable / unknown).

## `emailVerifierApiKey` (type: `string`):

Required when a verification service is selected. Stored encrypted by Apify, never logged.

## `excludeFreeEmailProviders` (type: `boolean`):

Drop gmail / yahoo / hotmail style addresses (useful for GDPR-conscious B2B outreach).

## `enableAi` (type: `boolean`):

Score each lead against your ICP, explain why, list pain points, summarise recurring review complaints, confirm the decision-maker and write a personalised opener (+ subject line for email). Off = rule-based audit and scoring only (cheaper).

## `llmProvider` (type: `string`):

<b>Apify (no keys)</b> — the AI runs through Apify's built-in OpenRouter proxy; tokens are billed to your Apify account at OpenRouter's rates (≈ $0.005–0.01 per lead). <b>My own key</b> — use your OpenAI / Anthropic / Gemini / Groq key instead.

## `llmModel` (type: `string`):

Leave empty for the default (Claude Haiku 4.5 — best cost/quality for this task). Apify mode takes an OpenRouter slug such as <code>anthropic/claude-haiku-4.5</code>, <code>openai/gpt-4.1-mini</code>, <code>google/gemini-3.6-flash</code>. Own-key mode takes <code>provider:model</code>, e.g. <code>anthropic:claude-haiku-4-5-20251001</code>.

## `llmApiKey` (type: `string`):

Required when <b>AI model access</b> is <i>My own API key</i>. Stored encrypted by Apify, never logged.

## `outreachChannel` (type: `string`):

The opener is written for this channel. Email also gets a subject line. If a decision-maker was found, the opener addresses them by first name.

## `outreachTone` (type: `string`):

Voice of the generated opener.

## `aiCandidateMultiplier` (type: `integer`):

How many of the best-contactability candidates get the AI pass, as a multiple of <b>Max qualified leads</b>. 2 = assess up to 2× the cap so the score filter still has room. Higher = more thorough, slower.

## `maxQualifiedLeads` (type: `integer`):

Hard cap on results (and therefore on what you pay for). The best-scoring leads are output first.

## `maxLeadsToProcess` (type: `integer`):

Upper bound on how many places are enriched/scored in one run (controls run time). Default = 3 × max qualified leads.

## `dedupeAcrossRuns` (type: `boolean`):

Remembers every lead you were charged for (in a named key-value store on your account) and skips it in future runs.

## `dedupeStoreName` (type: `string`):

Key-value store used for cross-run memory. Use different names for different campaigns.

## `webhookUrl` (type: `string`):

Qualified leads are POSTed here as JSON (Zapier, Make, n8n, Smartlead, Instantly, your CRM). For Google Sheets / HubSpot / Slack you can also use Apify's built-in Integrations tab.

## `webhookHeaders` (type: `object`):

Extra HTTP headers for the webhook, e.g. <code>{"Authorization": "Bearer …"}</code> or an API-key header.

## `webhookBatchSize` (type: `integer`):

1 = one POST per lead (<code>{event: "lead.qualified", lead: {…}}</code>) the moment it is ready. Higher = one POST per N leads (<code>{event: "leads.qualified", leads: \[...]}</code>).

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

Apify Proxy is recommended so website crawling is not blocked.

## Actor input object example

```json
{
  "campaignPreset": "custom",
  "sourceMode": "googleMaps",
  "location": "Austin, TX",
  "maxPlacesPerSearch": 60,
  "language": "en",
  "skipClosedPlaces": true,
  "maxReviewsPerPlace": 0,
  "discoveryActorId": "compass/crawler-google-places",
  "leadsList": [
    {
      "name": "Example Dental Studio",
      "website": "https://example.com",
      "phone": "+1 512 555 0100",
      "city": "Austin",
      "countryCode": "US"
    }
  ],
  "defaultCountryCode": "US",
  "minRating": 0,
  "minReviews": 0,
  "websiteFilter": "any",
  "requireEmail": false,
  "minEmailQuality": "any",
  "minLeadScore": 50,
  "enrichWebsite": true,
  "maxPagesPerSite": 3,
  "respectRobotsTxt": true,
  "verifyEmails": true,
  "emailVerifier": "none",
  "excludeFreeEmailProviders": false,
  "enableAi": true,
  "llmProvider": "apify",
  "outreachChannel": "email",
  "outreachTone": "friendly",
  "aiCandidateMultiplier": 2,
  "maxQualifiedLeads": 100,
  "dedupeAcrossRuns": true,
  "dedupeStoreName": "local-leads-seen",
  "webhookBatchSize": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `qualifiedLeads` (type: `string`):

All qualified leads as JSON, best-scoring first. Each item includes name, category, address, phone (E.164), whatsappNumber, website, googleRating, reviewCount, bestEmail / directEmail / genericEmail, contactQuality, emails\[] (type, domainMatchesWebsite, mxValid, verification), decisionMakerName/Role, socials, techStack, trackingTools, runsPaidAds, performanceGrade, digitalMaturityScore/Grade, opportunityFlags\[], auditFindings\[], growthSignals\[], targetFlagsMatched\[], negativeReviewCount, recurringComplaints\[], contactabilityScore, icpFitScore, leadScore, fitLabel, scoreReasons\[], painPoints\[], businessSummary, outreachSubject, outreachOpener, recommendedNextStep, chargedEvent and dedupeKey.

## `contactSheet` (type: `string`):

The same leads reduced to name, decision-maker, direct / generic email, contact quality, phone, WhatsApp, website, socials and address - ready to import into a CRM.

## `outreachSheet` (type: `string`):

Business, decision-maker, direct email, phone, WhatsApp, subject line, opener and next step - the columns Smartlead / Instantly / lemlist / a dialer need.

## `auditSheet` (type: `string`):

One audit row per lead: maturity score and grade, speed grade, mobile/HTTPS, tech stack, tracking tools, booking/chat/WhatsApp, review health, findings and growth signals.

## `runSummary` (type: `string`):

JSON record with the funnel (discovered → filtered → enriched → AI-assessed → qualified), per-reason skip counts, charged events by type, free-tier leads used and remaining, whether a budget limit was reached, the preset and target flags used, webhook delivery counts and the dedupe store size.

# 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 = {
    "location": "Austin, TX",
    "leadsList": [
        {
            "name": "Example Dental Studio",
            "website": "https://example.com",
            "phone": "+1 512 555 0100",
            "city": "Austin",
            "countryCode": "US"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rich_minds/local-business-lead").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 = {
    "location": "Austin, TX",
    "leadsList": [{
            "name": "Example Dental Studio",
            "website": "https://example.com",
            "phone": "+1 512 555 0100",
            "city": "Austin",
            "countryCode": "US",
        }],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("rich_minds/local-business-lead").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 '{
  "location": "Austin, TX",
  "leadsList": [
    {
      "name": "Example Dental Studio",
      "website": "https://example.com",
      "phone": "+1 512 555 0100",
      "city": "Austin",
      "countryCode": "US"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call rich_minds/local-business-lead --silent --output-dataset

```

## MCP server setup

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

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/bdmoueC5hRerx5yIc/builds/ZvCda77Zh4HVybLw3/openapi.json
