# Google Maps Rank Tracker (Geo-Grid) (`steadyscrape/google-maps-rank-tracker`) Actor

Where a business ranks on Google Maps for a keyword at every point of a grid around it: rank per point, local pack, average rank, share of local voice, the competitors that outrank it and a heatmap. Matched by place id. A pay-per-point Local Falcon alternative.

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

## Pricing

from $3.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.

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 Rank Tracker (Geo-Grid) — local SEO heatmaps

See **where a business ranks on Google Maps for a keyword at every point of a grid around it** — the way a customer
standing on each corner of town would see it. For each grid point you get the business's **rank**, whether it is in
the **local pack** (top 3), and **who ranks above it there**. For each keyword you get the **average rank**, the
**share of local voice**, the **competitors that beat it most often**, and a **heatmap** image.

A pay-per-point alternative to Local Falcon, BrightLocal grids and other geo-grid rank trackers: no subscription, no
login, no browser, no Google API key.

### What it is for

- **Local SEO agencies**: monthly geo-grid reports for every client and keyword, ready to chart.
- **Business owners**: see the neighbourhoods where you vanish from Google Maps, and who takes those customers.
- **Before / after**: run the same grid before and after a Google Business Profile change, new reviews or a new page.
- **Competitor research**: the competitor leaderboard tells you which places own the local pack around you.

### How it works

1. You give the business (its **Google Maps link**, a `maps.app.goo.gl` share link, its **place id** or its **CID**),
   one or more **keywords**, a **grid size** (3×3 to 13×13) and the **distance between points**.
2. The Actor looks the business up on Google Maps and centres the grid on it (or on a centre you give).
3. From every grid point it runs the keyword search Google Maps runs for someone looking at the map there (the zoom
   is yours to set; 13 is the usual one), and finds the business **by its place id** among the results — never by its
   name, so a branch or a namesake is never mistaken for it.
4. It writes one row per point and one summary per keyword, plus a heatmap.

**Ranks 1–20 by default.** Google Maps lists twenty places a page. Turn on **Look down to rank 40** to read a second
page at the points where the business is not in the first twenty.

### Input example

```json
{
    "businesses": ["https://www.google.com/maps/place/Swish+Dental+Downtown/data=!4m2!3m1!1s0x8644b5b434046875:0x644e6eb422388b7a"],
    "keywords": ["dentist", "cosmetic dentist"],
    "gridSize": "7x7",
    "gridSpacing": 1,
    "distanceUnit": "km",
    "zoom": 13,
    "checkTop40": false
}
```

### Output

Every row has a `rowType`: `point` (one keyword from one grid point), `summary` (one keyword over the whole grid) or
`business` (an input that did not resolve).

#### A grid point

```json
{
    "rowType": "point",
    "status": "ok",
    "businessName": "Swish Dental Downtown",
    "placeId": "ChIJdWgENLS1RIYReos4IrRuTmQ",
    "cid": "7227836171923524474",
    "keyword": "dentist",
    "row": 2, "col": 3,
    "lat": 30.267404, "lng": -97.682738,
    "distance": 6, "distanceUnit": "km",
    "rank": 32,
    "inTop3": false,
    "inTop20": false,
    "searchedDepth": 40,
    "resultsSeen": 40,
    "topResults": [
        { "position": 1, "name": "Brident Dental & Orthodontics", "placeId": "ChIJ…", "cid": "…", "rating": 4.1, "reviewCount": 185, "category": "Dentist" }
    ],
    "mapsUrl": "https://www.google.com/maps/search/dentist/@30.267404,-97.682738,13z"
}
```

`rank` is `null` when the business is not within the depth searched (and `inTop20` is `false`): it is never a made-up
number. On a `blocked` point nothing was measured, so `rank`, `inTop3` and `inTop20` are all `null`.

#### A keyword summary

| field | meaning |
|---|---|
| `scannedPoints`, `foundPoints`, `top3Points` | points searched, points where the business appears, points where it is in the local pack |
| `averageRank` | average rank over the points where it appears |
| `averageRankWithMisses` | average rank counting a point where it does not appear as one past the depth searched (21 or 41) — what local rank tools call ARP |
| `shareOfLocalVoice` | % of points where the business is in the local pack (top 3) |
| `competitors` | up to 10 places, ranked by how often they are in the local pack across the grid, with their average rank |
| `heatmapUrl` | an SVG of the grid, each point coloured and labelled with the rank |

### Pricing

Pay per event, no start fee:

| event | price |
|---|---|
| `grid-point` — one keyword searched from one grid point | **US$0.003** |
| `extra-depth-point` — the second page (ranks 21–40) at a point, only when you ask for it and only where needed | US$0.002 |

A 7×7 grid for one keyword is 49 points = **US$0.147**. Summaries, heatmaps, unresolved businesses and blocked
points are free. The spending limit of your run is respected **before** any search is sent: the Actor plans only the
points your limit pays for (counting the second page where you asked for it) and says in the summary which ones it
left out.

### Stops when Google asks for verification

The Actor reads the same answers Google Maps gives its own web page, at a moderate pace. **If Google ever asks for
verification** — its "unusual traffic" page, a CAPTCHA, a JavaScript check, or any answer that is not Google Maps'
normal data — **the run stops sending requests** and every point left is a free `blocked` row that says so. It never
solves, bypasses or replays a challenge, and it does not hide where it asks from: the grid's coordinates travel in the
map view, as they do when you move the map yourself. Plain overloads (HTTP 429 or 5xx without a challenge) are
retried through Apify's datacenter proxy; points that still get no answer are free.

### Limits

- Up to 10 businesses and 10 keywords per run; grids from 3×3 to 13×13; points 0.1 to 20 km (or miles) apart.
- Ranks are those of Google Maps' place list for the search around each point (the "Places" list a customer scrolls).
  Paid ads are not counted.
- Results depend on the zoom and on Google's personalisation; this Actor sees them as a signed-out visitor.

### Is it legal?

It reads publicly visible search results, at a pace a person browsing the map could produce, without logging in and
without collecting personal data. Use the data in line with Google's terms and the laws that apply to you.

# Actor input Schema

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

The business to track, one per line: its <b>Google Maps link</b> (the place page, or a <code>maps.app.goo.gl</code> share link), its <b>place id</b> (<code>ChIJ…</code>) or its <b>CID</b>. The business is matched by these ids, never by its name, so a branch with the same name is never mistaken for yours. Up to 10.

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

What customers type in Google Maps, one per line (<code>dentist</code>, <code>emergency dentist</code>, <code>teeth whitening</code>). Each keyword is searched from every grid point. Up to 10.

## `gridSize` (type: `string`):

Points per side of the square grid around the business. Each point is one search for each keyword: a 7x7 grid with two keywords is 98 searches.

## `gridSpacing` (type: `number`):

From one grid point to the next, in the unit below (0.1 to 20). 1 km covers a neighbourhood with a 7 × 7 grid (6 km across); a service-area business wants 3–5 km.

## `distanceUnit` (type: `string`):

No description

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

The Google Maps zoom each point is searched at, as a phone would show it: 13 is part of a city (the usual setting of local rank tools), 15 a neighbourhood, 17 a few streets.

## `checkTop40` (type: `boolean`):

Google Maps lists twenty places a page. Off: where the business is not in the first twenty, its rank is empty. On: a second page is read at those points (charged as an extra page) to find ranks 21–40.

## `center` (type: `string`):

<code>latitude, longitude</code> to centre the grid elsewhere, such as a neighbourhood you want to win. Empty: the business's own location.

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

Language of names and categories, as a code: <code>en</code>, <code>es</code>, <code>de</code>, <code>pt-BR</code>.

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

Two-letter country code Google answers for (<code>us</code>, <code>gb</code>, <code>au</code>). The grid's own location decides which places appear.

## Actor input object example

```json
{
  "businesses": [
    "https://www.google.com/maps/place/Swish+Dental+Downtown/data=!4m2!3m1!1s0x8644b5b434046875:0x644e6eb422388b7a"
  ],
  "keywords": [
    "dentist"
  ],
  "gridSize": "5x5",
  "gridSpacing": 1,
  "distanceUnit": "km",
  "zoom": 13,
  "checkTop40": false,
  "center": "",
  "language": "en",
  "country": "us"
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

## `summary` (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 = {
    "businesses": [
        "https://www.google.com/maps/place/Swish+Dental+Downtown/data=!4m2!3m1!1s0x8644b5b434046875:0x644e6eb422388b7a"
    ],
    "keywords": [
        "dentist"
    ],
    "gridSize": "5x5",
    "gridSpacing": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/google-maps-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 = {
    "businesses": ["https://www.google.com/maps/place/Swish+Dental+Downtown/data=!4m2!3m1!1s0x8644b5b434046875:0x644e6eb422388b7a"],
    "keywords": ["dentist"],
    "gridSize": "5x5",
    "gridSpacing": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/google-maps-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 '{
  "businesses": [
    "https://www.google.com/maps/place/Swish+Dental+Downtown/data=!4m2!3m1!1s0x8644b5b434046875:0x644e6eb422388b7a"
  ],
  "keywords": [
    "dentist"
  ],
  "gridSize": "5x5",
  "gridSpacing": 1
}' |
apify call steadyscrape/google-maps-rank-tracker --silent --output-dataset

```

## MCP server setup

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