# Google Maps Lead Quality & Duplicate Audit (`8tp/google-maps-lead-quality-audit`) Actor

Turn supplied Google Maps place and business-leads datasets into CRM-readiness, email-evidence, closed-business, and duplicate reports. Support current Compass enrichment fields; preserve only transparent source signals and never scrape, verify mailboxes, merge records, or invent a universal score.

- **URL**: https://apify.com/8tp/google-maps-lead-quality-audit.md
- **Developed by:** [Hunter M.](https://apify.com/8tp) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 place lead scoreds

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 web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

![Google Maps lead quality audit artwork](assets/actor-art.png)

Turn an existing Google Maps business or business-leads enrichment dataset into an explicit CRM-readiness report and a bounded duplicate-review queue. This Actor measures the fields you choose, flags closed businesses, explains every duplicate signal, and preserves caller-supplied email-verification classifications as labeled evidence. It does **not** scrape Google Maps, enrich leads, merge records, verify deliverability, or invent an opaque “AI lead score.”

> **Unofficial Actor.** This Actor is unofficial and is not affiliated with, endorsed by, or sponsored by Google LLC. It does not access Google services; it audits only user-supplied data that the user is authorized to process.

### Why use this Actor?

Google Maps extractors can produce useful local-business leads, but a large export is not automatically ready for a CRM or agent workflow. Rows may lack a website, phone, syntax-valid email, address, rating, or stable place ID. The same business may also appear more than once because searches overlap, exports were combined, or formatting differs.

Google Maps Lead Quality & Duplicate Audit is the deterministic quality-control step between collection and an authorized CRM workflow. It works with a dataset you are authorized to process, makes no network requests, and writes a portfolio summary, ranked per-row scorecards, transparent duplicate candidates, and run metadata to the default Apify dataset.

Typical uses include:

- auditing a Google Maps lead list before CRM import;
- comparing contact-field coverage across extractor runs;
- identifying records that need enrichment without pretending enrichment happened;
- reviewing duplicate places before a human-approved merge;
- excluding permanently or temporarily closed businesses from ready counts;
- giving an AI agent a structured, confidence-labeled review queue.

### Supported Google Maps dataset shapes

The `auto` contract recognizes common fields documented by Compass Google Maps Extractor and Crawler, Compass Business Leads Enrichment, Lukaskrivka Google Maps with Contact Details, Santamaria Google Maps Scraper, ScrapAPI Google Maps Scraper, and flat Google Maps lead exports. Recognized place data includes `placeId`, `place_id`, `cid`, `data_id`, business names, full or component addresses, coordinates, websites, phone and email arrays, categories, ratings, review counts, social URLs, and closed flags.

For current Compass business-leads enrichment rows, v0.2 recognizes `companyId`, `companyName`, `companyWebsite`, company location/phone/social fields, company industry, `email`, and the nested `emailVerification` result. It deliberately ignores `personId`, personal names, job titles, photos, and profile identities. The source row is labeled `business_lead_enrichment`, while ordinary place rows are labeled `place`.

Choose `custom` or add individual `field_map` paths when your source uses another shape. A declared source contract is recorded for reproducibility; it is not authenticated provenance, and upstream duplicate flags are not accepted as this Actor's own evidence.

### Input

Choose one effective source:

- `source_dataset_id`: an Apify dataset selected with read-only permission; or
- `leads`: up to 10,000 inline business objects for testing.

Then choose `required_fields`. The default readiness contract requires name, address, website, phone, and email. You can instead require any nonempty combination of name, address, website, phone, email, categories, coordinates, rating, and review count.

```json
{
  "source_dataset_id": "YOUR_DATASET_ID",
  "source_contract": "auto",
  "required_fields": ["name", "address", "website", "phone", "email"],
  "max_rows": 25000,
  "max_source_bytes": 16777216,
  "max_source_row_bytes": 1048576,
  "max_scorecard_rows": 10000,
  "max_duplicate_pairs": 5000,
  "include_business_names": true,
  "include_contact_values": false
}
```

`include_contact_values` is off by default for data minimization. When enabled, the output can copy normalized website domains, phones, syntax-valid emails, and supplied full addresses. City, state/region, postal code, country, and coordinates remain explicit scorecard fields in either mode so location coverage can be audited; omit or map those source fields before the run if they must not appear. The source-row number remains available for joining an audit result back to the original dataset. Stable IDs up to 500 characters are included; longer opaque IDs are represented only by their complete-value SHA-256 fingerprint.

`max_rows`, `max_source_bytes`, and `max_source_row_bytes` apply identically to selected-Dataset and inline sources. Selected Datasets use bounded pages plus one extra-row probe. The `run_meta` record reports retained canonical JSON bytes, transport completeness, exact cap flags, an oversized-row count, and closed truncation reasons. A row that would cross a byte limit is withheld rather than partially parsed.

### What “ready” means

Readiness is deliberately narrow. For each row, the Actor divides the number of selected fields that are structurally present by the number of selected required fields. There are no hidden weights.

- `ready`: all selected fields are present and structurally usable;
- `needs_enrichment`: one or more selected fields are absent or unusable;
- `review_duplicate`: the row participates in a retained duplicate candidate;
- `review_email_evidence`: a supplied upstream email classification is invalid, disposable, inconclusive, errored, or unrecognized;
- `excluded_closed`: a permanent or temporary closed signal is present;
- `invalid`: the row lacks a stable ID and enough business signals to identify it.

A syntax-valid email is **not** a delivered, owned, safe, or reachable mailbox. A normalized phone is **not** a reachable line. A ready row is **not** a qualified buyer and does not imply consent or sales intent. Without upstream verification evidence, email readiness remains syntax-only.

### Google Maps leads and email verification evidence

When an input row contains Compass's documented `emailVerification.result`, the Actor copies only these allowlisted labels: `ok`, `invalid`, `disposable`, `catch_all`, `unknown`, and `error`. Any other nonempty result or malformed verification object becomes `unrecognized`. Optional upstream `free` and `role` booleans are retained; other verification metadata is discarded.

These fields are observations from the caller-supplied source row. This Actor performs no DNS, MX, SMTP, mailbox, or ownership check and cannot confirm that the classification is current. Only `ok` can remain structurally ready; every other supplied result becomes `review_email_evidence` unless a higher-priority invalid, closed, or duplicate status applies. Summary records count each observed classification so agents can route inconclusive and negative evidence explicitly.

### Duplicate evidence

The Actor never merges or removes rows. It emits one record per candidate pair and explains the exact evidence.

Reliable evidence is limited to the same **exact, case-sensitive, complete** stable identifier in the same namespace, such as an exact `placeId` match. Opaque IDs are never case-folded or truncated for comparison. IDs up to 500 characters are displayed verbatim; longer IDs are withheld from display and represented by a SHA-256 fingerprint computed over the full value. Potential evidence uses exact normalized combinations: domain plus any observed phone, any observed phone plus address, domain plus address, or business name plus address. All retained normalized phones (up to 20 per source row) participate; evidence is not limited to the first phone.

Phone normalization is intentionally conservative. A leading calling code `1` is removed only when an observed or custom-mapped country field explicitly identifies the United States or Canada. With missing or other country evidence, the full digit sequence is retained, preventing an unknown international number from being silently treated as NANP. This is structural duplicate evidence, not E.164 validation or proof that a line belongs to the business.

Different stable IDs always remain a conflict, even when composite signals match. The recommended action says to keep those rows separate until the IDs and business facts are reviewed. Oversized signal buckets are skipped, and pair output is capped deterministically, preventing a placeholder value from creating an unbounded comparison explosion.

### Output

Records appear in a stable delivery order:

1. accepted `lead_scorecard` rows (`readiness_status` other than `invalid`) as one exact leading prefix, ranked for review;
2. one free `portfolio_summary` recomputed over that delivered prefix plus free invalid rows;
3. free `lead_scorecard` rows whose status is `invalid`;
4. free `duplicate_candidate` evidence only when both source rows occur in the delivered cohort, reliable before potential;
5. one free `run_meta` source, cap, billing, privacy, and restart receipt.

The same order is used under FREE pricing. `source_rows_read` remains a receipt for the bounded source read, while readiness, field-coverage, email-evidence, duplicate, and status metrics describe only scorecards actually delivered. `paid_scorecards_planned`, `paid_scorecards_delivered`, `paid_scorecards_charged`, `paid_scorecards_omitted`, `spending_limit_reached`, and `delivery_plan_fingerprint` make this boundary machine-readable. Paid scorecards do not carry run-wide warning counts that could disclose an omitted cohort.

```json
{
  "record_kind": "duplicate_candidate",
  "coverage_scope": "observed_business_rows_only",
  "source_row_number": 18,
  "other_source_row_number": 204,
  "duplicate_evidence_level": "potential",
  "duplicate_signals": ["domain_phone", "name_address"],
  "stable_id_conflict": true,
  "recommended_action": "Keep separate until the conflicting stable place IDs and composite evidence are reviewed."
}
```

Every metric describes only the supplied, bounded rows. Read `source_transport_complete` and `source_truncation_reasons` before treating the analyzed rows as the complete requested source. Truncation and skipped comparison buckets are written as both machine-readable receipt fields and warnings instead of being silently treated as complete coverage.

### Cost and agent use

The Actor does no scraping, proxying, DNS, SMTP, browser automation, or upstream Actor calls. It uses no LLM: runtime is dominated by reading, normalization, bounded exact-signal indexing, and dataset writes. Duplicate comparison is bucket-capped before pairs are created.

The Store uses PAY\_PER\_EVENT pricing: **$0.0005 per `place-lead-scored` event**. One event corresponds only to one accepted, non-`invalid` scorecard confirmed in the leading Dataset prefix. The platform-generated `apify-actor-start` event is $0.00005, and there is no `apify-default-dataset-item` event, so summaries, invalid rows, duplicate evidence, and the receipt have no custom per-record event charge. Apify currently bills platform compute, storage operations, and transfer separately to the user (`platformUsageBillingModel=USER`); those usage charges are additional to the custom events.

A $0.001 maximum-total-charge canary is sufficient for one custom scorecard event on the small default input: after the $0.00005 start, the Actor delivers exactly one accepted scorecard and then free evidence for that prefix. It is **not** a safe cap for a large source because user-billed Dataset reads, KVS writes, compute, and transfer also consume budget. Exact 1,000- and 10,000-row selected-Dataset canaries used about $0.0011 and $0.0049 in platform usage; a 25,000-row transform-only run used about $0.0104. Allow headroom above expected platform usage plus the custom-event budget, or lower `max_rows` and `max_scorecard_rows`. A cap that is exhausted by platform usage can abort the run even after the Actor has committed its terminal receipt.

Within the remaining custom-event capacity, the Actor produces a strict affordable prefix. It saves and reads back each new prefix before making direct idempotent custom-event charges in batches of at most 25 units. Each batch advances one deterministic charged prefix and is confirmed from live run state before the next batch begins. It fails closed if live charge counts, charging-manager counts, Dataset contents, run identity, or event configuration disagree.

Before source analysis, the complete derived result is committed to chunked immutable records in the run's default key-value store. A restart reuses that plan instead of rereading a changed source. If a failure occurs between Dataset storage and charge confirmation, the Actor reconciles stored rows, live `place-lead-scored` counts, the spending limit, and a deterministic idempotency key before appending free evidence. Once free evidence begins, the paid prefix is final and a restart cannot insert additional paid rows ahead of it.

For agent workflows, locate the single `portfolio_summary` by `record_kind` rather than assuming it is the first row. Treat `review_duplicate`, `review_email_evidence`, `excluded_closed`, and `invalid` as human-review gates. Use `missing_required_fields` to plan an authorized enrichment step. Never interpret `potential` as a merge instruction, treat upstream verification as this Actor's own check, or treat field presence as consent to contact.

### Privacy, safety, and limitations

Use only business data you are authorized to process and follow applicable platform rules, privacy law, marketing law, and your organization's retention policy. This Actor intentionally ignores personal lead names, identifiers, job titles, photos, and profile identities and does not determine whether a contact value belongs to a person or a business. Contact values are omitted unless you enable them.

Normalization can produce false positives, particularly for chains, shared offices, shared domains, switchboards, virtual addresses, franchises, and renamed businesses. Stable source IDs can also be wrong or stale. All candidate pairs require review; this is a data-quality aid, not identity verification, legal advice, or a compliance decision.

Google Maps is a trademark of Google LLC. This unofficial Actor is not affiliated with, endorsed by, or sponsored by Google LLC. It processes user-supplied datasets and does not access Google services.

### Support checklist

When reporting a problem, include the selected source contract, a redacted example row, expected semantic fields, record kind, schema version, and warnings. Do not post API tokens, private dataset links, personal contact data, or unredacted proprietary lead lists in a public issue.

# Actor input Schema

## `source_dataset_id` (type: `string`):

Optional Apify dataset containing Google Maps business rows. Selecting it grants this limited-permission Actor read access only to that dataset and takes precedence over inline rows.

## `leads` (type: `array`):

Inline rows for a quick audit. Common Compass place and business-leads enrichment, Lukaskrivka, Santamaria, ScrapAPI, and flat lead-export fields are recognized automatically.

## `source_contract` (type: `string`):

Choose a reviewed source family for run metadata, or auto for alias detection. This selection does not authenticate provenance or trust upstream duplicate flags.

## `field_map` (type: `object`):

Optional semantic-to-source dotted paths. Use with custom or to override individual auto aliases. Array or string values are accepted for contact/category fields.

## `required_fields` (type: `array`):

Select the fields that define ready for this run. Every percentage is a direct count across this list; no fields are secretly weighted.

## `max_rows` (type: `integer`):

Hard cap on selected-Dataset or inline rows retained and analyzed in one run. One extra selected row is probed to make truncation explicit.

## `max_source_bytes` (type: `integer`):

Hard compact canonical-JSON byte cap applied identically to selected-Dataset and inline rows. A row that would cross the cap is withheld and reported.

## `max_source_row_bytes` (type: `integer`):

Hard compact canonical-JSON byte cap for one source row. An oversized row is withheld, stops the bounded read, and is reported without copying its contents.

## `max_scorecard_rows` (type: `integer`):

Cap scorecards before publication. Accepted non-invalid rows are planned as the chargeable prefix; effective summaries cover only delivered scorecards.

## `max_duplicate_pairs` (type: `integer`):

Cap deterministic pair records. Zero still computes duplicate-row status and summary counts but emits no pair details.

## `max_duplicate_bucket_size` (type: `integer`):

Skip any exact signal shared by more rows than this cap, preventing quadratic pair explosions from placeholder values.

## `include_business_names` (type: `boolean`):

Copy normalized public business names into lead scorecards and duplicate-candidate rows.

## `include_contact_values` (type: `boolean`):

Off by default for data minimization. Enable to copy normalized addresses, website domains, phones, and syntax-valid emails into the immutable run plan and output dataset.

## `as_of` (type: `string`):

Optional ISO 8601 timestamp for reproducible runs. It affects generated\_at only and never changes source facts.

## Actor input object example

```json
{
  "leads": [
    {
      "placeId": "sample-a",
      "title": "North Plumbing",
      "address": "10 Main St, Chicago, IL 60601",
      "countryCode": "US",
      "website": "https://north-plumbing.example",
      "phone": "+1 312 555 0100",
      "emails": [
        "hello@north-plumbing.example"
      ],
      "categoryName": "Plumber",
      "totalScore": 4.7,
      "reviewsCount": 82
    },
    {
      "place_id": "sample-b",
      "name": "Lake Electric",
      "complete_address": "20 Lake St, Chicago, IL 60601",
      "country_code": "US",
      "website": "https://lake-electric.example",
      "phone": "+1 312 555 0200",
      "emails_from_website": [
        "not-an-email"
      ],
      "rating": 4.5,
      "review_count": 39
    },
    {
      "place_id": "sample-c",
      "name": "Lake Electric",
      "address": "20 Lake Street, Chicago, IL 60601",
      "country": "United States",
      "website": "https://lake-electric.example/",
      "phone": "312-555-0200",
      "rating": 4.5,
      "review_count": 39
    },
    {
      "cid": "sample-d",
      "title": "Closed Roofing",
      "address": "30 Roof Ave, Chicago, IL 60602",
      "countryCode": "US",
      "website": "https://closed-roofing.example",
      "phone": "+1 312 555 0300",
      "permanentlyClosed": true
    }
  ],
  "source_contract": "auto",
  "field_map": {},
  "required_fields": [
    "name",
    "address",
    "website",
    "phone",
    "email"
  ],
  "max_rows": 25000,
  "max_source_bytes": 16777216,
  "max_source_row_bytes": 1048576,
  "max_scorecard_rows": 10000,
  "max_duplicate_pairs": 5000,
  "max_duplicate_bucket_size": 50,
  "include_business_names": true,
  "include_contact_values": false
}
```

# Actor output Schema

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

All structured scorecards, summaries, duplicate evidence, and machine-readable source receipts in stable publication order.

## `lead_readiness` (type: `string`):

Open the lead\_readiness Dataset view for CRM-readiness status, missing fields, email evidence, and duplicate counts per supplied business row.

## `duplicate_review` (type: `string`):

Open the duplicate\_review Dataset view for exact identity and bounded composite-signal candidate pairs that require review.

## `source_receipt` (type: `string`):

Open the source\_receipt Dataset view for row and byte bounds, truncation state, paid-prefix counts, and immutable-plan identity.

# 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("8tp/google-maps-lead-quality-audit").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("8tp/google-maps-lead-quality-audit").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 8tp/google-maps-lead-quality-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,8tp/google-maps-lead-quality-audit"
        }
    }
}

```

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/ohW9QdFaP0XEUehwm/builds/yvWGfHHncFzZF0Cwt/openapi.json
