Overview
Local businesses need to know where they rank in Google Map Pack results across their service area, not just from one location. This workflow runs at 6 AM daily, queries Google for your target keywords from a grid of geo-points across your service area, records your position in the local pack at each point, computes rank changes from the previous day, and generates a heatmap dataset showing where you rank well and where competitors dominate. Track local SEO progress with daily data instead of monthly spot checks.
Trigger
Cron schedule (daily at 6 AM UTC)
Schedule
Daily at 6 AM UTC
Workflow Steps
Define grid points
Load the grid of lat/lng points covering your service area. Each point represents a simulated search location.
Query each grid point
For each point and target keyword, search Google via Scavio. Extract local pack results and your position.
Record positions
Store the rank at each grid point with timestamp. Track which competitors appear above and below you.
Compute daily changes
Compare today's positions against yesterday's data. Flag grid points where rank improved or dropped.
Generate heatmap data
Output a JSON dataset of grid points with rank values, suitable for rendering a visual heatmap.
Python Implementation
import requests, os, json
from datetime import datetime
H = {"x-api-key": os.environ["SCAVIO_API_KEY"]}
BUSINESS_NAME = "Acme Plumbing"
KEYWORDS = ["plumber near me", "emergency plumber", "plumbing repair"]
# Grid points covering service area (lat, lng, label)
GRID_POINTS = [
{"label": "Downtown", "geo": "downtown Austin TX"},
{"label": "North", "geo": "north Austin TX"},
{"label": "South", "geo": "south Austin TX"},
{"label": "East", "geo": "east Austin TX"},
{"label": "West", "geo": "west Austin TX"},
]
def check_rank(keyword, geo_point):
"""Search from a geo-point and find business rank in results."""
query = f"{keyword} {geo_point['geo']}"
r = requests.post("https://api.scavio.dev/api/v1/search", headers=H,
json={"platform": "google", "query": query}, timeout=10).json()
local = r.get("local_results", r.get("organic", []))[:10]
rank = None
for i, item in enumerate(local):
title = item.get("title", "").lower()
if BUSINESS_NAME.lower() in title:
rank = i + 1
break
return {
"keyword": keyword,
"geo": geo_point["label"],
"rank": rank,
"top_3": [item.get("title", "")[:50] for item in local[:3]],
"checked_at": datetime.utcnow().isoformat()
}
heatmap_data = []
for kw in KEYWORDS:
for point in GRID_POINTS:
result = check_rank(kw, point)
heatmap_data.append(result)
rank_str = str(result["rank"]) if result["rank"] else "not found"
print(f"[{result['geo']}] {kw}: rank {rank_str}")
found = sum(1 for d in heatmap_data if d["rank"] is not None)
print(f"\nGrid checks: {len(heatmap_data)} | Found in results: {found}")JavaScript Implementation
const H = {"x-api-key": process.env.SCAVIO_API_KEY, "Content-Type": "application/json"};
const BUSINESS_NAME = "Acme Plumbing";
const KEYWORDS = ["plumber near me", "emergency plumber", "plumbing repair"];
const GRID_POINTS = [
{label: "Downtown", geo: "downtown Austin TX"},
{label: "North", geo: "north Austin TX"},
{label: "South", geo: "south Austin TX"},
{label: "East", geo: "east Austin TX"},
{label: "West", geo: "west Austin TX"},
];
async function checkRank(keyword, geoPoint) {
const r = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST", headers: H,
body: JSON.stringify({platform: "google", query: `${keyword} ${geoPoint.geo}`})
}).then(r => r.json());
const local = (r.local_results || r.organic || []).slice(0, 10);
let rank = null;
local.forEach((item, i) => {
if ((item.title || "").toLowerCase().includes(BUSINESS_NAME.toLowerCase())) rank = i + 1;
});
return {
keyword, geo: geoPoint.label, rank,
top3: local.slice(0, 3).map(i => (i.title || "").slice(0, 50)),
checkedAt: new Date().toISOString()
};
}
(async () => {
const heatmapData = [];
for (const kw of KEYWORDS) {
for (const point of GRID_POINTS) {
const result = await checkRank(kw, point);
heatmapData.push(result);
console.log(`[${result.geo}] ${kw}: rank ${result.rank || "not found"}`);
}
}
const found = heatmapData.filter(d => d.rank !== null).length;
console.log(`\nGrid checks: ${heatmapData.length} | Found in results: ${found}`);
})();Platforms Used
Web search with knowledge graph, PAA, and AI overviews