# Google Maps Website Leads - Find & Rank Sales Opportunities (`northpeak_data/google-maps-website-gap-leads`) Actor

Find and qualify Google Maps sales leads across multiple cities and business categories. Detect missing or weak websites, remove duplicate prospects, rank opportunities, and get tailored service offers, sales angles, and personalized outreach recommendations. No dataset required.

- **URL**: https://apify.com/northpeak_data/google-maps-website-gap-leads.md
- **Developed by:** [Northpeak Data](https://apify.com/northpeak_data) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 qualified website-gap leads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Maps Website Leads - Evidence-screened sales opportunities

Find local businesses, audit website gaps and keep only material opportunities that match your service and have a usable direct contact channel. A missing meta description is an audit finding, not enough evidence to sell a redesign. A healthy or inconclusive website is never a qualified opportunity.

### Two outputs with different purposes

| Output | Contents | Qualified-lead event |
| --- | --- | --- |
| **Qualified real opportunities** (default dataset) | Real records passing every qualification gate | Charged per returned qualified row |
| **All audited businesses** (`audits` dataset) | Every reviewed business, including rejected and fictional preview rows, with reasons | No qualified-lead charge for audit-only rows |
| **SAMPLE_RESULTS** | Fictional preview only | No qualified-lead charge |

The datasets are physically separate. Setting the legacy `onlyOpportunities: false` no longer mixes healthy/weak rows into the default dataset. The synchronous dataset API and run Export button return qualified rows only. Use the `auditedBusinesses` output URL, or the run's Storage dataset dropdown, to retrieve all audits. `SUMMARY` includes the audit dataset ID, counts and rejection reasons. Each dataset follows the run's retention policy.

**Compatibility change:** qualification version 2.0 can legitimately return an empty default dataset even when many businesses were reviewed. Healthy businesses, metadata-only findings, records without a usable phone/email and service mismatches remain available in `audits`. Fictional preview rows are also kept out of the qualified default dataset.

### Your first result

1. Preview with `{"runSample":true}`. Inspect **Fictional preview only** or **All audited businesses**. Rows have `sampleMode:true`, `qualificationStatus:SAMPLE_PREVIEW` and `isQualifiedLead:false`. The default dataset stays empty. No websites or upstream Actors are called; platform/Actor-start charges may apply.
2. Provide existing business JSON or an Apify `datasetId`. Common Google Maps fields such as `title`, `categoryName`, `city`, `phone`, `website`, `placeId` and `url` are mapped automatically. Explicit `businesses: []` produces no demo substitution. A run with limited permissions may not be allowed to read a dataset from another run, even in the same account. In that case, export that source as JSON, paste the array into `businesses` and remove `datasetId`. The Actor never expands account permissions or silently starts paid discovery. A denied source produces `INPUT_ERROR` with code `SOURCE_DATASET_ACCESS_DENIED`; an unavailable/not-found source produces `SOURCE_DATASET_UNAVAILABLE`. Neither produces lead output or fallback discovery.
3. Choose the service you actually sell and review the qualified output plus reasons. The tool never verifies purchase intent, sales readiness or willingness to buy.

Copyable fictional customer-data example (normal mode, may incur qualified-lead charges):

```json
{
  "businesses": [{"name":"FICTIONAL Plumbing Example","category":"Plumber","city":"Austin","phone":"+1 512 555 0199","website":""}],
  "serviceType":"website_build",
  "minOpportunityStrength":"strong",
  "minOpportunityScore":65,
  "requiredContactChannel":"phone"
}
```

### Qualification is a set of gates, not just a score

All gates must pass: a material observed gap, sufficient strength, selected-service match, syntactically usable required phone/email, score floor and any selected category/location targeting. Reputation and contact data affect ranking but cannot make a healthy or metadata-only website qualify.

| `serviceType` | Relevant evidence |
| --- | --- |
| `website_build` | Parked domain or observed website error |
| `website_repair` | Parked/error website, HTTPS or viewport problems; strength rules still apply |
| `conversion_optimization` | Missing CTA/contact path or viewport issues; strength rules still apply |
| `seo` | Combined HTTPS and viewport issues; metadata/copyright alone never qualifies. Search rankings are not audited. |

`strong` (default) requires a parked/error website or at least two material technical/conversion signals. A single material signal is `moderate` and requires explicit opt-in. Metadata and copyright alone remain `weak` and are always excluded. The score floor defaults to **65**, after the hard gates. Lowering it never overrides those gates.

`requiredContactChannel` accepts `any` (phone or email), `phone` or `email`. A website URL or Google Maps link alone is insufficient. Contact checks assess format, not ownership, working numbers, consent or email deliverability. Select `serviceType` explicitly to control eligibility. Recommendations use the observed evidence and the selected service. The legacy `serviceOffer` field is accepted for input compatibility.

These are conservative, provisional screening rules, not thresholds calibrated against closed sales. See `validation/QUALIFICATION_REVIEW.md` for the test design, public sample, limitations and results. Do not interpret an opportunity score as a probability of purchase.

### What every audit explains

- `isQualifiedLead`, `qualificationStatus` and `qualificationVersion`
- `opportunityStrength`, `serviceMatches` and `selectedService`
- `qualificationReasons` or `rejectionReasons`, plus effective thresholds
- `availableContactChannels` and `contactVerification`
- `websiteStatus`, `auditSignals`, `auditConfidence` and evidence reasons
- Separate gap, fit, contact and overall scores
- `purchaseIntentVerified:false` on every row

Audit-only rows have `outreachPlaybook.nextStep:DO_NOT_CONTACT` and no fabricated personalized pitch. Qualified opportunities still require human verification before outreach. Missing website fields and social-only listing URLs remain audit-only with `WEBSITE_ABSENCE_UNVERIFIED`. Only an HTTP 404/410 at the listed URL is classified as an observed missing page; verify the current homepage before proposing repair. Other HTTP errors, empty responses, non-HTML responses and request failures are inconclusive, never proven broken websites. `auditEvidence` records the requested/final URL, response status, content type and HTTP-audit scope when a response was received. JavaScript-rendered content can be missed by an HTTP audit.

### Direct search and pricing

```json
{"searchCategory":"plumbers","searchCity":"Austin, Texas, USA","maxBusinesses":5,"maxSearchCombinations":1,"serviceType":"website_build"}
```

Direct search calls `compass/crawler-google-places` using your account. **The upstream provider bills separately**, in addition to this Actor's qualified-lead event charges and any platform/start charges. Review [the source pricing](https://apify.com/compass/crawler-google-places/pricing). No fixed combined total is promised. The parent's run cost limit does not cap separately billed upstream runs. `maxUpstreamCostUsd` forwards a separate limit to each pay-per-event upstream run (default $1 per search combination). Set it explicitly and check the provider minimum. In the verified 2026-11-04 acceptance attempt, Compass required a cap of at least $0.50 and rejected $0.15 before starting a child run. The Actor checks active provider metadata first and returns `INPUT_ERROR` code `UPSTREAM_BUDGET_BELOW_MINIMUM` when its minimum exceeds your cap. It never raises the limit or retries without your changed input. A cap is potential spending authority, not the expected bill. This is per search, not an aggregate across combinations; other pricing models may not enforce it. `UPSTREAM_RUNS` and `SUMMARY.upstreamRuns` record returned child run IDs and account-reported usage/event counts without estimating missing values.

Multi-city/category search supports `searchCategories`, `searchQueries` and `searchCities`. Each combination starts an upstream search; `maxSearchCombinations` caps combinations (default 12, maximum 25). Duplicates are removed by Place ID, with conservative name-and-address/phone fallback, before audit. Different branches remain separate. Duplicate tracking does not persist across runs.

The current qualified-lead event price is $2 per 1,000 events ($0.002 each). The account API also exposes a default-dataset item event of $0.00001 per returned row, making the two per-row events $2.01 per 1,000 qualified rows before Actor Start ($0.00005 per GB, minimum one event), platform/timed storage and separate upstream charges. Check the Store Pricing tab and run API for current pricing. Owner runs may have different accounted charges; do not interpret QA event counts as customer revenue. Exactly the same `isQualifiedLead` decision controls default output and the qualified-lead event. No event is charged for healthy, inconclusive, weak, rejected, or fictional rows. Upstream collection can still cost money even when zero leads qualify.

### API and automation

```bash
curl -X POST 'https://api.apify.com/v2/acts/northpeak_data~google-maps-website-gap-leads/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @input.json
```

This synchronous endpoint returns qualified rows only. For all audits, start an asynchronous `/runs` request, wait for completion and read the run's `output.auditedBusinesses` link. Store tokens as secrets. Use API/Schedules/Webhooks/Make/n8n/CRM integrations on qualified output; use `placeId` and your own state to avoid repeated outreach. No contacts are sent messages by this Actor.

Example files in `examples/` are fictional. The automated tests assert separate dataset routing and matching billing, including the observed AAA AUGER metadata-only and Radiant healthy regression cases. A successful test is not evidence of a paying customer or improved sales conversion.

# Actor input Schema

## `searchCategory` (type: `string`):

Direct search: e.g. plumbers. Enter with Search city. Upstream Google Maps Actor usage incurs separate charges.

## `searchCity` (type: `string`):

Direct search: e.g. Austin, Texas, USA.

## `searchQueries` (type: `array`):

Optional additional business categories/terms; combine with one or more cities. Each combination may incur upstream charges.

## `mapsActorId` (type: `string`):

Optional upstream Apify Actor ID. Default compass/crawler-google-places. Its charges are separate.

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

Optional Apify dataset ID from a Google Maps scraper or another business-data Actor.

## `businesses` (type: `array`):

Paste businesses directly. Common Google Maps field names are mapped automatically.

## `serviceOffer` (type: `string`):

Accepted for compatibility. Select the service type to control eligibility. Commercial recommendations are based on observed evidence.

## `idealCustomer` (type: `string`):

Optional. Describe the businesses you want to target, for example: Plumbers, electricians and HVAC companies with an established local reputation.

## `targetCategories` (type: `array`):

Optional categories that should receive a stronger fit score, for example Plumber, Dentist or HVAC contractor.

## `targetLocations` (type: `array`):

Optional cities, neighborhoods or location terms that should receive a stronger fit score.

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

Optional qualification preference. Businesses meeting this rating receive a stronger fit signal.

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

Optional qualification preference. Established businesses meeting this review count receive a stronger fit signal.

## `auditWebsites` (type: `boolean`):

Fetch each public business website and detect visible technical and conversion gaps.

## `onlyOpportunities` (type: `boolean`):

Deprecated compatibility field. The default dataset always contains qualified real opportunities only, even when false. All reviewed businesses are stored separately in the audits dataset.

## `minOpportunityScore` (type: `integer`):

Ranking floor after material-gap, selected-service and contact-channel gates. Lowering this never qualifies healthy, inconclusive or metadata-only audits.

## `maxBusinesses` (type: `integer`):

Maximum number of business records to analyze in one run.

## `requestTimeoutSecs` (type: `integer`):

Maximum time to wait for each public website request.

## `searchCategories` (type: `array`):

Optional: search several business categories in one run. Each category/city combination invokes the upstream Actor and may incur separate charges.

## `searchCities` (type: `array`):

Optional: search multiple cities in one run. Duplicate businesses across searches are removed.

## `maxSearchCombinations` (type: `integer`):

Safety cap on paid upstream searches. Up to 25 combinations; each combination may incur upstream charges.

## `runSample` (type: `boolean`):

Fictional examples go to audits and SAMPLE_RESULTS only. Default qualified-lead dataset remains empty; no qualified-lead event charges. No upstream search or website requests. Actor/platform charges may apply.

## `serviceType` (type: `string`):

Hard service-fit gate. Metadata/copyright alone does not qualify even for SEO; this audit does not verify search rankings.

## `minOpportunityStrength` (type: `string`):

Strong requires a parked or broken website, or multiple material technical/conversion gaps matching the selected service. A metadata-only gap is always weak and excluded.

## `requiredContactChannel` (type: `string`):

Require a syntactically usable phone or email. A website or Maps link alone is not a direct contact channel. Availability does not verify ownership or deliverability.

## `maxUpstreamCostUsd` (type: `number`):

Separate spending limit forwarded to each upstream pay-per-event run. Default $1 per category/city combination. Multiple searches each have this limit; parent run cap is separate. Non-pay-per-event providers may not enforce it.

## Actor input object example

```json
{
  "businesses": [],
  "targetCategories": [],
  "targetLocations": [],
  "minRating": 0,
  "minReviews": 0,
  "auditWebsites": true,
  "onlyOpportunities": true,
  "minOpportunityScore": 65,
  "maxBusinesses": 500,
  "requestTimeoutSecs": 10,
  "maxSearchCombinations": 12,
  "runSample": false,
  "serviceType": "website_build",
  "minOpportunityStrength": "strong",
  "requiredContactChannel": "any",
  "maxUpstreamCostUsd": 1
}
```

# Actor output Schema

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

No description

## `auditedBusinesses` (type: `string`):

No description

## `sampleResults` (type: `string`):

No description

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

No description

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

No description

## `inputError` (type: `string`):

No description

## `upstreamRuns` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("northpeak_data/google-maps-website-gap-leads").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("northpeak_data/google-maps-website-gap-leads").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call northpeak_data/google-maps-website-gap-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,northpeak_data/google-maps-website-gap-leads"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/fPAulPgp8c8ee9Q6j/builds/7waoPCIqGMdAsYkQq/openapi.json
