# Google Maps Geogrid Rank Tracker — Local Rank Heatmap (`yugenox/google-maps-geogrid-rank-tracker`) Actor

Track a business's Google Maps search rank at every point of a 3x3 to 13x13 grid for up to 20 keywords. Rank 1-20 per point with the top 3 competitors, ARP, ATRP, Share of Local Voice, a heatmap and changes since your last scan.

- **URL**: https://apify.com/yugenox/google-maps-geogrid-rank-tracker.md
- **Developed by:** [Yugenox Corp](https://apify.com/yugenox) (community)
- **Categories:** SEO tools, Marketing, Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 grid points

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Google Maps Geogrid Rank Tracker — Local Rank Heatmap

See where a business ranks on Google Maps **across its whole area**, not just from one spot. Give the
business and up to 20 keywords. The Actor lays a grid of 3 x 3 to 13 x 13 points over the map and runs
a Google Maps search from every point. For each point you get the business's **Google Maps search rank at this
point** (1-20, or "20+"), plus the top 3 competitors there. Each keyword also gets a summary with ARP, ATRP,
Share of Local Voice and a heatmap.

No login, no subscription, no browser extension. You pay per grid point, and only for points Google Maps answered.

### Who it's for

- **Local SEO agencies:** monthly or weekly heatmaps for every client and keyword, with numbers you can put
  straight into a report.
- **Multi-location brands and franchises:** which neighbourhoods each location wins, and where a competitor
  owns the map.
- **Single businesses:** dentists, lawyers, plumbers, restaurants, salons. See whether your Google Business
  Profile work moved the needle, block by block.
- **AI agents and dashboards:** clean JSON per point, per keyword and per competitor, through the Apify API or MCP.

### What you get

**One `point` row per keyword and grid point (charged):**

```json
{
  "type": "point",
  "keyword": "pizza",
  "rank": null,
  "rankLabel": "20+",
  "found": false,
  "point": 5, "row": 0, "col": 4,
  "lat": 43.634102, "lng": -79.579741,
  "distanceKm": 4.243, "bearing": "NE",
  "resultsCount": 20,
  "competitors": [
    { "rank": 1, "name": "Pizzaiolo", "placeId": "ChIJw8hf7pY4K4gRFmqnhK1pI0I", "rating": 4.3, "reviewsCount": 90, "category": "Pizza restaurant", "address": "4335 Bloor St W, Etobicoke, ON M9C 2A5" },
    { "rank": 2, "name": "Red Swan Pizza - Rockwood Mall", "rating": 4.6, "reviewsCount": 635, "...": "..." },
    { "rank": 3, "name": "Pizza Wings & More", "rating": 4, "reviewsCount": 103, "...": "..." }
  ],
  "businessName": "Marconi Pizza",
  "businessPlaceId": "ChIJ5wXPnW1HK4gRws7j2NFlViU",
  "searchAreaKm": 3,
  "scannedAt": "2026-10-10T12:57:59.372Z"
}
```

**One `summary` row per keyword (free):**

```json
{
  "type": "summary",
  "keyword": "pizza",
  "pointsScanned": 25, "pointsFound": 17, "foundShare": 68,
  "arp": 9, "atrp": 12.84, "solv": 24,
  "top3Points": 6, "top10Points": 10, "bestRank": 2, "worstRank": 20,
  "complete": true,
  "heatmap": [[11, 15, 16, 18, null], [13, 2, 3, 3, 20], [null, 2, 2, 3, 18], [null, 9, 6, 4, 8], [null, null, null, null, null]],
  "heatmapText": " 11  15  16  18 20+\n 13   2   3   3  20\n20+   2   2   3  18\n20+   9   6   4   8\n20+ 20+ 20+ 20+ 20+",
  "gridSize": 5, "radiusKm": 3, "spacingKm": 1.5
}
```

The same heatmap, as you'd read it (north at the top):

```
 11  15  16  18 20+
 13   2   3   3  20
20+   2   2   3  18
20+   9   6   4   8
20+ 20+ 20+ 20+ 20+
```

`20+` means not in the top 20 at that point. A `·` (`"-"` in `heatmap`) means there is no number for that
cell: outside a circle grid, not scanned, not answered, or Google Maps had no places at all there.

**`competitor` rows per keyword (free):** the businesses that show up most often in the top 3 across the
grid, each with its own ARP, ATRP, Share of Local Voice and best rank. You can see who beats you, and where.

#### The metrics

| Metric | Meaning |
|---|---|
| **ARP** (average rank position) | Average rank over the points where the business is in the top 20 |
| **ATRP** (average total rank position) | Average rank over every scanned point, counting "not in the top 20" as 21 |
| **SoLV** (Share of Local Voice) | Share of scanned points where the business ranks 1-3, in percent |
| **foundShare** | Share of scanned points where the business is in the top 20, in percent |

Lower ARP and ATRP are better. Higher SoLV and foundShare are better. These are this Actor's own
definitions. Other rank trackers use similar names, but their formulas and grids can differ, so compare
numbers from the same tool over time.

### How to use it

1. **Business:** paste its Google Maps link. On Google Maps, open the business and copy the link from
   the address bar, or use a `maps.app.goo.gl` share link. A place ID (`ChIJ…`) or CID works too. With
   any of these, the business is recognised at every point by Google's own ID, never by its name. As a
   fallback you can type `Name, full address`. The Actor looks it up once and only uses it when both the
   name and the address match (street number and street, or postal code); otherwise it stops and lists
   the closest businesses with their place IDs.
2. **Keywords:** what customers type, e.g. `dentist`, `emergency dentist`, `teeth whitening` (1-20).
3. **Grid:** size (3, 5, 7, 9, 11 or 13 points per side), radius (km or miles), square or circle. The
   grid is centred on the business unless you set a centre.
4. Run it. A 7 x 7 grid with 3 keywords (147 points) takes about 20-40 seconds.

#### Example inputs

```json
{
  "business": "https://www.google.com/maps/place/Midtown+Dental/...",
  "keywords": ["dentist", "emergency dentist", "cosmetic dentist"],
  "gridSize": 7,
  "radius": 3
}
```

```json
{
  "business": "ChIJ64KdcK40K4gRxXMYfG2HKZM",
  "keywords": ["plumber"],
  "gridSize": 9,
  "radius": 10,
  "radiusUnit": "mi",
  "gridShape": "circle",
  "monitorMode": true
}
```

#### Monitor mode: what changed since the last scan

Turn on **Monitor mode** and run the Actor on a schedule (Apify Schedules, weekly or monthly). Each scan
is compared with the previous scan of the same business, keyword and grid:

- every point gets `previousRank`, `rankChange` (positive = moved up) and `movement` (`up`, `down`,
  `same`, `entered` the top 20, `dropped` out of it, or `new` on the first scan);
- every summary gets `changes` (ARP, ATRP, SoLV, foundShare, top-3 points) and counts of improved and declined points;
- the run's key-value store gets a `MONITOR_DELTAS` record with the changes for all keywords.

Scans are kept in a key-value store in your Apify account (`geogrid-rank-monitor` by default; set
**Monitor store name** to keep separate histories, e.g. one per client). Only a scan that got an answer
for every point is saved: one cut short by a timeout or your maximum cost, or with points Google Maps
didn't answer, never replaces the saved one. Changes are free.

### How a point is scanned

Each point is one Google Maps web search for the keyword, centred on that point. The map view it searches
is sized to the grid: twice the distance between points, between 2 and 15 km. This way every point ranks the
businesses around it, not the whole city. With a city-wide view every point would get the same list and
the heatmap would be flat. You can set **Search area per point** yourself (0.5-30 km).

What the rank is, and what it isn't:

- It is the business's **Google Maps search rank at this point**: its position among the 20 places Google
  Maps web search returns for that keyword and map view.
- It is not a reading of the Google Maps app on a particular phone, the 3-pack in regular Google Search, or
  ads. Google personalises results, so a real person may see a slightly different order.
- Google's ranking moves a little from minute to minute. Two scans back to back can differ by a place or
  two at some points. Compare trends over several scans rather than single points.

### Pricing

Pay per event: you pay for grid points, never for summaries, competitors or monitor changes.

| Event | FREE / BRONZE | SILVER | GOLD and above |
|---|---|---|---|
| Grid point (one keyword at one point, with its top 3 competitors) | $0.0015 | $0.0012 | $0.001 |
| Actor start (once per run) | $0.001 | $0.001 | $0.001 |

Examples at the FREE price:

| Scan | Points | Cost |
|---|---|---|
| 3 x 3, 1 keyword | 9 | $0.0145 |
| 7 x 7, 1 keyword | 49 | $0.0745 |
| 7 x 7, 3 keywords | 147 | $0.2215 |
| 13 x 13, 5 keywords | 845 | $1.2685 |

Four weekly 7 x 7 scans of 3 keywords cost about $0.89 a month on FREE, or about $0.59 on GOLD.

You are never charged for:

- points Google Maps doesn't answer (after a network or server error they're retried first);
- points where Google Maps has no places at all for the keyword (there is nothing to rank). A keyword with
  no places at its first points is not scanned further;
- answers the Actor can't read (if Google changes its format, the run stops instead of guessing);
- points outside a circle grid;
- points skipped because the run reached its timeout or your **maximum cost per run**. The Actor never scans a
  point it can't charge, and it saves everything it scanned, even when the timeout stops it mid-keyword;
- grid points on runs where the business can't be found. They stop before the first point, so only the
  $0.001 Actor start applies.

If Google Maps shows a bot check (CAPTCHA or "unusual traffic"), the run stops right there with the points
saved so far. It never retries from other IPs to get around it.

### Tips

- **Pick the radius for the business type.** In a dense city, 1-3 km shows the competition block by block.
  For service-area businesses (plumbers, movers) use 5-15 km or more.
- **Odd grid sizes keep the business in the centre cell.** 7 x 7 is a good default; use 9-13 for a
  finer picture over a wide area.
- **Circle grids** skip the corners, which costs fewer points and often matches a service area better.
- **Same settings every time:** monitor mode compares scans of the same grid only. If you change size,
  radius, shape, centre or search area, a new history starts.
- **Timeout:** about 1 second per 8 points. The default 1 hour covers even 20 keywords on 13 x 13 (3,380 points, about 5 minutes).

### FAQ

**Why is the business "20+" right next to its own location?**
In a dense area more than 20 similar businesses can sit inside that point's map view. The business is
then outside the top 20 there, even close to home. That is exactly what the heatmap is meant to show.

**Can I track a business by name only?**
Yes, with "Name, full address". The name and the address both have to match the business Google finds,
otherwise the run stops and lists the closest matches. A place ID, CID or Google Maps link (including the
"write a review" link) is exact, and recommended for anything you report on.

**Do I need a proxy?**
No setup is needed: the default Apify datacenter proxy works. Only Apify datacenter proxy groups are used.
Residential or SERP proxy groups in the input are replaced by datacenter, because they would cost more per
point than the point itself.

**Is this legal?**
The Actor reads public Google Maps search results, with no login. It collects business listing data
(names, ratings, review counts, categories, addresses), not personal data. You're responsible for how
you use the results in your jurisdiction.

### Output fields

`point` rows: `keyword`, `rank`, `rankLabel`, `found`, `point`, `row`, `col`, `lat`, `lng`, `distanceKm`,
`bearing`, `resultsCount`, `competitors[]`, `businessName`, `businessPlaceId`, `businessCid`, `searchAreaKm`,
`country`, `scannedAt`. Monitor mode adds `previousRank`, `rankChange` and `movement`.

`summary` rows: `pointsScanned`, `pointsFound`, `foundShare`, `arp`, `atrp`, `solv`, `top3Points`,
`top10Points`, `bestRank`, `worstRank`, `pointsInGrid`, `pointsNotAnswered`, `pointsNoResults`, `pointsNotScanned`, `complete`,
`heatmap`, `heatmapText`, the business, the grid (`centerLat`, `centerLng`, `gridSize`, `gridShape`,
`radiusKm`, `spacingKm`, `searchAreaKm`) and, in monitor mode, `previousScanAt`, `changes`, `improvedPoints`,
`declinedPoints` and `unchangedPoints`.

`competitor` rows: `competitorRank`, `name`, `placeId`, `cid`, `rating`, `reviewsCount`, `category`,
`address`, `pointsFound`, `foundShare`, `top3Points`, `solv`, `arp`, `atrp`, `bestRank`.

The run's key-value store also holds `RUN_REPORT`: the matched business, the grid, per-keyword metrics,
the request count and how the run ended.

# Actor input Schema

## `business` (type: `string`):

The business to track. Best: its Google Maps link (open the business on Google Maps and copy the link, or a maps.app.goo.gl share link), its place ID (ChIJ…) or its CID. These are exact: the business is then recognised at every point by Google's own ID, never by name. Fallback: "Name, full address" — looked up once and used only when both the name and the address match (otherwise the run stops and lists the closest businesses with their place IDs).

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

What customers type into Google Maps, one per line (1-20), e.g. "dentist", "emergency dentist", "teeth whitening". Every keyword is searched at every grid point.

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

Points per side: 3, 5, 7, 9, 11 or 13 (3 = 9 points, 7 = 49 points, 13 = 169 points). Each point is one search per keyword. Other numbers are rounded to the nearest of these.

## `radius` (type: `number`):

Distance from the grid center to the middle of its edge (the grid is 2 x radius wide). Points are evenly spaced: a 7 x 7 grid with a 3 km radius puts points 1 km apart. Typical: 1-2 km for a dense city, 5-15 km for a service area.

## `radiusUnit` (type: `string`):

Kilometers or miles.

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

Square: every point of the grid. Circle: only points within the radius of the center (the corners are skipped, so fewer points to pay for).

## `gridCenter` (type: `string`):

"latitude, longitude" of the grid's center, e.g. "43.6532, -79.3832". Leave empty to center the grid on the business itself.

## `monitorMode` (type: `boolean`):

Compare with the previous scan of the same business, keyword and grid: every point gets its previous rank and how it moved (up / down / entered / dropped), every summary gets the change in ARP, ATRP, Share of Local Voice and found share. Scans are kept in a key-value store in your account ("geogrid-rank-monitor"). Run it on a schedule (weekly or monthly) to track progress. Changes are free.

## `competitorsPerKeyword` (type: `integer`):

How many competitor rows to save per keyword: the businesses that show up most often in the top 3 across the grid, with their ARP, ATRP and Share of Local Voice. 0 = none. Competitor rows are free.

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

Google Maps interface language (hl), e.g. en, fr, es, de. Affects category names, not ranks.

## `country` (type: `string`):

Two-letter country code Google Maps searches as (gl), e.g. us, ca, gb. Leave empty to use the business's own country.

## `searchAreaKm` (type: `number`):

Width of the map view each point searches from. Empty = automatic: 2 x the distance between points, between 2 and 15 km, so every point ranks the businesses around it rather than the whole city. Only change it if you know you need a wider or tighter view (0.5-30 km).

## `monitorStoreName` (type: `string`):

Key-value store (in your account) that keeps previous scans for monitor mode. Use a different name to keep separate histories, e.g. per client. Only a-z, 0-9 and "-".

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

Apify datacenter proxies work for Google Maps (and are the cheapest). Points Google doesn't answer are retried after a network or server error and never charged; a bot check stops the run. Only Apify datacenter groups are used: residential or SERP groups are replaced by datacenter (they would cost more per point than the point itself).

## Actor input object example

```json
{
  "business": "ChIJ64KdcK40K4gRxXMYfG2HKZM",
  "keywords": [
    "dentist"
  ],
  "gridSize": 3,
  "radius": 1,
  "radiusUnit": "km",
  "gridShape": "square",
  "monitorMode": false,
  "competitorsPerKeyword": 10,
  "language": "en",
  "monitorStoreName": "geogrid-rank-monitor",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All rows of this run: the business's Google Maps search rank at each grid point, one summary per keyword (ARP, ATRP, Share of Local Voice, heatmap) and the competitors across the grid.

## `run` (type: `string`):

Status and statistics for this run.

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

RUN_REPORT: the business that was matched, the grid, per-keyword metrics, requests and how the run ended.

## `deltas` (type: `string`):

MONITOR_DELTAS (monitor mode, from the second scan on): per keyword, how ARP, ATRP, Share of Local Voice and found share changed and how many points moved up or down.

# 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 = {
    "business": "ChIJ64KdcK40K4gRxXMYfG2HKZM",
    "keywords": [
        "dentist"
    ],
    "gridSize": 3,
    "radius": 1,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/google-maps-geogrid-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 = {
    "business": "ChIJ64KdcK40K4gRxXMYfG2HKZM",
    "keywords": ["dentist"],
    "gridSize": 3,
    "radius": 1,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("yugenox/google-maps-geogrid-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 '{
  "business": "ChIJ64KdcK40K4gRxXMYfG2HKZM",
  "keywords": [
    "dentist"
  ],
  "gridSize": 3,
  "radius": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/google-maps-geogrid-rank-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yugenox/google-maps-geogrid-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/oMl6qbIo5ZckQM06F/builds/05RnYJBOYb2C1wtrB/openapi.json
