Documentation
Une API REST et un serveur MCP pour les cartes de classement local. 1 crédit = 1 point de la grille.
Démarrage rapide
1. Créez un compte et une clé de test (gga_test_) dans la console. 2. Envoyez une fiche Google, un mot-clé et le format de la carte à POST /v1/grids. 3. Lisez le résultat avec GET /v1/grids/{id} ou recevez-le par webhook.
curl https://api.geogridapi.io/v1/grids \
-H "Authorization: Bearer gga_test_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: plombier-vauban-2026-10" \
-d '{
"place_id": "ChIJ…",
"keyword": "plombier",
"size": 7,
"radius_km": 3,
"webhook_url": "https://example.com/webhooks/geogridapi"
}'L’API répond tout de suite 202 avec l’identifiant de la grille, statut pending. La mesure prend de 20 secondes à 2 minutes selon la taille.
curl https://api.geogridapi.io/v1/grids/3f2c… -H "Authorization: Bearer gga_test_…"- Tutoriel : un suivi de rang en ~20 lignes (en anglais)Node.js ou Python : clé de test, attente du résultat ou webhook signé, carte en couleurs dans le terminal et image PNG.
Authentification
Chaque requête porte une clé dans l’en-tête Authorization: Bearer. Les clés gga_live_ mesurent de vraies grilles et débitent vos crédits (1 crédit = 1 point de la grille). Les clés gga_test_ renvoient des grilles fictives, gratuites et déterministes, pour intégrer sans payer. Une clé n’est affichée qu’une fois, à sa création ; une clé révoquée cesse de fonctionner immédiatement.
Créer une grille
POST /v1/grids. Indiquez soit place_id, soit query (nom et ville) : jamais les deux.
| Paramètre | Type | Description |
|---|---|---|
place_id | string | Identifiant Google de la fiche (ChIJ…). Trouvez-le avec GET /v1/places/search. |
query | string | Nom et ville de la fiche ; la première correspondance est mesurée. |
center | { lat, lng } | Facultatif : centre de la grille. Évite la résolution de la fiche. |
keyword | string | Mot-clé recherché sur Google Maps (1 à 120 caractères). |
shape | string | Forme de la grille : square (par défaut), circle, diamond, hexagon, cross, ellipse ou rectangle. |
size | 3 | 5 | 7 | 9 | Carré seulement : côté de la grille, 7 par défaut. Coût : size × size points. Interdit avec une autre forme. |
density | low | medium | high | Autres formes seulement : densité du treillis, medium par défaut. Points : cercle 13 / 29 / 49, losange 13 / 25 / 41, hexagone 17 / 27 / 47, croix 21 / 33 / 45, ellipse 19 / 35 / 45, rectangle 15 / 35 / 45. Interdit avec le carré. |
radius_km | number | Distance du centre au bord : de 0,5 à 30 km pour le carré (1,5 / 2,5 / 3,5 / 4,5 km par défaut selon la taille), de 1 à 30 km pour les autres formes (par défaut, la moitié de la plus grande dimension du treillis, en km). |
language | string | Langue de la recherche (code à 2 lettres). Par défaut : la langue du compte. |
country | string | Pays de la recherche (code ISO à 2 lettres), facultatif. |
webhook_url | string | URL HTTPS publique appelée à la fin de la grille. Par défaut : celle du compte. |
En-tête Idempotency-Key (facultatif) : une même clé renvoie la grille déjà créée, sans nouveau débit.
curl https://api.geogridapi.io/v1/grids \
-H "Authorization: Bearer gga_test_…" \
-H "Content-Type: application/json" \
-d '{
"query": "Plomberie Vauban, Lille",
"keyword": "plombier",
"shape": "circle",
"density": "medium",
"radius_km": 3.5
}'Sur le littoral, les points qui tombent en mer ne sont ni mesurés ni débités : points_total peut être inférieur au compte de la forme.
Prix exact avant de lancer
POST /v1/grids/estimate prend le même corps que POST /v1/grids et renvoie le nombre de points, les crédits et le solde après, sans rien réserver ni débiter. Carré : size × size points, sans appel au moteur. Autres formes : les points en mer sont retirés (points_at_sea), la fiche est donc placée comme pour une recherche (solde positif, 30 recherches par minute) ; sinon exact vaut false et points est une borne haute. Avec une clé de test, credits vaut 0.
curl https://api.geogridapi.io/v1/grids/estimate \
-H "Authorization: Bearer gga_live_…" \
-H "Content-Type: application/json" \
-d '{ "query": "Plomberie Vauban, Lille", "keyword": "plombier", "shape": "circle" }'{
"object": "estimate",
"shape": "circle", "density": "medium", "rows": 7, "cols": 7, "radius_km": 3.5,
"points": 24, "points_at_sea": 5, "exact": true,
"credits": 24, "balance": 9976, "balance_after": 9952, "enough": true,
"place": { "place_id": "ChIJ…", "name": "Plomberie Vauban", "lat": 50.63, "lng": 3.06 }
}L’objet grille
ranks est une matrice rows × cols (size × size pour le carré) : rangée 0 au nord, colonne 0 à l’ouest. Chaque case donne la position de la fiche dans les résultats Google Maps à ce point ; null signifie absente du top 20. points détaille chaque point mesuré avec ses coordonnées et le top 3 des fiches.
Pour une forme autre que le carré, une case hors de la forme (ou en mer) vaut aussi null dans ranks : c’est points qui fait foi, une case absente de points n’a pas été mesurée. shape et density rappellent le format ; pour une forme, size vaut max(rows, cols).
summary : average_rank (moyenne sur les points où la fiche apparaît), average_rank_with_misses (absences comptées 21), top3_points, found_points.
{
"id": "3f2c9b1e-…",
"object": "grid",
"status": "completed",
"mode": "live",
"place": { "place_id": "ChIJ…", "name": "Plomberie Vauban", "lat": 50.63, "lng": 3.06 },
"keyword": "plombier",
"shape": "square",
"density": null,
"size": 7,
"rows": 7,
"cols": 7,
"radius_km": 3,
"points_total": 49,
"points_charged": 49,
"summary": { "average_rank": 4.3, "average_rank_with_misses": 4.6, "top3_points": 28, "found_points": 48 },
"ranks": [[4, 3, 2, 2, 3, 5, 8], …, [11, 8, 6, 5, 9, 15, null]],
"points": [{ "row": 0, "col": 0, "lat": 50.657, "lng": 3.024, "rank": 4,
"top": [{ "rank": 1, "place_id": "ChIJ…", "name": "…" }, …] }, …],
"error": null,
"created_at": "2026-10-16T10:00:00Z",
"completed_at": "2026-10-16T10:00:41Z"
}GET /v1/grids liste vos grilles, les plus récentes d’abord (limit jusqu’à 100, pagination par before = created_at du dernier élément), sans le détail des points. Filtres : place_id (une fiche) et keyword (mot-clé exact, sans tenir compte de la casse).
competitors : qui est devant. Les fiches du top 3 cumulées sur la carte, la plus présente d’abord : top3_points (points où elle est dans le top 3), first_points (points où elle est première), ahead_points (points où elle est classée devant votre fiche).
previous : l’écart avec la carte précédente de la même fiche (même mot-clé, format, rayon et centre), sinon null. changes.average_rank négatif = mieux ; rank_changes donne les places gagnées par case (positif = mieux, une absence compte 21). Présent dans GET /v1/grids/{id} et dans le webhook.
"competitors": [
{ "place_id": "ChIJ…", "name": "Plomberie Express", "top3_points": 29, "first_points": 12, "ahead_points": 21 }
],
"previous": {
"grid_id": "41d0…", "created_at": "2026-10-10T05:00:12Z",
"average_rank": 7.2, "top3_points": 10, "found_points": 40,
"changes": { "average_rank": -2.4, "top3_points": 14, "found_points": 8 },
"ranks": [[…]], "rank_changes": [[2, 0, -1, …]],
"points": { "improved": 27, "worsened": 6, "unchanged": 16 }
}L’image de la carte
GET /v1/grids/{id}/image renvoie l’image PNG d’une grille terminée (1200 × 1092) : la carte, les pastilles de rang, le mot-clé, la fiche, la date et les trois chiffres clés. lang=fr ou en (sinon la langue du compte). Prête pour un rapport client ; l’outil MCP get_grid la joint à sa réponse.
curl "https://api.geogridapi.io/v1/grids/8f3c2a6e-…/image?lang=fr" \
-H "Authorization: Bearer gga_live_…" \
-o carte.pngGratuite. 409 grid_not_ready tant que la grille tourne, grid_failed si elle a échoué. 30 images par minute et 500 par jour par compte.
Évolution d’une fiche
GET /v1/history renvoie la série des grilles terminées d’une même carte (même fiche, mot-clé, format, rayon et centre), de la plus ancienne à la plus récente, et l’écart entre la première et la dernière (change.average_rank négatif = mieux). Désignez la carte par place_id et keyword (la grille la plus récente fixe le format) ou par grid_id. limit de 1 à 100, 50 par défaut ; has_more indique des grilles plus anciennes. Gratuit.
curl "https://api.geogridapi.io/v1/history?place_id=ChIJ…&keyword=plombier" \
-H "Authorization: Bearer gga_live_…"{
"object": "history",
"reference_grid_id": "8f3c…",
"keyword": "plombier", "shape": "square", "size": 7, "radius_km": 3,
"data": [
{ "grid_id": "41d0…", "created_at": "2026-09-01T05:00:12Z", "average_rank": 7.2, "top3_points": 10, "found_points": 40, "points_total": 49 },
{ "grid_id": "8f3c…", "created_at": "2026-10-13T05:00:09Z", "average_rank": 4.3, "top3_points": 28, "found_points": 48, "points_total": 49 }
],
"has_more": false,
"change": { "since": "2026-09-01T05:00:12Z", "average_rank": -2.9, "top3_points": 18, "found_points": 8 }
}Plusieurs grilles en un appel
POST /v1/batches lance jusqu’à 50 grilles au même format : une fiche sur plusieurs mots-clés, ou plusieurs fiches sur un mot-clé. Le format se donne une fois pour le lot (shape, size ou density, radius_km, language, country, webhook_url) ; chaque élément de items porte place_id ou query, center en option, et keyword. L’en-tête Idempotency-Key est accepté.
curl https://api.geogridapi.io/v1/batches \
-H "Authorization: Bearer gga_live_…" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: audit-lille-2026-10" \
-d '{
"size": 7,
"items": [
{ "place_id": "ChIJ…", "keyword": "plombier" },
{ "place_id": "ChIJ…", "keyword": "plombier urgence" },
{ "query": "Plomberie Wazemmes, Lille", "keyword": "plombier" }
]
}'Le lot est refusé d’emblée (402) si le solde ne couvre pas credits_estimated (points de la forme × grilles ; les points en mer ne seront pas débités). Les grilles partent au fil des places libres (10 grilles en cours par compte), chacune comme une grille ordinaire : débit au lancement, remboursement si elle échoue, webhook à la fin.
GET /v1/batches/{id} suit le lot : status running puis completed, counts, et pour chaque grille son statut (queued, pending, running, completed, failed), son grid_id, son summary ou son error. 3 lots en lancement par compte au plus ; une grille qui n’a pas trouvé de place en 2 h est close sans débit (expired), et révoquer la clé qui a lancé le lot arrête ses grilles en attente (key_revoked).
{
"id": "b7a1…", "object": "batch", "status": "running",
"items_count": 3, "credits_estimated": 147, "credits_charged": 49,
"counts": { "queued": 1, "pending": 0, "running": 1, "completed": 1, "failed": 0 },
"items": [
{ "index": 0, "keyword": "plombier", "status": "completed", "grid_id": "8f3c…",
"place": { "place_id": "ChIJ…", "name": "Plomberie Vauban" },
"summary": { "average_rank": 4.3, "top3_points": 28, "found_points": 48 }, "error": null },
{ "index": 1, "keyword": "plombier urgence", "status": "running", "grid_id": "c21e…", … },
{ "index": 2, "keyword": "plombier", "status": "queued", "grid_id": null, … }
]
}Cartes répétées
POST /v1/schedules relance une grille existante chaque semaine (weekday : 1 = lundi … 7 = dimanche) ou chaque mois (month_day : 1 à 28), vers 7 h, heure de Paris. Même fiche, mot-clé, format et rayon ; chaque passage débite ses crédits et appelle votre webhook.
curl https://api.geogridapi.io/v1/schedules \
-H "Authorization: Bearer gga_live_…" \
-H "Content-Type: application/json" \
-d '{ "grid_id": "8f3c2a6e-…", "frequency": "weekly", "weekday": 1 }'GET /v1/schedules liste vos cartes répétées ; PATCH /v1/schedules/{id} avec { "status": "paused" | "active" } les met en pause ou les relance ; DELETE /v1/schedules/{id} les arrête. Solde insuffisant : la carte répétée s’arrête (status stopped) et vous recevez un e-mail ; relancez-la après avoir rechargé.
Trouver une fiche et lire le solde
curl "https://api.geogridapi.io/v1/places/search?q=Plomberie%20Vauban%20Lille&language=fr" \
-H "Authorization: Bearer gga_live_…"GET /v1/balance renvoie votre solde de crédits. Ces deux appels sont gratuits.
Webhooks
À la fin de chaque grille, GeoGridAPI envoie un POST JSON : type grid.completed ou grid.failed, data = l’objet grille. Trois essais : immédiat, après 1 minute, après 10 minutes. Répondez par un code 2xx.
Chaque appel est signé : en-tête GeoGridAPI-Signature: t=<horodatage>,v1=<signature>, où la signature est un HMAC-SHA256 de « t.corps » avec votre secret whsec_ (console, section Webhook). Refusez un horodatage trop ancien.
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the raw request body, exactly as received.
export function verifyGeoGridSignature(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}import hashlib, hmac, time
def verify_geogrid_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(kv.split("=", 1) for kv in header.split(","))
t = int(parts.get("t", "0"))
if not t or abs(time.time() - t) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))Erreurs et limites
Toutes les erreurs ont la forme { "error": { "code", "message" } }.
| Statut | Code | Signification |
|---|---|---|
400 | invalid_body / invalid_json | Corps ou paramètres invalides ; le message nomme le champ. |
401 | invalid_key | Clé absente, inconnue ou révoquée. |
402 | insufficient_points | Crédits insuffisants ; required et balance sont indiqués. |
404 | not_found / place_not_found | Grille inconnue pour ce compte, ou aucune fiche correspondante. |
409 | grid_not_ready / grid_failed / already_scheduled | Image d’une grille en cours ou échouée ; grille déjà répétée (schedule_id indiqué). |
422 | schedule_limit | 100 cartes répétées par compte au plus. |
429 | rate_limited / too_many_open_grids / too_many_open_batches | Plus de 60 requêtes par minute et par clé, 10 grilles en cours ou 3 lots en lancement. |
503 | engine_unavailable | Moteur momentanément indisponible ; réessayez (en-tête Retry-After). |
Une grille qui échoue est remboursée automatiquement. Prix : 1 crédit par point de la grille, packs de crédits dès 20 € HT, crédits sans expiration, 500 crédits offerts dès l’inscription.
Serveur MCP
Le serveur MCP donne la grille à Claude, ChatGPT, Cursor et à tout client MCP compatible Streamable HTTP. Outils : find_business, estimate_grid (prix exact), run_grid (mêmes formats que l’API : shape, size ou density, radius_km), run_grids et get_batch (jusqu’à 50 grilles en un appel), get_grid, list_grids (grilles passées), get_history (évolution), schedule_grid, list_schedules, update_schedule et delete_schedule (grilles répétées), get_balance. Une grille terminée revient avec l’image de la carte, qui est devant et l’écart avec la carte précédente. Il utilise vos clés : une clé de test pour essayer gratuitement.
Six raccourcis prêts à l’emploi : dans Claude, menu + puis GeoGridAPI ; dans Claude Code, commandes /mcp__geogridapi__… Local visibility audit (local_audit), Who ranks ahead (who_ranks_ahead), Several keywords (several_keywords, jusqu’à 50 mots-clés), Ranking trend (ranking_trend), Track my listing (track_listing, chaque semaine ou chaque mois) et Client report (client_report). Chacun demande la fiche et le mot-clé, annonce le prix avant de lancer une grille, puis répond dans la langue du mot-clé.
Claude (claude.ai, application) et ChatGPT : ajoutez un connecteur personnalisé avec l’adresse https://api.geogridapi.io/mcp. La connexion ouvre GeoGridAPI : connectez-vous, puis autorisez l’assistant. Les grilles lancées ainsi sont réelles et débitent vos crédits.
Claude Code : lancez la commande, puis /mcp, geogridapi et Authenticate : la connexion se fait dans le navigateur, sans clé à copier. Sur une machine sans navigateur (CI, serveur), passez une clé dans l’en-tête.
claude mcp add --transport http geogridapi https://api.geogridapi.io/mcpclaude mcp add --transport http geogridapi https://api.geogridapi.io/mcp \
--header "Authorization: Bearer gga_live_…"{
"mcpServers": {
"geogridapi": {
"url": "https://api.geogridapi.io/mcp",
"headers": { "Authorization": "Bearer gga_live_…" }
}
}
}Le serveur figure au registre MCP officiel sous le nom io.geogridapi/geogridapi.
- Page du serveur MCP (en anglais)Les 13 outils et leur coût, l’installation client par client, les clés de test et des exemples de prompts.
SDK et outils
Clients officiels et outils, en open source sur GitHub (github.com/geogridapi). Ils suivent l’API v1 et utilisent les mêmes clés.
npm install geogridapi
pip install "git+https://github.com/geogridapi/geogridapi-python"- SDK Node.js et TypeScript (npm geogridapi)Client typé sans dépendance, ESM et CommonJS. Il attend la fin d’une grille et traverse les erreurs 429 et 5xx.
- SDK Python (geogridapi-python)Client synchrone et asynchrone (httpx). En attendant sa publication sur PyPI, installez-le depuis GitHub.
- Outils LangChain (langchain-geogridapi)Les 13 outils du serveur MCP en outils LangChain pour vos agents, avec une version en lecture seule. Publication sur PyPI à venir.
- Serveur MCP en stdio (geogridapi-mcp)Pour les clients MCP sans Streamable HTTP : npx -y github:geogridapi/geogridapi-mcp, avec GEOGRIDAPI_API_KEY.
- local-rank-heatmapUne commande transforme une fiche et un mot-clé en carte HTML, PNG, CSV et JSON.
- Nœud n8n (n8n-nodes-geogridapi)Les cartes dans vos workflows n8n, sans code.
- Registre MCP officielFiche io.geogridapi/geogridapi, reprise par les annuaires de serveurs MCP.