# Local Service Lead Qualifier (`rasputinkaiser/local-service-lead-qualifier`) Actor

Rank local service businesses by outreach-readiness and return evidence-backed qualified leads.

- **URL**: https://apify.com/rasputinkaiser/local-service-lead-qualifier.md
- **Developed by:** [Rasputin Kaiser](https://apify.com/rasputinkaiser) (community)
- **Categories:** Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 qualified 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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Local Service Lead Qualifier

Qualified local service leads with evidence-backed reasons to contact.

This Actor helps local SEO agencies, web studios, reputation teams, and B2B sales teams prioritize outreach to local service businesses. It is not another raw Google Maps clone: it discovers or accepts candidates, scores them, enriches public websites when requested, and returns only the leads that look actionable.

### Quick Start

1. Pick a vertical (for example `HVAC`) and a city (`Dallas, TX`).
2. Run in **Live places** mode with the default caps (25 places, 25 leads).
3. Get back a short list of evidence-backed leads: contact paths, score reasons, and outreach copy you can send with light editing.

**What it costs:** billing is per event, so you pay for what the run actually finds. A real 25-place HVAC/Dallas run charged 24 candidate evaluations at $0.008 each and 5 qualified leads at $0.05 each — about $0.44 in events; typical single-city runs land around $0.30–0.80 all-in. A 100-place benchmark across three vertical/city mixes charged 98 candidate evaluations and, with the current credibility gate, those mixes yield 11 qualified leads — about $1.33 in events for the whole batch.

![Real run output: 25 Dallas HVAC places in, 5 evidence-backed leads out](https://api.apify.com/v2/key-value-stores/XWJgy1uFmrIKVnV1t/records/lead-table.png)

**What you get** — a lead discovered in that Dallas run, exactly as emitted:

```json
{
  "businessName": "Texla HVAC",
  "vertical": "HVAC",
  "location": "Dallas, TX",
  "phone": "(972) 514-5618",
  "categoryName": "HVAC contractor",
  "rating": 5,
  "reviewCount": 12,
  "leadScore": 79,
  "leadQuality": "strong",
  "contactReadiness": "phoneReady",
  "evidenceSummary": [
    "Listed as HVAC contractor",
    "Strong 5-star rating but only 12 reviews so far",
    "Google Maps listing links to no website",
    "Direct contact path available",
    "Competitive local market"
  ],
  "reasonToContact": "Texla HVAC looks worth contacting: strong 5-star rating but only 12 reviews so far; Google Maps listing links to no website; direct contact path available.",
  "suggestedMessage": "Hi Texla HVAC, I was researching HVAC in Dallas, TX and yours stood out. I noticed you hold a strong 5-star rating but only 12 reviews so far, and your Google Maps listing doesn't link to a website. I help businesses like yours with Local SEO and conversion audit and think there may be a quick win here. Worth a short note with specifics?"
}
````

![Evidence in, sendable outreach out: the suggested message is built only from observed evidence](https://api.apify.com/v2/key-value-stores/XWJgy1uFmrIKVnV1t/records/outreach-example.png)

### Who It Is For

- Local SEO and web-design agencies building weekly prospect lists.
- Reputation-management teams looking for review/rating pain.
- B2B sales teams selling to local service businesses.
- SaaS vendors selling booking, CRM, review, phone, or field-service tools.

### When Not To Use It

Do not use this Actor when you need a complete raw places export with every business in an area. Use it when you want a shorter list of businesses that already look worth contacting, with the evidence to back that up.

### Input

Core fields:

- `discoveryMode`: `demo`, `providedCandidates`, or `livePlaces`.
- `vertical`: business type. Cross-vertical noise filtering covers HVAC, roofers, plumbers, dentists, lawyers, med spas, landscapers, pest control, electricians, garage door, cleaning, painters, locksmiths, movers, auto repair, and veterinarians; other verticals still score but without that filter (the run logs a warning and sets `RUN_SUMMARY.verticalFilterActive: false` so the difference is visible).
- `locations`: one or more cities, counties, metros, or states.
- `maxPlacesPerLocation`: hard cap to control spend.
- `qualificationThreshold`: minimum score required for dataset output.
- `maxQualifiedLeads`: stop after this many qualified leads have been emitted.
- `minimumEvidenceSignals`: minimum number of score reasons required for output.
- `minimumReviewCount`: skip candidates with fewer than this many public reviews before any scoring or billing (default 0; set 3+ for a firmer evidence floor on volume outreach).
- `requireContactPath`: skip candidates without a phone, email, website, or Maps URL.
- `excludeChains`: off by default. Skip national chain/franchise listings (known brand names, `#1042`-style unit markers, or the same name repeated 3+ times in one location) before any billing; counted in `RUN_SUMMARY.filteredChains`.
- `suppressBusinessIdentifiers`: optional repeat-run suppression list. Add an exact website domain, business phone, public email, or business name from your CRM to skip businesses you already contacted or already manage before enrichment and billing. The run receipt keeps only the number supplied and the number skipped.
- `offerType`: the pitch or service the buyer wants to sell.
- `includeWebsiteCrawl`: fetch candidate homepage/contact/about/careers pages and infer website signals. Defaults to on for provided-candidate and live-place runs.
- `includeContactExtraction`: extract public emails and social profile links from crawled pages. Defaults to on for provided-candidate and live-place runs.
- `includeUpstreamContactEnrichment`: off by default. Enables the Google Maps Scraper's paid `scrapeContacts` add-on during live discovery, which pulls business emails and social profiles from each place's website far more reliably than a static crawl. Adds a small upstream cost per place (see the discovery Actor's pricing tab).
- `maxWebsitePages`: page cap per candidate.
- `websiteTimeoutSecs`: per-page request timeout.

Advanced fields:

- `placesActorId`: Apify Store Actor used for live discovery. Defaults to the Google Maps Scraper (`compass/crawler-google-places`), which manages its own proxying internally. An alternative Actor must accept `searchStringsArray`/`locationQuery`-style input and return items shaped like the Google Maps Scraper's; the common aliases `name`, `rating`, `reviewCount`, and `category` are also accepted. Only the default Actor has been tested end to end.
- `parallelLocationDiscovery`: off by default. Starts the next location's discovery while the current one is being evaluated (one location of lookahead), cutting wall-clock time on multi-city runs. The trade-off: a run that stops early at a lead or charge limit has already paid for the prefetched location's discovery.
- `proxyConfiguration`: proxy used for this Actor's own website/contact enrichment fetches. Defaults to Apify Proxy. Does not apply to the discovery Actor call, which handles proxying itself.

#### Discovery modes

- **Demo candidates** — three fixed sample businesses, useful for verifying the pipeline without spend.
- **Provided candidates** — score business rows you already have (from your own list or an upstream discovery Actor).
- **Live places** — orchestrates the Google Maps Scraper (or another Actor you point `placesActorId` at) per vertical/location, normalizes results, deduplicates by website domain, phone, and address, then scores and enriches them. This is the mode to use for a self-contained, no-upstream-data-needed run.

### Output

Each dataset item includes business identity, source category evidence, contact fields, a Google Maps link, enrichment status, candidate source, score, quality tier, confidence score, score reasons, suggested offer, outreach copy, next best action, and source URLs.
The default key-value store also includes `RUN_SUMMARY`, a compact run receipt with evaluated, rejected, enriched, emitted, charged, and deduplicated counts plus proof-boundary notes. It also reports `verticalFilterActive` (whether the requested vertical gets cross-vertical noise filtering), `filteredBelowReviewMinimum` (candidates skipped by `minimumReviewCount` before billing), `filteredSuppressed` (repeat-run CRM exclusions skipped before billing), and `contactReadinessCounts` (how many emitted leads are emailReady, phoneReady, or manual-lookup).

Billing-safety guarantees: candidates are deduplicated across all requested locations, so a business serving two nearby cities is charged and emitted at most once per run. If the platform migrates or restarts a run, it resumes from a checkpoint instead of re-charging already-billed candidates (`RUN_SUMMARY.resumedFromCheckpoint` is set to `true`), and re-reads each location's already-paid discovery results instead of calling the discovery Actor again. If a lead is ever charged but fails to write to the dataset, it is recorded under the `CHARGE_DISCREPANCIES` key-value store record so it can be recovered or refunded, and counted in `RUN_SUMMARY.pushFailures`.

Outreach copy (`reasonToContact`, `suggestedSubject`, `suggestedMessage`) is built only from concrete, buyer-usable evidence — specific ratings, review counts, and website gaps — never from internal scoring labels, so it can be sent with light editing instead of a rewrite.

```json
{
  "businessName": "Metro Comfort Air",
  "vertical": "HVAC",
  "location": "Dallas, TX",
  "phone": "+1 214 555 0101",
  "website": "https://metro-comfort-air.invalid",
  "mapsUrl": "https://www.google.com/maps/place/Metro+Comfort+Air",
  "categoryName": "HVAC contractor",
  "categories": [
    "HVAC contractor",
    "Air conditioning contractor"
  ],
  "rating": 4.2,
  "reviewCount": 184,
  "websiteSignals": [
    "Public email found",
    "Social profile found"
  ],
  "enrichmentStatus": "enriched",
  "candidateSource": "livePlaces",
  "leadScore": 95,
  "leadQuality": "hot",
  "confidenceScore": 90,
  "contactReadiness": "emailReady",
  "evidenceSummary": [
    "Listed as HVAC contractor",
    "4.2-star rating across 184 reviews leaves reputation upside",
    "Website has no obvious booking or contact call to action",
    "Direct contact path available"
  ],
  "scoreReasons": [
    "High-value local service category",
    "Review volume exists but rating leaves reputation upside",
    "Website has no obvious booking or contact CTA"
  ],
  "reasonToContact": "Metro Comfort Air looks worth contacting: 4.2-star rating across 184 reviews leaves reputation upside; website has no obvious booking or contact call to action.",
  "suggestedOffer": "Local SEO and conversion audit",
  "suggestedSubject": "Quick Local SEO and conversion audit idea for Metro Comfort Air",
  "suggestedMessage": "Hi Metro Comfort Air, I was researching HVAC in Dallas, TX and yours stood out. I noticed your 4.2-star rating across 184 reviews has real room to climb, and your website doesn't give visitors an obvious way to book or request a quote. I help businesses like yours with Local SEO and conversion audit and think there may be a quick win here. Worth a short note with specifics?",
  "nextBestAction": "Send a short personalized email and follow up by phone within two business days.",
  "sourceUrls": [
    "https://maps.example.invalid/metro-comfort-air",
    "https://metro-comfort-air.invalid/contact"
  ]
}
```

### Scoring

The scoring model is deterministic and transparent:

- High-value local service category.
- Reputation gap from rating and review volume.
- Missing website or weak conversion CTA.
- Stale service or offer copy.
- Direct contact path.
- Hiring, growth, or multi-location signal.
- Competitive local density (computed from how many candidates were returned per location in Live places mode).
- Source evidence present.

Only candidates at or above `qualificationThreshold` with at least `minimumEvidenceSignals` score reasons are emitted. Candidates are deduplicated by website domain, normalized phone, and normalized address before scoring.
Credibility gate: the missing-website signal only counts when the listing has at least one public review, and rating-based signals require at least three reviews. Listings with no website, no reviews, and no rating — the shape of Google Maps lead-gen spam networks — do not reach the qualification threshold and are never charged as leads.
Live discovery also filters obvious vertical mismatches before scoring or candidate billing, using source category/name evidence. For example, a roofing/siding listing returned by a broad HVAC Maps query is counted in `RUN_SUMMARY.filteredOutOfVertical` instead of being charged or emitted.
Use `maxQualifiedLeads` to cap the final list size and spend. With `requireContactPath` enabled, candidates without any public route to contact or verify them are skipped before scoring.

### Pricing Direction

Monetization is pay-per-event. The primary event is `qualifiedLead`, priced at $50 per 1,000 qualified leads ($0.05 each). The Actor calls `Actor.charge({ eventName: 'qualifiedLead' })` before writing each emitted lead and stops cleanly when the user's max-charge limit prevents another paid lead.

For live discovery, configure a secondary `candidateEvaluated` event before publishing. The pilot economics currently support about $8 per 1,000 evaluated live candidates ($0.008 each) to cover upstream Google Maps discovery and wrapper runtime before qualified leads are known.

The Store setup also includes Apify's synthetic `apify-actor-start` and `apify-default-dataset-item` events at minimal prices. Platform usage costs are included, not passed through separately to users.

### Accuracy And Limits

- Live place discovery depends on a third-party Store Actor (the Google Maps Scraper by default); its coverage, freshness, and cost are separate from this Actor's own proof.
- Website enrichment uses static HTML fetches, not a full browser; script-rendered contact details will be missed.
- Some websites block crawlers or hide contact details behind scripts.
- Contact extraction filters common asset filenames and placeholder domains, but buyers should still spot-check email deliverability before outreach.
- Live place category data can be noisy, so category/name relevance checks are conservative guardrails rather than a guarantee that every retained business perfectly matches the buyer's target vertical.
- Scores explain likely outreach fit; they do not prove buyer intent or conversion.
- Live discovery runs pay upstream Store Actor costs before this Actor can decide which candidates qualify, so use `maxPlacesPerLocation` for bounded tests and review `RUN_SUMMARY` before scaling a new vertical/location mix.

### Support & Issues

Report problems through this Actor's **Issues** tab in Apify Console (or the Issues section of the Store listing). Include the run ID so the `RUN_SUMMARY` receipt and charge records can be checked against what you were billed. Issues are typically answered within one business day; anything that looks like a charged-but-undelivered lead is treated as a refund-priority bug — the Actor records those under `CHARGE_DISCREPANCIES` precisely so they can be verified and made right.

### Compliance Note

Use public or authorized business data, respect target-site terms, and contact businesses responsibly. This Actor is designed to prioritize legitimate B2B outreach, not to bypass privacy controls or spam recipients.

### Local Development

Install dependencies:

```bash
npm install
```

Run the demo:

```bash
apify run --purge
```

Run with provided candidates and website enrichment:

```bash
npm run run:provided
```

Run with live place discovery (requires `APIFY_TOKEN` and a funded account, since it calls a paid Store Actor):

```bash
apify run --purge --input-file examples/live-places-input.json
```

Validate release files and syntax:

```bash
npm run check
```

Validate sample output:

```bash
npm run validate:sample
```

Validate local run output:

```bash
npm run validate:local
```

### Proof Boundaries

- Local `apify run` proves Actor wiring and Console schema validity for the demo and provided-candidates modes.
- Harness validation proves output schema and lead-evidence minimums.
- Provided-candidate runs prove static website/contact enrichment against the supplied URLs.
- Live place discovery has been run end to end, both locally and as a real cloud `apify actors call`, against the actual Google Maps Scraper: discovery, dedupe, website enrichment, scoring, and dataset output all verified with real data.
- Pay-per-event pricing is configured in Console with `qualifiedLead` as the primary event. The code now checks `eventChargeLimitReached` and stops once the buyer's max-charge limit prevents another paid lead.
- Store publication, source freshness, manual spot-checks, and revenue remain separate proof layers that require an actual Store listing.

# Actor input Schema

## `discoveryMode` (type: `string`):

Live places discovers real businesses on Google Maps for the given vertical and locations (this is the mode for real lead runs). Provided candidates scores business rows you already have. Demo emits three clearly fake sample businesses (.invalid domains) so you can verify the pipeline and output shape without any discovery spend.

## `vertical` (type: `string`):

One local service vertical. Relevance-filtered verticals: HVAC, roofers, plumbers, dentists, lawyers, med spas, landscapers, pest control, electricians, garage door, cleaning, painters, locksmiths, movers, auto repair, veterinarians. Other verticals still work but without cross-vertical noise filtering.

## `locations` (type: `array`):

Cities, counties, metros, or states to evaluate.

## `maxPlacesPerLocation` (type: `integer`):

Hard cap for discovered or provided candidates per location. Live discovery and candidate evaluation are billed per place, so this cap bounds run cost.

## `qualificationThreshold` (type: `integer`):

Only emit leads at or above this score.

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

Stop after this many qualified leads are emitted, even if more candidates remain. Use this to control spend and list size.

## `minimumReviewCount` (type: `integer`):

Skip candidates with fewer than this many public reviews before any scoring or billing. 0 keeps every credibility-gated candidate; 3 or more gives a firmer evidence floor for volume outreach.

## `minimumEvidenceSignals` (type: `integer`):

Minimum number of score reasons required before a lead can be emitted.

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

Skip listings that look like national chains or franchises (known brand names, franchise unit markers like #1042, or the same name repeated 3+ times in one location's results). These are usually poor targets for SMB outreach. Filtered before any billing and counted in RUN\_SUMMARY.filteredChains.

## `suppressBusinessIdentifiers` (type: `array`):

Optional CRM suppression list for repeat runs. Add an exact website domain, business phone, public email, or business name for any company you already contacted or already manage. Matches are skipped before enrichment and billing; only the count is retained in RUN\_SUMMARY.

## `requireContactPath` (type: `boolean`):

Only emit or score candidates with at least one public contact path such as phone, email, website, or Maps URL.

## `offerType` (type: `string`):

Suggested offer buyers can pitch to emitted leads.

## `includeWebsiteCrawl` (type: `boolean`):

Fetch candidate websites and infer conversion, recency, and hiring signals. Defaults to on for provided-candidate and live-place runs; demo mode skips crawling its non-resolvable fixture domains.

## `includeContactExtraction` (type: `boolean`):

Extract public emails and social profile links from crawled candidate pages. Defaults to on for provided-candidate and live-place runs.

## `includeUpstreamContactEnrichment` (type: `boolean`):

Enable the Google Maps Scraper's paid contact add-on (scrapeContacts) during live discovery. It extracts business emails and social profile links from each place's website far more reliably than this Actor's static crawl, at a small extra upstream cost per place (see the discovery Actor's pricing). Only applies to Live places mode.

## `maxWebsitePages` (type: `integer`):

Crawl cap for homepage plus contact/about/careers pages found on the same website.

## `websiteTimeoutSecs` (type: `integer`):

Maximum time to wait for each website page request.

## `debugRejected` (type: `boolean`):

Include below-threshold candidates in logs only, not dataset output.

## `placesActorId` (type: `string`):

Apify Store Actor used for live place discovery when discovery mode is Live places. Defaults to the Google Maps Scraper. An alternative Actor must accept searchStringsArray/locationQuery-style input and return items shaped like the Google Maps Scraper's (title/name, website, phone, categoryName/category, totalScore/rating, reviewsCount/reviewCount, url).

## `parallelLocationDiscovery` (type: `boolean`):

Start the next location's discovery while the current location is still being evaluated (one location of lookahead). Cuts wall-clock time on multi-city runs. Off by default: if a run stops early at a lead or charge limit, the prefetched location's discovery has already been paid for.

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

Proxy used for this Actor's own website/contact enrichment requests. The discovery Actor manages its own proxying and does not receive this value.

## `candidates` (type: `array`):

Optional candidate rows to score when discovery mode is Provided candidates.

## Actor input object example

```json
{
  "discoveryMode": "livePlaces",
  "vertical": "HVAC",
  "locations": [
    "Dallas, TX"
  ],
  "maxPlacesPerLocation": 25,
  "qualificationThreshold": 65,
  "maxQualifiedLeads": 25,
  "minimumReviewCount": 0,
  "minimumEvidenceSignals": 2,
  "excludeChains": false,
  "suppressBusinessIdentifiers": [],
  "requireContactPath": true,
  "offerType": "Local SEO and conversion audit",
  "includeWebsiteCrawl": true,
  "includeContactExtraction": true,
  "includeUpstreamContactEnrichment": false,
  "maxWebsitePages": 2,
  "websiteTimeoutSecs": 10,
  "debugRejected": false,
  "placesActorId": "compass/crawler-google-places",
  "parallelLocationDiscovery": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "candidates": []
}
```

# Actor output Schema

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

Ranked local service businesses with outreach score, evidence, contact fields, and suggested offer.

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

Compact summary of evaluated, rejected, enriched, emitted, charged, and deduplicated leads plus proof-boundary notes.

# 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 = {
    "discoveryMode": "livePlaces",
    "vertical": "HVAC",
    "locations": [
        "Dallas, TX"
    ],
    "maxPlacesPerLocation": 25,
    "maxQualifiedLeads": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("rasputinkaiser/local-service-lead-qualifier").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 = {
    "discoveryMode": "livePlaces",
    "vertical": "HVAC",
    "locations": ["Dallas, TX"],
    "maxPlacesPerLocation": 25,
    "maxQualifiedLeads": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("rasputinkaiser/local-service-lead-qualifier").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "discoveryMode": "livePlaces",
  "vertical": "HVAC",
  "locations": [
    "Dallas, TX"
  ],
  "maxPlacesPerLocation": 25,
  "maxQualifiedLeads": 25
}' |
apify call rasputinkaiser/local-service-lead-qualifier --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=rasputinkaiser/local-service-lead-qualifier",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Local Service Lead Qualifier",
        "description": "Rank local service businesses by outreach-readiness and return evidence-backed qualified leads.",
        "version": "0.5",
        "x-build-id": "DFfTOQuDEPjuTrxjb"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/rasputinkaiser~local-service-lead-qualifier/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-rasputinkaiser-local-service-lead-qualifier",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/rasputinkaiser~local-service-lead-qualifier/runs": {
            "post": {
                "operationId": "runs-sync-rasputinkaiser-local-service-lead-qualifier",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/rasputinkaiser~local-service-lead-qualifier/run-sync": {
            "post": {
                "operationId": "run-sync-rasputinkaiser-local-service-lead-qualifier",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "vertical",
                    "locations"
                ],
                "properties": {
                    "discoveryMode": {
                        "title": "Discovery mode",
                        "enum": [
                            "demo",
                            "providedCandidates",
                            "livePlaces"
                        ],
                        "type": "string",
                        "description": "Live places discovers real businesses on Google Maps for the given vertical and locations (this is the mode for real lead runs). Provided candidates scores business rows you already have. Demo emits three clearly fake sample businesses (.invalid domains) so you can verify the pipeline and output shape without any discovery spend.",
                        "default": "demo"
                    },
                    "vertical": {
                        "title": "Vertical",
                        "type": "string",
                        "description": "One local service vertical. Relevance-filtered verticals: HVAC, roofers, plumbers, dentists, lawyers, med spas, landscapers, pest control, electricians, garage door, cleaning, painters, locksmiths, movers, auto repair, veterinarians. Other verticals still work but without cross-vertical noise filtering.",
                        "default": "HVAC"
                    },
                    "locations": {
                        "title": "Locations",
                        "type": "array",
                        "description": "Cities, counties, metros, or states to evaluate.",
                        "items": {
                            "type": "string"
                        },
                        "default": [
                            "Dallas, TX"
                        ]
                    },
                    "maxPlacesPerLocation": {
                        "title": "Maximum places per location",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Hard cap for discovered or provided candidates per location. Live discovery and candidate evaluation are billed per place, so this cap bounds run cost.",
                        "default": 50
                    },
                    "qualificationThreshold": {
                        "title": "Qualification threshold",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Only emit leads at or above this score.",
                        "default": 65
                    },
                    "maxQualifiedLeads": {
                        "title": "Maximum qualified leads",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Stop after this many qualified leads are emitted, even if more candidates remain. Use this to control spend and list size.",
                        "default": 100
                    },
                    "minimumReviewCount": {
                        "title": "Minimum review count",
                        "minimum": 0,
                        "maximum": 100000,
                        "type": "integer",
                        "description": "Skip candidates with fewer than this many public reviews before any scoring or billing. 0 keeps every credibility-gated candidate; 3 or more gives a firmer evidence floor for volume outreach.",
                        "default": 0
                    },
                    "minimumEvidenceSignals": {
                        "title": "Minimum evidence signals",
                        "minimum": 2,
                        "maximum": 8,
                        "type": "integer",
                        "description": "Minimum number of score reasons required before a lead can be emitted.",
                        "default": 2
                    },
                    "excludeChains": {
                        "title": "Exclude chains and franchises",
                        "type": "boolean",
                        "description": "Skip listings that look like national chains or franchises (known brand names, franchise unit markers like #1042, or the same name repeated 3+ times in one location's results). These are usually poor targets for SMB outreach. Filtered before any billing and counted in RUN_SUMMARY.filteredChains.",
                        "default": false
                    },
                    "suppressBusinessIdentifiers": {
                        "title": "Businesses to skip",
                        "type": "array",
                        "description": "Optional CRM suppression list for repeat runs. Add an exact website domain, business phone, public email, or business name for any company you already contacted or already manage. Matches are skipped before enrichment and billing; only the count is retained in RUN_SUMMARY.",
                        "items": {
                            "type": "string"
                        },
                        "default": []
                    },
                    "requireContactPath": {
                        "title": "Require contact path",
                        "type": "boolean",
                        "description": "Only emit or score candidates with at least one public contact path such as phone, email, website, or Maps URL.",
                        "default": true
                    },
                    "offerType": {
                        "title": "Offer type",
                        "type": "string",
                        "description": "Suggested offer buyers can pitch to emitted leads.",
                        "default": "Local SEO and conversion audit"
                    },
                    "includeWebsiteCrawl": {
                        "title": "Include website crawl",
                        "type": "boolean",
                        "description": "Fetch candidate websites and infer conversion, recency, and hiring signals. Defaults to on for provided-candidate and live-place runs; demo mode skips crawling its non-resolvable fixture domains.",
                        "default": true
                    },
                    "includeContactExtraction": {
                        "title": "Include contact extraction",
                        "type": "boolean",
                        "description": "Extract public emails and social profile links from crawled candidate pages. Defaults to on for provided-candidate and live-place runs.",
                        "default": true
                    },
                    "includeUpstreamContactEnrichment": {
                        "title": "Include upstream contact enrichment",
                        "type": "boolean",
                        "description": "Enable the Google Maps Scraper's paid contact add-on (scrapeContacts) during live discovery. It extracts business emails and social profile links from each place's website far more reliably than this Actor's static crawl, at a small extra upstream cost per place (see the discovery Actor's pricing). Only applies to Live places mode.",
                        "default": false
                    },
                    "maxWebsitePages": {
                        "title": "Maximum website pages per candidate",
                        "minimum": 1,
                        "maximum": 5,
                        "type": "integer",
                        "description": "Crawl cap for homepage plus contact/about/careers pages found on the same website.",
                        "default": 2
                    },
                    "websiteTimeoutSecs": {
                        "title": "Website timeout seconds",
                        "minimum": 3,
                        "maximum": 30,
                        "type": "integer",
                        "description": "Maximum time to wait for each website page request.",
                        "default": 10
                    },
                    "debugRejected": {
                        "title": "Debug rejected leads",
                        "type": "boolean",
                        "description": "Include below-threshold candidates in logs only, not dataset output.",
                        "default": false
                    },
                    "placesActorId": {
                        "title": "Place discovery Actor",
                        "type": "string",
                        "description": "Apify Store Actor used for live place discovery when discovery mode is Live places. Defaults to the Google Maps Scraper. An alternative Actor must accept searchStringsArray/locationQuery-style input and return items shaped like the Google Maps Scraper's (title/name, website, phone, categoryName/category, totalScore/rating, reviewsCount/reviewCount, url).",
                        "default": "compass/crawler-google-places"
                    },
                    "parallelLocationDiscovery": {
                        "title": "Parallel location discovery",
                        "type": "boolean",
                        "description": "Start the next location's discovery while the current location is still being evaluated (one location of lookahead). Cuts wall-clock time on multi-city runs. Off by default: if a run stops early at a lead or charge limit, the prefetched location's discovery has already been paid for.",
                        "default": false
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Proxy used for this Actor's own website/contact enrichment requests. The discovery Actor manages its own proxying and does not receive this value.",
                        "default": {
                            "useApifyProxy": true
                        }
                    },
                    "candidates": {
                        "title": "Provided candidates",
                        "type": "array",
                        "description": "Optional candidate rows to score when discovery mode is Provided candidates.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "businessName": {
                                    "title": "Business name",
                                    "description": "Business display name.",
                                    "type": "string"
                                },
                                "location": {
                                    "title": "Location",
                                    "description": "Candidate location, such as Dallas, TX.",
                                    "type": "string"
                                },
                                "address": {
                                    "title": "Address",
                                    "description": "Street address or formatted business address.",
                                    "type": "string"
                                },
                                "phone": {
                                    "title": "Phone",
                                    "description": "Public business phone number.",
                                    "type": "string"
                                },
                                "website": {
                                    "title": "Website",
                                    "description": "Business website URL.",
                                    "type": "string"
                                },
                                "categoryName": {
                                    "title": "Primary category",
                                    "description": "Primary business category from the source listing, when available.",
                                    "type": "string"
                                },
                                "categories": {
                                    "title": "Categories",
                                    "description": "Business categories from the source listing, when available.",
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                },
                                "emails": {
                                    "title": "Emails",
                                    "description": "Public business email addresses.",
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                },
                                "socialProfiles": {
                                    "title": "Social profiles",
                                    "description": "Public social profile URLs.",
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                },
                                "rating": {
                                    "title": "Rating",
                                    "description": "Public review rating.",
                                    "type": "number"
                                },
                                "reviewCount": {
                                    "title": "Review count",
                                    "description": "Number of public reviews.",
                                    "type": "integer"
                                },
                                "hasBookingCta": {
                                    "title": "Has booking CTA",
                                    "description": "Whether the website has an obvious booking or contact call to action.",
                                    "type": "boolean"
                                },
                                "hasRecentCopy": {
                                    "title": "Has recent copy",
                                    "description": "Whether the website appears to have recent service, offer, or promotion copy.",
                                    "type": "boolean"
                                },
                                "hasHiringSignal": {
                                    "title": "Has hiring signal",
                                    "description": "Whether candidate evidence suggests hiring or growth.",
                                    "type": "boolean"
                                },
                                "hasMultipleLocations": {
                                    "title": "Has multiple locations",
                                    "description": "Whether the business appears to operate multiple locations.",
                                    "type": "boolean"
                                },
                                "competitiveDensity": {
                                    "title": "Competitive density",
                                    "description": "Estimated local competitive density for the vertical.",
                                    "type": "string",
                                    "enum": [
                                        "low",
                                        "medium",
                                        "high"
                                    ]
                                },
                                "sourceUrls": {
                                    "title": "Source URLs",
                                    "description": "Evidence URLs used to score this candidate.",
                                    "type": "array",
                                    "items": {
                                        "type": "string"
                                    }
                                },
                                "notes": {
                                    "title": "Notes",
                                    "description": "Optional caller-provided context for this candidate.",
                                    "type": "string"
                                }
                            }
                        },
                        "default": []
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
