# Google Maps Monitor — Only What Changed Since Your Last Run (`godberry/google-maps-monitor`) Actor

Watch a Google Maps territory and get back only what is new or changed — rating moves, review growth, new businesses, renames, relocations, hours and contact changes — with the previous values attached. Unchanged businesses are re-checked free and never billed.

- **URL**: https://apify.com/godberry/google-maps-monitor.md
- **Developed by:** [Tomas Lebedinskas](https://apify.com/godberry) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 change detecteds

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.

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

## Google Maps Monitor — Only What Changed Since Your Last Run

Most Google Maps scrapers hand you the same 500 businesses every time you run them, and charge you for all 500. You then diff them yourself to find the six that actually moved.

This one remembers. Point it at a territory, run it whenever you like, and it returns **only what is new or changed** — with the previous values attached, so you can see the move. Businesses that did not change deliver nothing and cost **$0.20 per 1,000** to re-check, against $1.50–$4.00 per 1,000 to scrape them again from scratch.

Maintained by Godberry Studios. Questions, a field you need, or something behaving oddly? Email [hello@godberrystudios.com](mailto:hello@godberrystudios.com) — the developer replies, usually within one business day.

---

### What it does

1. Scans the Google Maps searches you give it — "restaurants in Berlin", "dentists, Austin TX".
2. Compares what it finds against what it showed you last time.
3. Returns only the differences, and bills only for those.

**First run is a baseline.** Everything is new the first time, so it prices like an ordinary leads scrape. From the second run on, you are paying for signal instead of volume.

### What counts as a change

| Field | Why you care |
| --- | --- |
| `rating` | Reputation moving up or down |
| `reviewCount` | Momentum — who is suddenly getting reviewed |
| `name` | Rebrand, or a franchise changing hands |
| `category` | A business repositioning itself |
| `address` | Relocation |
| `hours` | Reduced hours often precede a closure |
| `phone`, `website` | Contact details appearing or changing |

Each returned row carries `changeType` (`new` or `changed`), `changedFields`, `previousValues`, and `lastSeenAt`.

**What is deliberately NOT a change:** a field that merely goes *missing*. Google renders sparser result cards at random, and treating a vanished phone number as a change would manufacture fake changes every run — and bill you for them. Only a move to a known value counts.

### Pricing

Every business in a run lands in exactly one of three buckets, so nothing is ever billed twice:

| Event | Price | When |
| --- | --- | --- |
| Business re-checked | **$0.0002** | We looked, it was identical. Nothing is delivered — you are covering the sweep |
| Business added | **$0.002** | A business enters your watchlist — every business on the baseline run, plus genuinely new ones later |
| Change detected | **$0.03** | A business you already watch actually changed |

Duplicates across overlapping searches are collapsed and charged **once**.

**A typical week** — 500 businesses watched, 10 of them changed — costs about **$0.40**. Re-scraping those same 500 places with a conventional Maps Actor is **$0.75–$2.00**, and you still have to diff them yourself to find the 10 that moved.

**A quiet week** — 500 watched, nothing changed — costs **$0.10**. You are paying two-tenths of a cent per business to know that nothing moved.

#### Why re-checks are not free

An earlier draft of this Actor billed only for changes. It was a nicer sentence and a broken business: checking 500 businesses costs real compute whether or not any of them moved, so a perfectly quiet run — the product doing its job — would have run at a loss, and the price per change needed to cover it depended on *your* churn rate rather than on anything we control.

$0.0002 is roughly what the sweep actually costs. It is priced to be honest, not to make money on the businesses that stayed put.

#### The cost cap is in dollars, not rows

`maxCostUsd` is a hard ceiling on what a single run can charge you. Most Actors cap the *number of results*, which does nothing to protect you when a territory changes more than expected.

Anything held back by the cap is **not** recorded as seen, so the next run offers it again. Nothing is silently dropped.

The sweep is reserved against the cap first, since by the time results are counted the scanning has already happened. If your cap is too small to even cover re-checking the territory, the run says so and tells you what it would take.

### Keeping a monitor stable

The Actor compares each run to the previous one, so consistency matters:

- **Keep `monitorId` the same** between runs. It names the watchlist and its memory. Use one per territory — `berlin-restaurants`, `lisbon-cafes` — and they stay independent. Changing it starts a fresh baseline.
- **Keep the searches the same.** Changing them changes what is being watched.
- **Keep `language` and `countryCode` the same.** Switching language changes how names and hours are written, which reads as a change.

### Input

```json
{
  "searchQueries": ["restaurants in Berlin"],
  "monitorId": "berlin-restaurants",
  "maxCostUsd": "5.00",
  "maxResultsPerSearch": 50,
  "language": "en",
  "countryCode": "de",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
````

### Good uses

- **Territory watch for local-services sales** — new businesses opening in your patch, weekly, without re-reading the whole list.
- **Reputation tracking across competitors** — rating and review-count moves for a set of rivals.
- **Franchise and multi-location monitoring** — catch a renamed, relocated or quietly-closing location.
- **Lead-list hygiene** — keep an existing CRM list current instead of re-importing thousands of unchanged rows.

### Honest limits

- Google caps a single Maps search at roughly 120 results. Narrow by city or neighbourhood for fuller coverage of dense categories.
- Change detection is only as good as what Google shows on the result card. If Google stops displaying a field, we cannot see it move.
- The first run has nothing to compare against, so everything is new. That is expected, and it is priced as a normal scrape rather than as changes.
- Defaults to Apify's datacenter proxy, which is included on paid plans. We tested it on 5 of 5 territories (Berlin, Austin, Manchester, Rome, Sydney) and it returned results identical to residential at about a fifth of the cost. If a territory does start getting blocked, switch Residential on in the proxy input — it is billed per GB.
- `phone` is read from the result card, which is where Google puts it — bundled into the same line as the opening hours. We split them apart, so a card that renders without the phone does not masquerade as an hours change. `website` is not on the search card at all, so it is watched but will never fire in this mode.

# Actor input Schema

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

One per line — the territory you want monitored. Use natural Google Maps searches: "restaurants in Berlin", "dentists, Austin TX", "plumbers near Manchester". Keep the SAME searches between runs: the monitor compares each run against the last one, so changing the searches changes what it is watching.

## `monitorId` (type: `string`):

Names this watchlist so its memory is kept separate from your others. Use one name per territory — e.g. 'berlin-restaurants' and 'lisbon-cafes' are tracked independently. Reuse the same name every run: that is what lets the Actor know what it already showed you. Changing it starts a fresh baseline.

## `maxCostUsd` (type: `string`):

A ceiling in DOLLARS, not in rows. The run stops delivering once it would exceed this, and anything held back is offered again on your next run rather than being silently dropped. Leave empty for no cap. This protects you from a surprise bill when a territory changes far more than usual.

## `maxResultsPerSearch` (type: `integer`):

How deep to scan each search (1–120; Google caps a single Maps search at ~120). This is how many businesses are CHECKED. Businesses that are identical to last run cost $0.0002 each to re-check — real money is only charged for the ones that are new or changed.

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

UI language code (e.g. 'en', 'de', 'fr'). Keep it consistent between runs — switching languages can change how names and hours are written, which reads as a change.

## `countryCode` (type: `string`):

Two-letter geo-bias country code (e.g. 'us', 'de', 'gb'). Match it to the territory you are watching.

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

Defaults to Apify's datacenter proxy pool, which is included on paid plans and costs you nothing per gigabyte. We tested this: datacenter succeeded on 5 of 5 territories (Berlin, Austin, Manchester, Rome, Sydney) and returned identical results to residential at about a fifth of the cost. Switch on Residential only if a specific territory starts getting blocked — it is billed per GB.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists, Austin TX",
    "coffee shops in Lisbon",
    "law firms in Toronto"
  ],
  "monitorId": "berlin-restaurants",
  "maxCostUsd": "5.00",
  "maxResultsPerSearch": 50,
  "language": "en",
  "countryCode": "us",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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 = {
    "searchQueries": [
        "restaurants in Berlin"
    ],
    "monitorId": "berlin-restaurants",
    "maxCostUsd": "5.00",
    "maxResultsPerSearch": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("godberry/google-maps-monitor").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 = {
    "searchQueries": ["restaurants in Berlin"],
    "monitorId": "berlin-restaurants",
    "maxCostUsd": "5.00",
    "maxResultsPerSearch": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("godberry/google-maps-monitor").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 '{
  "searchQueries": [
    "restaurants in Berlin"
  ],
  "monitorId": "berlin-restaurants",
  "maxCostUsd": "5.00",
  "maxResultsPerSearch": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call godberry/google-maps-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=godberry/google-maps-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Google Maps Monitor — Only What Changed Since Your Last Run",
        "description": "Watch a Google Maps territory and get back only what is new or changed — rating moves, review growth, new businesses, renames, relocations, hours and contact changes — with the previous values attached. Unchanged businesses are re-checked free and never billed.",
        "version": "0.1",
        "x-build-id": "39QxoafweWv69Eif9"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/godberry~google-maps-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-godberry-google-maps-monitor",
                "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/godberry~google-maps-monitor/runs": {
            "post": {
                "operationId": "runs-sync-godberry-google-maps-monitor",
                "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/godberry~google-maps-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-godberry-google-maps-monitor",
                "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": [
                    "searchQueries"
                ],
                "properties": {
                    "searchQueries": {
                        "title": "Searches to watch",
                        "type": "array",
                        "description": "One per line — the territory you want monitored. Use natural Google Maps searches: \"restaurants in Berlin\", \"dentists, Austin TX\", \"plumbers near Manchester\". Keep the SAME searches between runs: the monitor compares each run against the last one, so changing the searches changes what it is watching.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "monitorId": {
                        "title": "Monitor name",
                        "type": "string",
                        "description": "Names this watchlist so its memory is kept separate from your others. Use one name per territory — e.g. 'berlin-restaurants' and 'lisbon-cafes' are tracked independently. Reuse the same name every run: that is what lets the Actor know what it already showed you. Changing it starts a fresh baseline.",
                        "default": "default"
                    },
                    "maxCostUsd": {
                        "title": "Hard cost cap for this run (USD)",
                        "type": "string",
                        "description": "A ceiling in DOLLARS, not in rows. The run stops delivering once it would exceed this, and anything held back is offered again on your next run rather than being silently dropped. Leave empty for no cap. This protects you from a surprise bill when a territory changes far more than usual."
                    },
                    "maxResultsPerSearch": {
                        "title": "Max businesses to check per search",
                        "minimum": 1,
                        "maximum": 120,
                        "type": "integer",
                        "description": "How deep to scan each search (1–120; Google caps a single Maps search at ~120). This is how many businesses are CHECKED. Businesses that are identical to last run cost $0.0002 each to re-check — real money is only charged for the ones that are new or changed.",
                        "default": 50
                    },
                    "language": {
                        "title": "Language",
                        "type": "string",
                        "description": "UI language code (e.g. 'en', 'de', 'fr'). Keep it consistent between runs — switching languages can change how names and hours are written, which reads as a change.",
                        "default": "en"
                    },
                    "countryCode": {
                        "title": "Country",
                        "type": "string",
                        "description": "Two-letter geo-bias country code (e.g. 'us', 'de', 'gb'). Match it to the territory you are watching.",
                        "default": "us"
                    },
                    "proxyConfiguration": {
                        "title": "Proxy",
                        "type": "object",
                        "description": "Defaults to Apify's datacenter proxy pool, which is included on paid plans and costs you nothing per gigabyte. We tested this: datacenter succeeded on 5 of 5 territories (Berlin, Austin, Manchester, Rome, Sydney) and returned identical results to residential at about a fifth of the cost. Switch on Residential only if a specific territory starts getting blocked — it is billed per GB.",
                        "default": {
                            "useApifyProxy": true
                        }
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
