The official Python client for GeoGridAPI, the geo-grid rank tracking API for Google Maps (local SEO).
Send a Google Business Profile and a keyword: GeoGridAPI measures the listing's rank in the Google Maps results at every point of a grid around it, and returns the rank matrix, the competitors ranking ahead, the change since the previous grid and a PNG heatmap. 1 credit = 1 grid point.
- Sync and async clients (
httpx), fully typed - Automatic retries with an idempotency key: a retried request never creates or charges a second grid
grids.run()starts a grid and waits for the result- Webhook signature verification
- For AI agents:
langchain-geogridapiwraps it as LangChain tools
pip install geogridapiCreate an account on geogridapi.io (500 free credits), then an API key in the console. A gga_live_ key measures real grids and spends credits. A gga_test_ key returns free sample grids, handy while you build.
from geogridapi import GeoGridAPI
gga = GeoGridAPI() # reads the GEOGRIDAPI_API_KEY environment variable
grid = gga.grids.run(
query="Joe's Pizza, New York", # or place_id="ChIJ..."
keyword="pizza",
size=7, # 7 x 7 = 49 credits
radius_km=3,
)
print(grid["summary"])
# {'average_rank': 4.3, 'average_rank_with_misses': 4.6, 'top3_points': 28, 'found_points': 48}
print(grid["ranks"]) # 7 x 7 matrix, row 0 = north, None = not in the top 20
print(grid["competitors"]) # who ranks aheadA grid takes 20 seconds to 2 minutes. grids.run() polls until it is completed or failed (a failed grid is refunded). To start it without waiting, use grids.create() then grids.get(), or pass a webhook_url.
import asyncio
from geogridapi import AsyncGeoGridAPI
async def main() -> None:
async with AsyncGeoGridAPI() as gga:
grid = await gga.grids.run(place_id="ChIJ...", keyword="pizza")
print(grid["summary"])
asyncio.run(main())# Find a listing and its place_id (free)
places = gga.places.search("Joe's Pizza, New York")["data"]
# Exact cost before running (free)
estimate = gga.grids.estimate(place_id=places[0]["place_id"], keyword="pizza", shape="circle", density="medium")
print(estimate["credits"], estimate.get("balance_after"))
# Start a grid without waiting
pending = gga.grids.create(place_id="ChIJ...", keyword="pizza", webhook_url="https://example.com/hooks/geogrid")
grid = gga.grids.wait(pending["id"])
# PNG heatmap of a completed grid (free)
with open("map.png", "wb") as f:
f.write(gga.grids.image(grid["id"], lang="en"))
# Ranking trend of the same map over time (free)
history = gga.history.get(place_id="ChIJ...", keyword="pizza")
print(history["change"]) # average_rank < 0 = better
# Up to 50 grids in one call: one listing x several keywords, or several listings x one keyword
batch = gga.batches.run(
size=5,
items=[
{"place_id": "ChIJ...", "keyword": "pizza"},
{"place_id": "ChIJ...", "keyword": "pizza delivery"},
],
)
# Repeat a grid every week or every month
schedule = gga.schedules.create(grid_id=grid["id"], frequency="weekly", weekday=1)
gga.schedules.update(schedule["id"], status="paused")
# Every grid, page after page
for g in gga.grids.list_all(keyword="pizza"):
print(g["id"], g["summary"])
# Credits left
print(gga.balance.get()["points"])| Parameter | Values |
|---|---|
shape |
square (default), circle, diamond, hexagon, cross, ellipse, rectangle |
size |
Square only: 3, 5, 7 (default) or 9 → 9, 25, 49 or 81 credits |
density |
Other shapes: low, medium (default) or high, 13 to 49 points |
radius_km |
0.5 to 30 km from center to edge (other shapes: from 1 km) |
language, country |
2-letter codes |
center |
{"lat": ..., "lng": ...}, defaults to the listing |
Points that fall at sea are neither measured nor charged.
Every API error is a GeoGridAPIError with status, code and message, and a subclass per status:
from geogridapi import InsufficientCreditsError, NotFoundError
try:
gga.grids.run(query="Unknown place", keyword="pizza")
except InsufficientCreditsError:
print("Top up at https://geogridapi.io")
except NotFoundError as error:
print(error.code) # place_not_found| Class | Status |
|---|---|
InvalidRequestError |
400 |
AuthenticationError |
401 |
InsufficientCreditsError |
402 |
NotFoundError |
404 |
ConflictError |
409 (grid_not_ready, already_scheduled...) |
UnprocessableEntityError |
422 (schedule_limit) |
RateLimitError |
429 |
APIError |
5xx |
APIConnectionError |
network error or timeout |
WaitTimeoutError |
run()/wait() gave up; the grid keeps running |
Network errors, 429 and 5xx are retried twice with backoff (Retry-After is honored). Grid and batch creations send an Idempotency-Key, so these retries never create or charge a second grid. The key is new for each call: when your own code retries a creation (a queue job, a cron), pass your own idempotency_key, for example an order or job id:
gga.grids.run(place_id="ChIJ...", keyword="pizza", idempotency_key=f"weekly-report-{job_id}")While run() or wait() waits, network errors, 429 and 5xx are waited out until the timeout. If waiting still fails, the error carries grid_id (or batch_id): the grid exists, read it later with grids.get() instead of running it again. A failed grid has status == "failed" and error == {"code": ..., "message": ...}.
Set max_retries and timeout (seconds) on the client.
When a grid ends, GeoGridAPI posts a grid.completed or grid.failed event to its webhook_url, signed with your whsec_ secret (console → Webhook). Verify it against the raw body:
from flask import Flask, request
from geogridapi import WebhookSignatureError, construct_event
app = Flask(__name__)
@app.post("/hooks/geogrid")
def geogrid_webhook():
try:
event = construct_event(request.get_data(), request.headers.get("GeoGridAPI-Signature"), WEBHOOK_SECRET)
except WebhookSignatureError:
return "", 400
if event["type"] == "grid.completed":
print(event["data"]["summary"])
return "", 200verify_webhook_signature() returns a boolean instead. Signatures older than 5 minutes are refused (tolerance).
- LangChain:
pip install langchain-geogridapi - MCP server for Claude, ChatGPT, Cursor and any MCP client:
https://api.geogridapi.io/mcp - Node.js:
npm install geogridapi - No-code: n8n node, Apify Actor, Make, Zapier (guide)
- API reference · OpenAPI specification