Introduction#
The Geogrid API lets you programmatically launch rank tracking scans, retrieve results, and manage your credits. Every endpoint returns JSON and uses standard HTTP status codes.
Launch scans
Run grid scans via API
Real-time results
Get heatmap data instantly
Secure
SHA-256 hashed API keys
Base URL
https://your-domain.com/api/v1Authentication#
All API requests require a Bearer token in the Authorization header. API keys start with gk_live_ and are 48 characters long.
curl https://your-domain.com/api/v1/credits \
-H "Authorization: Bearer gk_live_a1b2c3d4e5f6g7h8i9j0..."Generate API keys from Dashboard → Settings → API Keys. API access requires a paid plan (Freelance, Agency, or Enterprise).
Rate Limits#
Rate limits are enforced per API key, based on your plan. Every response includes rate limit headers.
| Plan | Requests / min | Price |
|---|---|---|
| Free | No API access | $0 |
| Freelance | 60 | $49/mo |
| Agency | 120 | $149/mo |
| Enterprise | 250 | $499/mo |
Response headers on every request:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117When rate limited, you'll receive a 429 response with a Retry-After: 60 header.
Error Handling#
Errors return a structured JSON object with a code and message. Some errors include additional context.
{
"error": {
"code": "insufficient_credits",
"message": "Insufficient credits",
"credits_available": 12,
"credits_required": 169
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_* | Validation error (bad parameters) |
| 401 | missing_api_key | No Authorization header |
| 401 | invalid_api_key | Key not found or malformed |
| 401 | revoked_api_key | Key has been revoked |
| 402 | insufficient_credits | Not enough credits for this operation |
| 403 | plan_required | Paid plan needed for API access |
| 403 | scan_limit_reached | Plan scan quota exceeded |
| 403 | account_blocked | Account suspended |
| 404 | not_found | Resource doesn't exist |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | internal_error | Server error (contact support) |
POST /scans#
Launch a new rank tracking scan. The scan runs synchronously and returns the full grid data when complete. One credit is consumed per API call to Google (cache hits are free).
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| keyword | string | Required | Search query (e.g. "plumber near me"). Max 200 chars. |
| latitude | number | Required | Center latitude (-90 to 90) |
| longitude | number | Required | Center longitude (-180 to 180) |
| target_cid | string | Optional | Google Maps CID of the business to track. Either target_cid or target_name required. |
| target_name | string | Optional | Business name (used for fuzzy matching if no CID) |
| grid_size | number | Optional | Grid resolution: 5, 7, or 13 (default: 13). Credits = grid_size² |
| radius_meters | number | Optional | Scan radius in meters. 500-50000 (default: 5000) |
| tags | string[] | Optional | Tags for organization (max 10, 50 chars each) |
curl -X POST https://your-domain.com/api/v1/scans \
-H "Authorization: Bearer gk_live_a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"keyword": "plumber near me",
"target_name": "John Plumbing LLC",
"latitude": 48.8566,
"longitude": 2.3522,
"grid_size": 7,
"radius_meters": 3000,
"tags": ["paris", "q1-2026"]
}'{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"keyword": "plumber near me",
"latitude": 48.8566,
"longitude": 2.3522,
"radius_meters": 3000,
"grid_size": 7,
"credits_consumed": 34,
"nodes_sampled": 49,
"cache_hits": 15,
"visibility_score": 4.2,
"duration_ms": 12500,
"nodes": [
{ "lat": 48.8612, "lng": 2.3450, "rank": 1 },
{ "lat": 48.8612, "lng": 2.3498, "rank": 3 },
{ "lat": 48.8612, "lng": 2.3546, "rank": null }
],
"created_at": "2026-03-19T14:30:00.000Z"
}GET /scans#
List your scans with pagination and filters. Returns scan metadata without grid data (use GET /scans/:id for full data).
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | number | Optional | Results per page, 1-100 (default: 20) |
| offset | number | Optional | Skip N results (default: 0) |
| status | string | Optional | Filter: completed, partial, failed, running, pending |
| keyword | string | Optional | Filter by keyword (partial match) |
| tag | string | Optional | Filter by tag (exact match) |
| sort | string | Optional | Sort: newest (default), oldest, score |
curl "https://your-domain.com/api/v1/scans?status=completed&limit=5&sort=newest" \
-H "Authorization: Bearer gk_live_a1b2c3d4..."{
"data": [
{
"id": "a1b2c3d4-...",
"keyword": "plumber near me",
"target_name": "John Plumbing LLC",
"latitude": 48.8566,
"longitude": 2.3522,
"radius_meters": 3000,
"grid_size": 7,
"status": "completed",
"nodes_sampled": 49,
"credits_consumed": 34,
"visibility_score": 4.2,
"tags": ["paris"],
"created_at": "2026-03-19T14:30:00.000Z",
"completed_at": "2026-03-19T14:30:12.500Z"
}
],
"pagination": {
"total": 42,
"limit": 5,
"offset": 0,
"has_more": true
}
}GET /scans/:id#
Retrieve a single scan with the complete grid_data array. This is the data you need to render a heatmap.
curl https://your-domain.com/api/v1/scans/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer gk_live_a1b2c3d4..."{
"data": {
"id": "a1b2c3d4-...",
"keyword": "plumber near me",
"target_cid": "0x12345",
"target_name": "John Plumbing LLC",
"latitude": 48.8566,
"longitude": 2.3522,
"radius_meters": 3000,
"grid_size": 7,
"status": "completed",
"nodes_sampled": 49,
"credits_consumed": 34,
"visibility_score": 4.2,
"grid_data": [
{ "lat": 48.8612, "lng": 2.3450, "rank": 1 },
{ "lat": 48.8612, "lng": 2.3498, "rank": 3 }
],
"tags": ["paris"],
"notes": "Baseline scan before campaign",
"created_at": "2026-03-19T14:30:00.000Z",
"completed_at": "2026-03-19T14:30:12.500Z"
}
}GET /credits#
Get your current credit balance, plan details, and recent transactions.
curl https://your-domain.com/api/v1/credits \
-H "Authorization: Bearer gk_live_a1b2c3d4..."{
"data": {
"credits": 8456,
"plan": "agency",
"plan_label": "Agency",
"credits_monthly": 20000,
"plan_renews_at": "2026-04-19T00:00:00.000Z",
"recent_transactions": [
{
"id": "tx_001",
"amount": -34,
"reason": "scan_consumed",
"metadata": { "scan_id": "a1b2c3d4-..." },
"created_at": "2026-03-19T14:30:12.500Z"
},
{
"id": "tx_002",
"amount": 20000,
"reason": "subscription",
"metadata": {},
"created_at": "2026-03-19T00:00:00.000Z"
}
]
}
}Create API Key#
API keys are managed from the dashboard UI at Settings → API Keys. You can also create them programmatically (requires session auth, not API key).
curl -X POST https://your-domain.com/api/v1/keys \
-H "Content-Type: application/json" \
-b "session_cookie=..." \
-d '{ "name": "Production" }'{
"data": {
"id": "key_uuid",
"name": "Production",
"key_prefix": "gk_live_a1b2...",
"key": "gk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8",
"created_at": "2026-03-19T15:00:00.000Z"
},
"warning": "Store this key securely. It will not be shown again."
}List API Keys#
curl https://your-domain.com/api/v1/keys \
-b "session_cookie=..."{
"data": [
{
"id": "key_uuid",
"name": "Production",
"key_prefix": "gk_live_a1b2...",
"last_used_at": "2026-03-19T14:55:00.000Z",
"requests_count": 1247,
"created_at": "2026-03-01T10:00:00.000Z"
}
]
}Revoke API Key#
Revoking a key is immediate and permanent. Any request using the revoked key will return 401.
curl -X DELETE https://your-domain.com/api/v1/keys/key_uuid \
-b "session_cookie=..."{
"success": true,
"id": "key_uuid"
}Quickstart#
Get from zero to your first scan in 3 steps.
Get your API key
Go to Settings, scroll to "API Keys", create a key, and copy it.
Launch your first scan
export GEOGRID_API_KEY="gk_live_your_key_here"
curl -X POST https://your-domain.com/api/v1/scans \
-H "Authorization: Bearer $GEOGRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"keyword": "pizza delivery",
"target_name": "Mario Pizza",
"latitude": 40.7128,
"longitude": -74.0060,
"grid_size": 5,
"radius_meters": 2000
}'Check your credits
curl https://your-domain.com/api/v1/credits \
-H "Authorization: Bearer $GEOGRID_API_KEY"Pagination#
List endpoints use offset-based pagination. The response always includes a pagination object.
// Node.js example
const API = "https://your-domain.com/api/v1";
const KEY = "gk_live_your_key_here";
async function getAllScans() {
let allScans = [];
let offset = 0;
const limit = 100;
while (true) {
const res = await fetch(
`${API}/scans?limit=${limit}&offset=${offset}`,
{ headers: { Authorization: `Bearer ${KEY}` } }
);
const { data, pagination } = await res.json();
allScans.push(...data);
if (!pagination.has_more) break;
offset += limit;
}
return allScans; // all your scans
}Webhooks#
Coming soon
Webhook notifications for scan completion, credit alerts, and scheduled scan results.
Geogrid API v1