# Google Maps Rank Tracker | Geo-Grid Heatmap & Competitors (`corvuslab/google-maps-geo-grid-rank-tracker`) Actor

Track where a business ranks on Google Maps at every point of a geo-grid (3×3 to 15×15). Competitors are ranked on the same scans at no extra cost. Average rank, share of local voice, top-3 share, top-10 competitor leaderboard, heatmap PNG, HTML report and change since your last scan.

- **URL**: https://apify.com/corvuslab/google-maps-geo-grid-rank-tracker.md
- **Developed by:** [Corvuslab](https://apify.com/corvuslab) (community)
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 grid point scanneds

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

### What does Google Maps Rank Tracker do?

Google Maps Rank Tracker shows where a business ranks on Google Maps **at every point of a grid around it**, the way customers across town see it. One run returns the rank at each point, **average rank, share of local voice, top-3 and top-10 share**, a **top-10 competitor leaderboard** per keyword, a **heatmap image**, a **shareable HTML report** and **the change since your last scan**, in **66 structured fields**. It's no-code, and the output goes to JSON, CSV, Excel, the API or an AI agent.

Give it a business (a name plus a city, a Google Maps link, a place ID or a CID), keywords and a grid size. You get one summary row per business × keyword, with the rank at every point inside it. Schedule it weekly and every summary shows what moved: a pay-per-scan alternative to a monthly local-rank-tracking subscription.

> New to Apify? Sign up free and use the included monthly platform credit to try this Actor. No credit card needed.

**Why this rank tracker**

- 🆚 **Competitors at no extra cost**: up to 19 more businesses ranked on the same scans.
- 📈 **See what changed**: the previous rank per point and the movement in every metric.
- 🗺️ **Client-ready visuals**: a heatmap PNG per keyword and a shareable HTML report.
- 🔔 **Drop alerts**: Telegram, Slack, Discord, WhatsApp or a webhook.
- ⚡ **Zero setup**: no login, no proxy setup, and an empty input runs a full example scan.

***

### What data can you extract from a Google Maps geo-grid scan?

Every record carries up to **66 fields**, grouped like this:

| Group | What you get |
|---|---|
| **Business & scan** | Business name, place ID, CID, address, Google category, Maps link, rating, review count, keyword, match mode, scan time |
| **Rank at every point** | Row, column, latitude/longitude, distance from the grid centre, rank, found / in top 3, the name that matched, results checked, a "check it on Google Maps" link, and the **top 3 businesses at that point** |
| **Headline metrics** | Average rank (ARP), average rank across all points (ATRP), share of local voice (SoLV), % of points in the top 3 and top 10, best and worst rank, rank at the business, points ranked, the full rank grid as a 2D array |
| **Change since last scan** | Previous rank, rank change and movement (`up`, `down`, `same`, `new`, `lost`) per point, the previous and changed ARP, SoLV and top-3 share, and the counts of changed, improved and declined points |
| **Competitor intelligence** | A top-10 leaderboard per keyword with each rival's name, place ID, CID, category, rating, reviews, points found, average rank, SoLV, top-3 share and whether it's one you track |
| **Grid & deliverables** | Grid size and shape, radius in km and miles, point spacing, zoom, deepest rank checked, centre coordinates, failed points, a completeness flag, and links to the **heatmap PNG** and the **HTML report** |

***

### Key features

- 🆚 **Competitors on the same grid, free.** Add up to 19 competitors or your client's other locations. One set of searches ranks all 20 businesses, so the scan count and your bill stay the same as for one.
- 📈 **Change since your last scan.** Each point carries `previousRank`, `rankChange` and a plain `movement` (`up`, `down`, `same`, `new`, `lost`), so a business that drops off the list shows as `lost`, not a blank. Each summary carries `arpChange`, `solvChange`, `pctTop3Change` and how many points improved or declined.
- 🏆 **Top-10 competitor leaderboard.** For every keyword, the 10 businesses that really own the grid, ranked by share of local voice, with average rank, top-3 share, rating and reviews. Competitors you added stay in the list, flagged `tracked`.
- 🗺️ **Heatmap PNG + shareable HTML report.** A colour-coded grid per business × keyword (green = top 3, amber = 4–10, orange = 11+, red = not ranked) and one self-contained report per run.
- 🎯 **Exact match, brand mode or one grid per business.** A Google Maps link, place ID or CID is always an exact match. Brand mode counts any branch of a chain, and one-grid-per-business lets an agency scan several clients in one run.
- 📐 **Bigger, deeper grids.** 3 × 3 to 15 × 15 points, up to 25 keywords, ranks checked down to position 60. A **circle grid** drops the corners outside the radius and scans 34–48 % fewer points.
- 📍 **Ranks as a local customer sees them**, wherever the scan runs from, so re-scans compare like with like.
- ⚡ **Runs out of the box.** No proxy setup and no Google account. Keywords are optional: without them it scans the business's own Google category.

***

### 🚀 How to scrape Google Maps rankings on a geo-grid

1. Enter the **business**: its name plus a **city**, or paste its Google Maps link, place ID or CID.
2. Add your **keywords** (what customers type into Google Maps) and, optionally, **competitors** to rank on the same grid.
3. Pick a **grid size** and **radius** (5 × 5 over 2 miles is the default and suits a typical town).
4. Click **Start**. The heatmap, the HTML report and the rows appear in the Output tab.
5. **Schedule** it weekly to track progress, and add a notification channel for drop alerts.

#### Quick-start example input

```json
{
  "businessName": "Loyal Coffee",
  "location": "Colorado Springs, CO",
  "keywords": ["coffee shop near me", "latte"],
  "businesses": ["Frankly Coffee", "Bittersweet Coffee and Venue"],
  "gridSize": 5,
  "radiusMiles": 2
}
```

This scans 2 keywords over a 5 × 5 grid (50 scans) and ranks Loyal Coffee **and both competitors** at all 25 points: 6 summary rows and 6 heatmaps, for the same 50 scans as Loyal Coffee alone.

***

### Input

Configure it in the visual editor with no code, or pass JSON through the API. **33 inputs** are available, grouped as in the editor.

#### 📍 What to track

| Field | Type | What it does |
|---|---|---|
| `businessName` | string | The business to track: its Google Maps name, Maps link, place ID (`ChIJ…`) or CID. The grid is centred on it. |
| `keywords` | array | What customers type into Google Maps, up to 25, each scanned across the whole grid. Empty = the business's Google category. |
| `location` | string | City, state or neighbourhood, used to find the business by name. Not needed with a link, place ID or CID. |
| `businesses` | array | Up to 19 more businesses (competitors, or your client's other listings) ranked on the same grid at no extra cost. |
| `matchAllBranches` | boolean | Brand mode: a point counts the first listing whose name matches (e.g. any "Starbucks"). |

#### 🗺️ Grid

| Field | Type | What it does |
|---|---|---|
| `gridSize` | integer | Points per side, 3–15 (default 5). 5 means 25 points, 15 means 225. |
| `radiusMiles` | number | How far the grid reaches from the centre in each direction, 0.25–25 miles (default 2). |
| `radiusKm` | number | The same radius in kilometres (0.4–40). When set, it replaces the radius in miles. |
| `gridShape` | enum | `square` (every point) or `circle` (corners dropped, fewer scans). |
| `separateGrids` | boolean | Give each business its own grid centred on it. |
| `centerLat` / `centerLng` | number | Centre the grid somewhere other than the business, such as a service-area town. |

#### ⚙️ Search settings

| Field | Type | What it does |
|---|---|---|
| `zoom` | integer | Google Maps zoom each point is searched at, 8–18 (default 13). Higher zoom is a closer view, so ranks change faster from point to point. |
| `maxRank` | integer | Deepest position checked, 1–60 (default 20). Anything deeper counts as not ranked. |
| `placeId` | string | Pins the exact listing when a business name has look-alikes nearby. |
| `language` | string | Search language as a 2-letter code (default `en`). |
| `countryCode` | string | Search country (e.g. `us`, `gb`, `de`). Empty = the business's own country. |

#### 📈 Tracking over time

| Field | Type | What it does |
|---|---|---|
| `compareWithPrevious` | boolean | Adds the previous scan of the same business, keyword and grid to every row (on by default). |
| `historyKey` | string | Keeps a separate scan history under this name, e.g. one per client. |
| `includePointRows` | boolean | Adds one row per grid point (rank, top 3 there, previous rank, movement, a Google Maps link). Off by default: the summary's `rankGrid` already holds every point's rank. |
| `generateHeatmaps` | boolean | Saves a heatmap PNG per business × keyword and one HTML report per run (on by default). |

#### 🔔 Notifications

| Field | Type | What it does |
|---|---|---|
| `notifyOn` | enum | `always`, `changes` (only when ranks changed) or `drops` (only when rankings dropped). |
| `dropThreshold` | number | With `drops`: how many positions the average rank must worsen to alert (default 1). |
| Channel fields | string / object | Telegram, Slack, Discord, WhatsApp and webhook settings, listed in the [alerts section](#-how-to-set-up-google-maps-ranking-drop-alerts). Tokens and URLs are secret inputs: encrypted, masked and never logged. |

#### 🛠️ Advanced

| Field | Type | What it does |
|---|---|---|
| `proxyConfiguration` | object | Leave it empty: the actor picks how to search so every point reflects a local customer. Set it only if a run reports points that could not be read. |

#### More example inputs

An exact match by place ID with a circle grid and a deep rank check:

```json
{
  "businessName": "ChIJyaUh0yFFE4cR9kbrTpDyb3I",
  "keywords": ["coffee shop near me", "espresso", "cafe with wifi"],
  "gridSize": 7,
  "gridShape": "circle",
  "radiusMiles": 3,
  "maxRank": 40
}
```

A weekly agency report, with one grid per client, its own history and alerts only on drops:

```json
{
  "businessName": "Loyal Coffee",
  "businesses": ["https://www.google.com/maps?cid=17399631780147321288"],
  "location": "Colorado Springs, CO",
  "keywords": ["coffee shop near me"],
  "separateGrids": true,
  "historyKey": "client-loyal-coffee",
  "notifyOn": "drops",
  "dropThreshold": 1.5,
  "slackWebhookUrl": "https://hooks.slack.com/services/..."
}
```

***

### Output

By default the dataset holds one **`summary`** row per business × keyword, with the metrics, competitors and change since the last scan. Turn on `includePointRows` to add a **`point`** row per grid point; `recordType` tells them apart. A real summary row from a 3 × 3 rescan, trimmed:

```json
{
  "recordType": "summary",
  "scannedAt": "2026-10-19T08:48:04+00:00",
  "keyword": "latte",
  "businessName": "Loyal Coffee",
  "businessPlaceId": "ChIJyaUh0yFFE4cR9kbrTpDyb3I",
  "businessCid": "8246076144351921910",
  "businessAddress": "408 S Nevada Ave, Colorado Springs, CO 80903",
  "businessCategory": "Coffee shop",
  "matchMode": "exact",
  "targetRating": 4.7,
  "targetReviews": 1850,
  "gridSize": 3,
  "radiusMiles": 2.0,
  "zoom": 13,
  "maxRank": 20,
  "total": 9,
  "pointsFound": 9,
  "arp": 5.0,
  "atrp": 5.0,
  "solv": 80.0,
  "pctTop3": 0.0,
  "pctTop10": 100.0,
  "rankAtCenter": 5,
  "rankGrid": [[4, 5, 7], [5, 5, 5], [4, 5, 5]],
  "complete": true,
  "previousArp": 4.44,
  "arpChange": -0.56,
  "solvChange": -2.8,
  "pctTop3Change": -11.1,
  "improvedPoints": 2,
  "declinedPoints": 5,
  "topCompetitors": [
    { "name": "Misty Mountain Collective", "placeId": "ChIJfzeYJOlFE4cRaYJqyMgHtSQ", "category": "Coffee shop",
      "rating": 4.9, "reviews": 426, "pointsFound": 9, "arp": 1.44, "solv": 97.8, "pctTop3": 100.0, "tracked": false }
  ]
}
```

And a point row from the same scan (the north-west corner, with `includePointRows` on):

```json
{
  "recordType": "point",
  "keyword": "latte",
  "businessName": "Loyal Coffee",
  "row": 0,
  "col": 0,
  "lat": 38.856884,
  "lng": -104.859523,
  "distanceMiles": 2.83,
  "rank": 4,
  "previousRank": 4,
  "rankChange": 0,
  "movement": "same",
  "top3": [
    { "rank": 1, "name": "Bittersweet Coffee and Venue", "rating": 4.8, "reviews": 63 },
    { "rank": 2, "name": "Misty Mountain Collective", "rating": 4.9, "reviews": 426 },
    { "rank": 3, "name": "Frankly Coffee", "rating": 4.8, "reviews": 480 }
  ],
  "mapsSearchUrl": "https://www.google.com/maps/search/latte/@38.856884,-104.859523,13z?hl=en&gl=us"
}
```

Every summary row also links its **`heatmapUrl`** (the PNG) and **`reportUrl`** (the run's HTML report).

#### Data fields

The field groups are in the [table above](#what-data-can-you-extract-from-a-google-maps-geo-grid-scan). Reading them: `row` 0 is the north edge and `col` 0 the west edge; a `null` rank means the business wasn't in the top `maxRank`; in every change field (`rankChange`, `arpChange`, `solvChange`, `pctTop3Change`) **positive means better**. `rankChange` needs a rank in both scans; `movement` covers every case, including `new` (entered the top `maxRank`) and `lost` (fell out of it). Change fields are `null` on a first scan. Missing values are `null`.

The Output tab has three views: **Overview**, **Summary per keyword** (metrics, changes, competitors, heatmap and report links) and **Rank at every point** (rank, movement and a Google Maps link per point). With point rows on, every view lists both kinds of row, so filter on `recordType` when you export only one kind.

***

### ♻️ How to monitor Google Maps rankings week by week

Schedule it, and **Compare with the previous scan** (on by default) adds the last scan of the same business, keyword and grid to every row:

| | First scan (baseline) | Every scan after |
|---|---|---|
| Rank at every point | ✅ | ✅ |
| Previous rank + change per point and per metric | — | ✅ |
| Alert only when rankings dropped | — | ✅ |

You're billed per **scan** (keyword × grid point), not per business, so every competitor on the grid is free:

| Businesses on one grid | Scans if run separately | Scans here | You save |
|---|---|---|---|
| 2 (you + 1 competitor) | 2× | 1× | **50 %** |
| 5 (you + 4 competitors) | 5× | 1× | **80 %** |
| 20 (the maximum) | 20× | 1× | **95 %** |

#### Setting up scheduled tracking

1. Configure your business, keywords, competitors and grid. Optionally set a **History name** per client.
2. Run it once to seed the baseline.
3. In the **Schedules** tab, add a recurring run (weekly is the usual cadence for local SEO).
4. Add a notification channel and set **Send a notification** to *Only when rankings dropped*.

Keep the grid settings unchanged between runs: a different size, shape, radius, centre, zoom, depth, language or country starts a fresh history.

***

### 🔔 How to set up Google Maps ranking drop alerts

Get a message after every scan, only when ranks moved, or only when they **dropped**. Fill in just the channels you want:

| Channel | What to configure |
|---|---|
| ✈️ **Telegram** | `telegramToken` (from @BotFather) + `telegramChatId` |
| 💬 **Slack** | `slackWebhookUrl` (an incoming webhook URL) |
| 🎮 **Discord** | `discordWebhookUrl` (a channel webhook URL) |
| 📱 **WhatsApp** | `whatsappAccessToken` + `whatsappPhoneNumberId` + `whatsappTo` (recipients must have messaged your business number in the last 24 h) |
| 🪝 **Webhook** | `webhookUrl` (+ optional `webhookHeaders`) receives every summary row plus the report link as JSON, ideal for n8n, Make or Zapier |

A **drop** is the average rank worsening by `dropThreshold` positions or more, the top-3 share falling by 10 points, or the business vanishing from the grid. A broken channel can't stop the scan or the other channels.

***

### 💡 What can you do with Google Maps rank data?

#### Weekly local SEO reports for clients

Schedule each client's grid weekly and send the heatmap and HTML report. The change fields show exactly which neighbourhoods improved after your work.

#### Prove the impact of Google Business Profile work

Scan before and after you change categories, add photos or run a review drive. The point-by-point `movement` shows where the work paid off.

#### See who owns the map in your area

The leaderboard shows which competitors dominate each keyword, with their rating and review counts. Check which of them also buy their way in with our [Google Ads Scraper](https://apify.com/corvuslab/google-ads-transparency-scraper).

#### Audit multi-location brands

Brand mode counts any branch, and one-grid-per-business scans every location in one run, so the stores invisible in their own neighbourhood stand out.

#### Win new agency clients

Scan a prospect and their top rivals, then open the pitch with a heatmap of where they lose customers. To find prospects in the first place, our [Google Maps No-Website Leads Scraper](https://apify.com/corvuslab/google-maps-no-website-leads-scraper) lists local businesses with no website of their own.

#### Feed dashboards and AI agents

The default output is one compact row per business × keyword, for Looker Studio, Google Sheets, an LLM or an MCP tool.

***

### 💰 How much does it cost to track Google Maps rankings?

This Actor uses Apify's **pay-per-event** model and bills **one event per grid point scanned** (one keyword at one point), so a 5 × 5 grid is 25 events per keyword. The **Pricing** tab shows the live numbers. What keeps the bill down:

- **Competitors are free**, and **circle grids** skip the corners (both covered above).
- **Failed points are never charged**: they're retried, then reported as failed.
- **Spending limits are respected.** The run won't start a keyword's grid your limit can't cover. If even the first doesn't fit, it scans the points your limit covers and warns you.

***

### 🔌 Integrations & export

Export to **JSON, CSV, Excel** or an HTML table, or pull from the **REST API** and the **JavaScript / Python** clients. Run it on a **schedule**, connect it to **Google Sheets, Looker Studio, Slack, Make, Zapier and n8n**, or use it as an **MCP tool** for AI agents. The heatmap PNGs and the HTML report sit in the run's key-value store, ready to link or download.

More Google data from us: [Google Maps No-Website Leads Scraper](https://apify.com/corvuslab/google-maps-no-website-leads-scraper) for local prospect lists, and [Google Ads Scraper](https://apify.com/corvuslab/google-ads-transparency-scraper) for any advertiser's ads from the Ads Transparency Center.

***

### 🔗 Using the API

Run this Actor from your own code. Example with the Apify Python client:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")

run_input = {
    "businessName": "Loyal Coffee",
    "location": "Colorado Springs, CO",
    "keywords": ["coffee shop near me"],
    "businesses": ["Frankly Coffee"],
    "gridSize": 5,
}

run = client.actor("corvuslab/google-maps-geo-grid-rank-tracker").call(run_input=run_input)

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["recordType"] == "summary":
        print(item["businessName"], item["keyword"], item["arp"], item["solv"], item["heatmapUrl"])
```

It also works with the JavaScript/TypeScript client, the Apify CLI and the REST API.

***

### ❓ FAQ

**How are average rank, ATRP and share of local voice calculated?** `arp` is the average rank over the points where the business ranked. `atrp` averages over **all** points, counting a miss as `maxRank + 1`, so it punishes gaps. `solv` (0–100) is the mean over all points of `(maxRank + 1 − rank) / maxRank × 100`: rank 1 earns a full share, a miss earns 0. `pctTop3` and `pctTop10` are the share of points in the top 3 or top 10.

**Why does a rank differ from what I see on my phone?** Your phone adds your own location and search history. Each point is a signed-out search by a customer standing at that point; `mapsSearchUrl` opens the same search on Google Maps.

**What do `null` and `0` mean in `rankGrid`?** `null` means the business wasn't in the top `maxRank` results there (raise `maxRank`, up to 60, to look deeper), or the point couldn't be read (counted in `failedPoints`, and marked `failed` in point rows). A `0` appears only in a circle grid, for a corner outside the radius.

**What is `rankAtCenter` for competitors or a custom centre?** It's the rank at the grid point nearest that business's own address, so each tracked business gets its own "rank at the business". It's `null` when the business lies outside the grid; in brand mode it's the rank at the grid centre. `distanceKm` / `distanceMiles` are measured from the grid centre.

**The business name matches several listings. What happens?** The run takes the first matching listing in your city and warns in the log. For a guaranteed match, paste the Google Maps link, place ID or CID, or add the `placeId` hint. Tracking a chain? Turn on **Count any branch of a brand** instead.

**What if some points can't be read?** They're retried. Any that still fail are counted in `failedPoints`, **not charged**, and `complete` is set to `false`. An incomplete grid isn't saved as history, so it never skews your next comparison.

**How long do the heatmap and report links work?** As long as Apify keeps the run's storage (your plan's data retention). Download the PNGs and the report, or export the dataset, to keep them longer.

**Which countries and languages work?** Any country where Google Maps works. By default the search uses the business's own country and English. Set `countryCode` and `language` (e.g. `de` / `de`) to match how your customers search.

***

### ⚖️ Is it legal to scrape Google Maps rankings?

This Actor reads only **publicly available** Google Maps search results, the same ones anyone sees in a browser, and it collects business listings rather than personal profiles. You're responsible for how you use the data and for complying with Google's terms and applicable law, including the GDPR where it applies. This Actor is not affiliated with, endorsed by or sponsored by Google.

***

**Keywords:** google maps rank tracker · geo-grid rank tracker · local rank tracker · google maps ranking heatmap · local SEO rank tracking · share of local voice · average rank ARP · map pack rank tracker · local pack tracker · google business profile ranking · competitor rank tracking · local SEO agency reporting · grid rank checker · google maps scraper · track google maps rankings weekly · export to CSV · MCP tool for AI agents

# Changelog

This Actor's version history is a separate document: https://apify.com/corvuslab/google-maps-geo-grid-rank-tracker/changelog.md

# Actor input Schema

## `businessName` (type: `string`):

The business to track: its name as it appears on Google Maps, its Google Maps link, its place ID (ChIJ…) or its CID. A link or place ID is always an exact match. The grid is centred on this business.

## `keywords` (type: `array`):

What customers type into Google Maps. One per line, up to 25. Each keyword is scanned across the whole grid.

## `location` (type: `string`):

City, state or neighbourhood the business is in. Used to find the right business when you type a name. Not needed with a link, place ID or CID.

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

More businesses to rank on the same grid: competitors, or your client's other listings. Names, Google Maps links, place IDs or CIDs, one per line, up to 19. They are read from the same searches, so they add no scans and no cost.

## `matchAllBranches` (type: `boolean`):

Off: one exact listing is tracked. On: a point counts the first listing whose name matches (e.g. any 'Starbucks'), for brands with many branches.

## `gridSize` (type: `integer`):

Points per side. 5 means a 5 × 5 grid of 25 points; 7 means 49. Each point × keyword is one scan.

## `radiusMiles` (type: `number`):

How far the grid reaches from the centre in each direction, in miles. 2 covers a typical town; use more for a whole metro.

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

The same radius in kilometres. When set, it replaces the radius in miles.

## `gridShape` (type: `string`):

A circle grid keeps only the points within the radius, so a 7 × 7 grid scans 29 points instead of 49.

## `separateGrids` (type: `boolean`):

Off: one grid around the first business, every business ranked on it (no extra cost). On: each business gets its own grid centred on it, for agencies scanning several clients in one run.

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

Centre the grid here instead of on the business. Set together with the centre longitude.

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

Set together with the centre latitude.

## `zoom` (type: `integer`):

The Google Maps zoom level each point is searched at. Higher zoom = a closer view, so ranks change faster from point to point.

## `maxRank` (type: `integer`):

Positions past this count as not ranked. Up to 60.

## `placeId` (type: `string`):

If the business name has look-alikes nearby, add its place ID here and the name lookup picks exactly that listing.

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

Language of the searches, as a 2-letter code (e.g. 'de', 'es'). Use the language your customers search in.

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

2-letter country code of the searches (e.g. 'us', 'gb', 'de'). Leave empty to use the business's own country.

## `compareWithPrevious` (type: `boolean`):

Adds each point's previous rank and the change in average rank, share of voice and top-3 share since the last scan of the same business, keyword and grid. Schedule this actor to track progress week by week.

## `historyKey` (type: `string`):

Optional. Keeps a separate scan history under this name (e.g. one per client).

## `includePointRows` (type: `boolean`):

On: adds one row per grid point (rank, top 3 there, previous rank, a Google Maps link) next to the summary rows. Off: one summary row per business × keyword, which already holds the rank at every point in its rank grid.

## `generateHeatmaps` (type: `boolean`):

Saves a PNG heatmap per business × keyword and one shareable HTML report for the run. Links are in each summary row.

## `notifyOn` (type: `string`):

When to message the channels below. Nothing is sent unless a channel is filled in.

## `dropThreshold` (type: `number`):

With 'only when rankings dropped': alert when the average rank worsens by at least this many positions (or the top-3 share falls by 10 points, or the business disappears).

## `telegramToken` (type: `string`):

Telegram bot token (from @BotFather).

## `telegramChatId` (type: `string`):

Telegram chat or channel ID. Required when a Telegram token is set.

## `slackWebhookUrl` (type: `string`):

Slack incoming webhook URL.

## `discordWebhookUrl` (type: `string`):

Discord incoming webhook URL.

## `whatsappAccessToken` (type: `string`):

WhatsApp Cloud API access token. The recipient must have messaged your business number within the last 24h.

## `whatsappPhoneNumberId` (type: `string`):

Your WhatsApp Business phone-number ID. Required when a WhatsApp token is set.

## `whatsappTo` (type: `string`):

Recipient phone in E.164 format without + (e.g. "15125550100"). Separate several with commas.

## `webhookUrl` (type: `string`):

Receives a JSON POST with every summary row and the report link after the scan (n8n / Make / Zapier / your own app).

## `webhookHeaders` (type: `object`):

Optional JSON object of custom headers (e.g. {"Authorization":"Bearer ..."}).

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

Leave empty: the actor picks the network itself so every grid point is searched as a local customer would see it. Set it only if your runs report that points could not be read.

## Actor input object example

```json
{
  "businessName": "Loyal Coffee",
  "keywords": [
    "coffee shop near me"
  ],
  "location": "Colorado Springs, CO",
  "matchAllBranches": false,
  "gridSize": 5,
  "radiusMiles": 2,
  "gridShape": "square",
  "separateGrids": false,
  "zoom": 13,
  "maxRank": 20,
  "language": "en",
  "compareWithPrevious": true,
  "includePointRows": false,
  "generateHeatmaps": true,
  "notifyOn": "always",
  "dropThreshold": 1
}
```

# Actor output Schema

## `summaries` (type: `string`):

No description

## `points` (type: `string`):

No description

## `report` (type: `string`):

No description

## `allItems` (type: `string`):

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "businessName": "Loyal Coffee",
    "keywords": [
        "coffee shop near me"
    ],
    "location": "Colorado Springs, CO"
};

// Run the Actor and wait for it to finish
const run = await client.actor("corvuslab/google-maps-geo-grid-rank-tracker").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 = {
    "businessName": "Loyal Coffee",
    "keywords": ["coffee shop near me"],
    "location": "Colorado Springs, CO",
}

# Run the Actor and wait for it to finish
run = client.actor("corvuslab/google-maps-geo-grid-rank-tracker").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 '{
  "businessName": "Loyal Coffee",
  "keywords": [
    "coffee shop near me"
  ],
  "location": "Colorado Springs, CO"
}' |
apify call corvuslab/google-maps-geo-grid-rank-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,corvuslab/google-maps-geo-grid-rank-tracker"
        }
    }
}
```

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/uWZW48cerWXIk6wEI/builds/Le0GUXCzndFmme5SG/openapi.json
