Skip to content

Latest commit

 

History

History
190 lines (138 loc) · 7.07 KB

README.md

File metadata and controls

190 lines (138 loc) · 7.07 KB

GeoGridAPI Python SDK

PyPI Python

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-geogridapi wraps it as LangChain tools

Install

pip install geogridapi

Quickstart

Create 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 ahead

A 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.

Async

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())

Usage

# 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"])

Grid formats

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.

Errors

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.

Webhooks

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 "", 200

verify_webhook_signature() returns a boolean instead. Signatures older than 5 minutes are refused (tolerance).

Also available

  • 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

License

MIT