# Google Maps Data Validator — Maps Quality Guard (`pbt-guard/maps-quality-guard`) Actor

Already have Google Maps business leads? Flag duplicate listings, out-of-area places, missing fields, and changes between runs before CRM import. Works with Apify Dataset IDs, JSON, and CSV. Conservative branch matching; no live scraping or contact verification.

- **URL**: https://apify.com/pbt-guard/maps-quality-guard.md
- **Developed by:** [PBT Guard](https://apify.com/pbt-guard) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 business records

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

## Maps Quality Guard — Business Data Validator

![Maps Quality Guard brand icon](https://apify-image-uploads-prod.s3.us-east-1.amazonaws.com/bOvMOANJ3qNhRS5QP-actor-sA8C1oboWr5hW3vwJ-vZTE453HCO-maps_quality_guard_icon.png)

Validate business datasets before CRM import: duplicates, wrong geographic area, missing fields and changes across runs. This Actor does NOT scrape Google Maps, fetch websites, verify email deliverability or send messages.

### Google Maps scraper -> quality-checked CRM export

You already have business records. Before sending them to a CRM, use this Actor to identify repeated Google Place IDs, locations outside your target area, incomplete source fields, and changes since an earlier dataset. **It keeps every source row with a transparent decision** so you can inspect excluded and review records before filtering.

**Try the synthetic sample:** [Open Maps Quality Guard in Apify Console](https://console.apify.com/actors/sA8C1oboWr5hW3vwJ?addFromActorId=sA8C1oboWr5hW3vwJ). The prefilled input contains 3 fictional businesses. One is a repeated Place ID; the other two are unique. No other scraper or API key is needed for this sample.

| Sample business | Decision | Why |
| --- | --- | --- |
| Harbor Coffee (richer source row) | `accepted` | Unique matching Place ID, more complete fields |
| Harbor Coffee (second source row) | `excluded` | `DUPLICATE_BUSINESS`; points to the retained row |
| Garden Cafe | `accepted` | Different Place ID and address |

*The sample above is fictional. A 3-row run producing 2 accepted and 1 excluded was verified in Apify; this is not a guarantee for other datasets.*

**Use your own Google Maps Scraper results in three steps:**

1. Open the source scraper's **Storage -> Dataset** and copy its **Dataset ID** (not the run URL or an API token).
2. In this Actor's **Input**, clear the prefilled `records` demo array and choose your `sourceDatasetId`. Optionally provide `previousDatasetId` to compare runs, and all of `centerLat`, `centerLng`, `radiusKm` for a radius check.
3. Start the Actor. In **Output**, inspect `status` and `reasonCodes`. Export the default dataset to JSON/CSV, or filter for `status = accepted` in your downstream CRM import.

**Best for:** local SEO agencies, B2B data teams, CRM integrators, and developers working with existing Google Maps exports of up to 10,000 rows. For ambiguous matches, the Actor marks records for review rather than deleting branches automatically.

**Pricing:** $0.001 per output row ($1 per 1,000 records) plus the stated per-start charge. Accepted, review, and excluded output rows are billed; the source scraping Actor is billed separately.

### Features

- Duplicate detection: prefer exact Google Place ID; otherwise match normalized business name + street address or name + coordinates. Distinct branches are not merged just because their names are similar.
- Geographic validation: optional latitude, longitude and radius in km. Outside records are excluded; missing/invalid coordinates require review, not a false outside verdict.
- Completeness: flags missing address, website, phone and email without claiming they do not exist.
- Compare datasets: detects new, changed, unchanged and unverifiable businesses by stable identity.
- Output: default dataset with accepted/review/excluded rows, reasons, heuristic score and normalized fields; SUMMARY JSON in key-value store.

### Quick start

1. Supply exactly one source: records (JSON array), sourceDatasetId (accessible Apify dataset) or csvText (CSV with headers). Clear the default synthetic demo records before choosing dataset or CSV.
2. Optional: provide previousDatasetId OR previousRecords to compare runs.
3. Optional: set centerLat, centerLng and radiusKm together.
4. Run and inspect Output. Export JSON/CSV using Apify's dataset export.

### Example input — synthetic demo

```
{
  "records": [
    {"title":"Harbor Coffee","placeId":"demo-101","address":"10 Harbor Street, Izmir","location":{"lat":38.42,"lng":27.13},"website":"https://example.com","phone":"+90000000000"},
    {"title":"Harbor Coffee","placeId":"demo-101","address":"10 Harbor Street, Izmir","location":{"lat":38.42,"lng":27.13}},
    {"title":"Garden Cafe","placeId":"demo-102","address":"20 Garden Avenue, Izmir","location":{"lat":38.425,"lng":27.14}}
  ],
  "maxRecords": 1000, "centerLat":38.42, "centerLng":27.13, "radiusKm":10
}
```

The two Harbor Coffee rows have the same Place ID. The more complete one is retained; the other is flagged duplicate. The Garden Cafe branch is separate.

### Outputs

- status: accepted, review, excluded. All input records (including excluded and review) are returned, for transparent decision-making.
- qualityScore: 0–100 heuristic data completeness score, NOT proof that a business is real or reachable.
- reasonCodes: e.g. DUPLICATE_BUSINESS, MISSING_PHONE, LOCATION_UNVERIFIED, OUTSIDE_TARGET_RADIUS.
- identityKey / identityEvidence / duplicateOf: match rationale and source-row link.
- changeStatus / changedFields: previous-data comparison, when supplied.
- distanceKm: numeric Haversine distance if a valid center and source coordinates are provided.
- businessName, placeId, address, city, website, phone, email, rating, reviewCount, latitude, longitude, sourceUrl: selected source fields, not independently verified.

### Limits and privacy

- Maximum 10,000 rows per current/baseline dataset; default maxRecords 1,000. CSV text at most 2 MB, 80 columns.
- No live website checks and no third-party API calls. The only network requests use the Apify SDK to read selected Apify datasets and save results.
- Source data may contain personal information; do not process records without a lawful basis. We don't log source rows or external credentials.
- Location data is based only on source coordinates; city names are not geocoded.
- Place IDs and contact values are from the source, not independently validated. Duplicate detection is heuristic; review excluded rows before deleting data.
- Source fields not needed for validation are not copied to the output. Records are stored under your Apify account with its standard access/retention settings.
- No automatic outreach, stealth scraping, or login automation.

### Pricing

Live Apify Store pricing: $0.001 per output dataset row ($1 / 1,000 business records), plus $0.00005 per Actor start. Each output row is billable whether marked accepted, review, or excluded. Apify platform usage is included in these prices (not separately charged to the user); source scraping and data acquisition are NOT included. The actual total charge is determined by event counts and your plan discounts, if any. The Actor does not run external paid APIs.

### Support and maintenance

Open an Issue on the Actor page. Include only sanitized, synthetic example input, the error type and expected result; never post secrets or client records.

### Changelog

0.1.0 — deterministic duplicate and location checks, missing-field flags, baseline comparison, CSV/JSON/Apify dataset input, summary and output schema.

# Actor input Schema

## `records` (type: `array`):

Paste Google Maps Scraper output. Remove the demo records when selecting a dataset ID or CSV.

## `sourceDatasetId` (type: `string`):

Optional. ID of a dataset accessible to your account. Clear JSON records when using this.

## `csvText` (type: `string`):

Optional UTF-8 CSV with headers (max 2 MB). Clear JSON records first.

## `previousDatasetId` (type: `string`):

Optional baseline dataset for new/changed/unchanged comparisons.

## `previousRecords` (type: `array`):

Optional baseline records alternative to previous Dataset ID.

## `centerLat` (type: `number`):

Optional geographic filter. Supply all three center/radius fields together.

## `centerLng` (type: `number`):

Optional center longitude.

## `radiusKm` (type: `number`):

Optional radius. Outside excluded; missing coordinates marked review.

## `maxRecords` (type: `integer`):

Hard cap; oversize inputs fail rather than truncate.

## Actor input object example

```json
{
  "records": [
    {
      "title": "Harbor Coffee",
      "placeId": "demo-101",
      "address": "10 Harbor Street, Izmir",
      "location": {
        "lat": 38.42,
        "lng": 27.13
      },
      "website": "https://example.com",
      "phone": "+90000000000"
    },
    {
      "title": "Harbor Coffee",
      "placeId": "demo-101",
      "address": "10 Harbor Street, Izmir",
      "location": {
        "lat": 38.42,
        "lng": 27.13
      }
    },
    {
      "title": "Garden Cafe",
      "placeId": "demo-102",
      "address": "20 Garden Avenue, Izmir",
      "location": {
        "lat": 38.425,
        "lng": 27.14
      },
      "website": "https://example.org"
    }
  ],
  "maxRecords": 1000
}
```

# Actor output Schema

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

Accepted/review/excluded records with reasons.

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

Aggregate counts and interpretation notice.

# 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 = {
    "records": [
        {
            "title": "Harbor Coffee",
            "placeId": "demo-101",
            "address": "10 Harbor Street, Izmir",
            "location": {
                "lat": 38.42,
                "lng": 27.13
            },
            "website": "https://example.com",
            "phone": "+90000000000"
        },
        {
            "title": "Harbor Coffee",
            "placeId": "demo-101",
            "address": "10 Harbor Street, Izmir",
            "location": {
                "lat": 38.42,
                "lng": 27.13
            }
        },
        {
            "title": "Garden Cafe",
            "placeId": "demo-102",
            "address": "20 Garden Avenue, Izmir",
            "location": {
                "lat": 38.425,
                "lng": 27.14
            },
            "website": "https://example.org"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pbt-guard/maps-quality-guard").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 = { "records": [
        {
            "title": "Harbor Coffee",
            "placeId": "demo-101",
            "address": "10 Harbor Street, Izmir",
            "location": {
                "lat": 38.42,
                "lng": 27.13,
            },
            "website": "https://example.com",
            "phone": "+90000000000",
        },
        {
            "title": "Harbor Coffee",
            "placeId": "demo-101",
            "address": "10 Harbor Street, Izmir",
            "location": {
                "lat": 38.42,
                "lng": 27.13,
            },
        },
        {
            "title": "Garden Cafe",
            "placeId": "demo-102",
            "address": "20 Garden Avenue, Izmir",
            "location": {
                "lat": 38.425,
                "lng": 27.14,
            },
            "website": "https://example.org",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("pbt-guard/maps-quality-guard").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 '{
  "records": [
    {
      "title": "Harbor Coffee",
      "placeId": "demo-101",
      "address": "10 Harbor Street, Izmir",
      "location": {
        "lat": 38.42,
        "lng": 27.13
      },
      "website": "https://example.com",
      "phone": "+90000000000"
    },
    {
      "title": "Harbor Coffee",
      "placeId": "demo-101",
      "address": "10 Harbor Street, Izmir",
      "location": {
        "lat": 38.42,
        "lng": 27.13
      }
    },
    {
      "title": "Garden Cafe",
      "placeId": "demo-102",
      "address": "20 Garden Avenue, Izmir",
      "location": {
        "lat": 38.425,
        "lng": 27.14
      },
      "website": "https://example.org"
    }
  ]
}' |
apify call pbt-guard/maps-quality-guard --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pbt-guard/maps-quality-guard"
        }
    }
}
```

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/sA8C1oboWr5hW3vwJ/builds/ceMhjhqkKU51FIzNe/openapi.json
