Google Maps Listing Change Monitor: Ratings, Hours, Closures avatar

Google Maps Listing Change Monitor: Ratings, Hours, Closures

Pricing

from $1.80 / 1,000 place signals

Go to Apify Store
Google Maps Listing Change Monitor: Ratings, Hours, Closures

Google Maps Listing Change Monitor: Ratings, Hours, Closures

Track a fixed set of Google Maps listings (clients, competitors or a category search) and get one row per change: closed, reopened, rating drop, review spike or stall, hours, phone, website or category changed, listing gone. Run 1 records a health baseline. No login.

Pricing

from $1.80 / 1,000 place signals

Rating

0.0

(0)

Developer

Detroit Web Agent - Open Data

Detroit Web Agent - Open Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 hours ago

Last modified

Share

Google Maps Listing Change Monitor: features, price and a real output row

Run it free on Apify

Price per 1,000 listingsFree planStarterScaleBusiness
This Actor$3.00$2.50$2.00$1.80
Most-used alternative$10.00$10.00$10.00$10.00

Google Maps Listing Change Monitor sample output table from a real run

Track a fixed set of Google Maps listings and get one row for every change that matters: a place closes or reopens, its rating drops, reviews suddenly spike or stop coming in, reviews get removed, hours change, the phone number or website changes, the primary category changes, or the listing disappears. The first run records a health baseline for every place, with a listing completeness score and its rank inside the set by rating and by review count.

It is built for the job local SEO agencies and multi-location brands already do by hand or pay a listing-management tool for: watching their own listings and their competitors' listings week after week. No login, no Google API key, no browser.

What it does

You give the actor a set of places to watch. A set is either:

  • A search you re-check, such as dentist in Royal Oak, MI or coffee shop in Ferndale, MI. The top N results (default 40) become the monitored set: a fixed competitor landscape.
  • A list of specific listings, as Google Maps place URLs or place IDs (ChIJ...). These form the set "Tracked places" and are read from their own place page on every run. Use this for client listings and for named competitors.

You can mix both in one run.

Run 1 (baseline). Every place gets one baseline row: status (operational, temporarily closed, permanently closed), rating, review count, hours, phone, website, category, a listing completeness score out of 100 with the missing fields named, and its rank in the set by rating and by reviews. Each set also gets a free setSummary row: how many places, how many closed, average rating, median reviews, the top-rated and most-reviewed place, and the least complete listing.

Run 2 and later (changes only). The actor reads the same set again, compares each place with what it stored last time, and writes one event row per change. Places that did not change produce no paid rows. The free set summary row is written every run, so a quiet week still shows you the current state.

State lives in a named key-value store (maps-listing-change-monitor), keyed by your monitorKey. Runs with the same key compare against each other. Schedule the actor and it becomes a listing monitor.

Who it is for

The local SEO agency with 30 client listings. An agency manages Google Business Profiles for dentists, HVAC companies and law firms. Clients ask "did anything change?" and the honest answer is usually "we would not know until someone complains". The agency pastes its 30 client place URLs, sets monitorKey: "agency-clients", picks the clientListings preset and schedules a weekly run. When someone at a client's office edits the phone number, when Google marks a location temporarily closed, or when the rating slips from 4.6 to 4.3 after a run of bad reviews, a row lands in the dataset (and in Slack or email through an Apify integration) with a one-line summary the account manager can forward.

The multi-location brand watching its own estate. A regional chain has 60 locations. Head office wants to know when a store's hours on Google drift from the hours on the website, when a location starts losing its rating, or when a store's review flow stalls (often a sign the review-request process stopped at that branch). The brand tracks its own place IDs with the reputationWatch preset for reviews and ratings, and a second task with closureWatch for hours and closures.

The competitor set for a pitch or a market report. A marketing consultant tracks med spa in Birmingham, MI and med spa in Troy, MI with the competitorSet preset. Run 1 gives a ranked table of every competitor by rating and reviews with completeness scores, ready for a pitch deck. Weekly runs after that show who closed, who is gaining reviews fast (a competitor running a review campaign), and which new places entered the top results.

Input examples

Empty input works. It runs a small demo: the search coffee shop in Ferndale, MI, 20 places, baseline rows plus one summary row, in well under two minutes.

Client listings, checked weekly:

{
"placeUrls": [
"https://www.google.com/maps/place/?q=place_id:ChIJn8X_1xevJIgR0nUJVsjFMb4",
"ChIJ2eUgeAK6j4ARbn5u_wAGqWA"
],
"preset": "clientListings",
"monitorKey": "agency-clients"
}

A competitor set from two searches, including places entering the results:

{
"searches": ["med spa in Birmingham, MI", "med spa in Troy, MI"],
"maxPlacesPerSearch": 30,
"preset": "competitorSet",
"monitorKey": "medspa-oakland-county"
}

Reputation only, with a stricter rating threshold:

{
"placeUrls": ["ChIJ2eUgeAK6j4ARbn5u_wAGqWA"],
"searches": ["dentist in Royal Oak, MI"],
"preset": "custom",
"alertOn": ["ratingDrop", "reviewSpike", "reviewStall", "reviewsRemoved"],
"ratingDropThreshold": 0.1,
"monitorKey": "dental-reputation"
}

Input fields:

FieldWhat it doesDefault
searchesGoogle Maps searches to re-check; each one is a monitored set["coffee shop in Ferndale, MI"]
placeUrlsPlace URLs or place IDs to track individuallyempty
maxPlacesPerSearchSize of each search set (1 to 120)40 (demo: 20)
presetcustom, clientListings, competitorSet, reputationWatch, closureWatchcustom
alertOnChange types to report when preset is customall except enteredSet
ratingDropThresholdStars of drop that count as RATING_DROP0.2
spikeMinReviewsMinimum new reviews for a REVIEW_SPIKE5
monitorKeyName of the stored historyderived from the input
resetBaselineStart over with a fresh baselinefalse
maxDisappearChecksPlace-page lookups per run for places that dropped out of a search25
language, countryCodeGoogle hl and glen, us

Output example

A change event:

{
"rowType": "event",
"eventType": "RATING_DROP",
"severity": "medium",
"summary": "RATING_DROP: \"Northside Dental\" (Dentist, Royal Oak) fell from 4.6 to 4.3 stars (-0.3) since 2026-10-06; 212 reviews.",
"setName": "Tracked places",
"title": "Northside Dental",
"categoryName": "Dentist",
"status": "OPERATIONAL",
"rating": 4.3,
"reviewsCount": 212,
"field": "rating",
"oldValue": "4.6",
"newValue": "4.3",
"ratingDelta": -0.3,
"completenessScore": 86,
"missingFields": "website",
"placeId": "ChIJ...",
"mapsUrl": "https://www.google.com/maps/search/?api=1&query=Northside%20Dental&query_place_id=ChIJ...",
"sourceUrl": "https://www.google.com/maps/place/?q=place_id:ChIJ...&hl=en&gl=us",
"previousCheckedAt": "2026-10-06T08:00:00.000Z",
"daysSinceLastCheck": 7,
"monitorKey": "dental-reputation",
"runNumber": 3,
"checkedAt": "2026-10-13T08:00:00.000Z"
}

A baseline row (run 1) carries the same place fields with rowType: "baseline", ratingRankInSet, reviewsRankInSet, setSize and a summary such as BASELINE: "Honey Dirt Coffee" (Coffee shop, Ferndale) is operational, 4.7 stars from 310 reviews, listing 100/100 complete; #3 by rating and #2 by reviews of 20 in coffee shop in Ferndale, MI. (the names and numbers here are illustrative).

The set summary row has rowType: "setSummary", placesCount, operationalCount, closedCount, closedShare, avgRating, medianReviews, avgCompleteness, topByRating, topByReviews, eventsCount and eventsByType.

The dataset has two views: Changes and baseline (the columns you read first) and All fields.

Signals explained

EventWhen it firesSeverity
CLOSEDStatus moves from operational to temporarily or permanently closedhigh
REOPENEDA closed place shows as operational againmedium
RATING_DROPRating falls by ratingDropThreshold or more from the reference rating. The reference only moves up (or resets after a reported drop), so a slow slide of 0.1 per week is still caughtmedium, high at 0.4+
REVIEW_SPIKEAt least spikeMinReviews new reviews since the last check and a pace at least 3x the earlier pace; without history, at least 10 new reviews and 15% growthmedium
REVIEW_STALLThe earlier pace predicts 4+ new reviews, at least 3 days passed, and none arrivedlow
REVIEWS_REMOVEDReview count falls by 3 or more (and at least 2%) since the last checkmedium
HOURS_CHANGEDNew opening hours seen on two checks in a row (a one-day holiday change does not fire)medium
CONTACT_CHANGEDPhone number or website domain added, changed or removed (field says which)medium, high when removed
CATEGORY_CHANGEDPrimary Google category changedlow
DISAPPEAREDThe listing no longer resolves on Google Maps on two checks in a rowhigh
ENTERED_SETA place new to the monitor enters a monitored search (only with enteredSet switched on, which the competitorSet preset does)info

Completeness score. Seven checks, each worth one seventh: phone, website, hours, category, address, photos, reviews. missingFields names the ones that fail. It is a quick listing-hygiene number, not Google's own profile strength score.

Dropping out of a search vs closing. A place can slide out of the top 40 results without anything happening to it. When a known place is missing from a search, the actor looks it up on its own place page (up to maxDisappearChecks per run). If it is still there, it is diffed normally; if it cannot be found twice in a row, you get DISAPPEARED. Operational places that stay outside the results for three runs leave the set quietly.

Pricing

Pay per event. You pay for rows that carry information, nothing for the rest.

EventFREE planBRONZESILVERGOLD and above
place-signal (one baseline row or one change event)$0.003$0.0025$0.002$0.0018
Actor start$0.00005$0.00005$0.00005$0.00005

Set summary rows and unchanged places are free. Typical cost on the FREE plan:

ScenarioRows chargedCost
Baseline of 30 client listings30about $0.09
Weekly check of those 30, 2 changes found2about $0.006
Baseline of 2 searches x 40 places80about $0.24
Weekly check of 80 places, 5 changes5about $0.015
A year of weekly checks on 80 places, ~5 changes a week80 + 260about $1.02

Platform compute is billed by Apify separately and is small: a 40-place search check usually runs in under a minute at 512 MB.

maxTotalChargeUsd is respected. If the run reaches your limit it stops before the next place, never pushes a paid row it did not charge, and keeps the undelivered changes pending: the next run reports them.

Monitor mode and schedule recipe

  1. Run once with your input and a monitorKey you choose, for example agency-clients. This is the baseline.
  2. In Apify Console, save the input as a task, then add a schedule: weekly (0 8 * * 1, Monday 08:00) suits most agencies; daily is fine for closure watching.
  3. Add an integration on the task (Slack, email, webhook, Make, Zapier, n8n) that fires on run success, and filter rows where rowType is event.
  4. Keep language and countryCode the same across runs. Hours and category text are compared as Google shows them in that language.
  5. To start over (for example after you change the client list a lot), run once with resetBaseline: true.

Adding a place URL to an existing tracked list gives that place a baseline row on its next run. Removing one stops checking it; its history ages out after 180 days.

Use via API or MCP

Start a run and read changes with the Apify API:

curl -X POST "https://api.apify.com/v2/acts/transparent_meteorite~maps-listing-change-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"placeUrls":["ChIJ2eUgeAK6j4ARbn5u_wAGqWA"],"preset":"clientListings","monitorKey":"agency-clients"}'

Python:

from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("transparent_meteorite/maps-listing-change-monitor").call(
run_input={"searches": ["dentist in Royal Oak, MI"], "preset": "competitorSet", "monitorKey": "dentists-ro"})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["rowType"] == "event":
print(row["summary"])

AI agents can call it through the Apify MCP server:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?actors=transparent_meteorite/maps-listing-change-monitor",
"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }
}
}
}

Then ask the agent something like "check my tracked listings with monitor key agency-clients and list anything that closed or lost rating".

Limits and honest caveats

  • Public Google Maps data only. The actor reads the same JSON the public google.com/maps page loads. It does not log in, does not use the Google Places API, and cannot see Business Profile insights, owner-only fields or review text history.
  • Review counts, not review text. Spike, stall and removal signals come from the review count Google shows. If you need the reviews themselves, use a review scraper on the places this actor flags.
  • Temporary vs permanent closure. Google marks closed listings with one internal flag. When the page text says "Temporarily closed" the actor reports CLOSED_TEMPORARILY; otherwise a closed listing is reported as CLOSED_PERMANENTLY. In our test captures every closed listing was a permanent closure, so treat the temporary/permanent split as best effort and read the summary.
  • Search sets move. Google's ranking shifts with time and location signals. That is why dropouts are confirmed on the place page, and why ENTERED_SET is off by default.
  • Hours are compared as text, after normalising spaces and dashes. A change of interface language looks like a change of hours, so keep language fixed.
  • Contact changes compare like with like. Search cards and place pages occasionally format phone and website differently, so a search-set observation is only compared with an earlier search-set observation of the same place.
  • Scale. Built for hundreds of places per run, not tens of thousands. Requests run at most two at a time with pauses between pages. Google lists about 120 places per search at most.
  • Proxy. It uses your proxy setting (Apify Proxy automatic by default) and falls back to a direct connection when rate limited. It does not escalate to residential proxies on its own; pick a residential group in the Proxy field if you need it.
  • Baselines are paid once per place per set. A place that appears in two of your searches gets a baseline row in each set, and change events only once per run.

Run in 30 seconds

  1. Open the Actor in Apify Console and click Try for free (or Start if you already have an account).
  2. Open the Input tab, switch to JSON, and paste the example below. It is small on purpose, so the first run is quick and cheap.
  3. Click Start. Most small runs finish in well under two minutes.
  4. Open the Output tab to read the Overview table, or use Export to download JSON, CSV or Excel.
{
"searches": [
"coffee shop in Ferndale, MI"
],
"maxPlacesPerSearch": 10,
"preset": "competitorSet",
"monitorKey": "ferndale-coffee-test"
}

Once the sample looks right, replace the example values with your own list and raise the limits.

Example API call

The first run for a given monitorKey stores a baseline and reports baseline rows plus one summary row. Changes appear from the second run onward, so schedule the Actor daily or weekly with the same monitorKey. Start it from code or with curl; replace the placeholder token and username.

from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("YOUR_USERNAME/maps-listing-change-monitor").call(run_input={"searches": ["coffee shop in Ferndale, MI"], "maxPlacesPerSearch": 10, "preset": "competitorSet", "monitorKey": "ferndale-coffee"})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row)
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~maps-listing-change-monitor/runs?token=APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"searches": ["coffee shop in Ferndale, MI"], "maxPlacesPerSearch": 10, "preset": "competitorSet", "monitorKey": "ferndale-coffee"}'

Tips to keep monitoring reliable

  • Always set a stable monitorKey. Changing it starts a new history and a new baseline.
  • Use resetBaseline only when you intentionally redefine the set, for example after changing the search wording.
  • Keep maxPlacesPerSearch modest while testing; raise it once alerts look right.
  • Lower maxDisappearChecks if you want shorter runs, at the cost of fewer closure confirmations.

FAQ

How is this different from a Google Maps scraper? A scraper returns every place every time. This actor remembers what it saw and returns only what changed, plus a one-time baseline. On a weekly schedule you pay for a handful of rows instead of the whole list.

Does it find new businesses in an area? Not as its purpose. It watches a set you define. With competitorSet it tells you when a new place enters a search you already monitor, which is useful for competitor tracking but is not a full new-business feed.

What happens if a run fails halfway? The monitor state is saved at the end of a run. Places checked in a failed run are compared again next time, so a change is reported late rather than lost.

Can I run several monitors? Yes. Use a different monitorKey for each client or market. Each key keeps its own history in the same key-value store.

How do I see the stored state? Open the key-value store maps-listing-change-monitor in your Apify account; the record snap-<monitorKey> holds it. The run's OUTPUT record summarises counts per set.

Why did I get no rows on run 2? Nothing changed. The set summary row confirms the check ran and shows the current numbers.

Can I change the rating threshold? Yes, ratingDropThreshold (0.1 to 2 stars).

Is this allowed? It reads publicly visible listing data at a low request rate, without logging in or bypassing any access control. You are responsible for how you use the data in your jurisdiction.

New to Apify? Create a free account and run this Actor in under a minute. Some links are affiliate links.