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/v1

Authentication#

All API requests require a Bearer token in the Authorization header. API keys start with gk_live_ and are 48 characters long.

Example request
bash
curl https://your-domain.com/api/v1/credits \
  -H "Authorization: Bearer gk_live_a1b2c3d4e5f6g7h8i9j0..."
Keep your API keys secret. Never expose them in client-side code, public repositories, or browser requests. If a key is compromised, revoke it immediately in Settings.

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.

PlanRequests / minPrice
FreeNo API access$0
Freelance60$49/mo
Agency120$149/mo
Enterprise250$499/mo

Response headers on every request:

X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117

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

402Payment Required
{
  "error": {
    "code": "insufficient_credits",
    "message": "Insufficient credits",
    "credits_available": 12,
    "credits_required": 169
  }
}
StatusCodeMeaning
400invalid_*Validation error (bad parameters)
401missing_api_keyNo Authorization header
401invalid_api_keyKey not found or malformed
401revoked_api_keyKey has been revoked
402insufficient_creditsNot enough credits for this operation
403plan_requiredPaid plan needed for API access
403scan_limit_reachedPlan scan quota exceeded
403account_blockedAccount suspended
404not_foundResource doesn't exist
429rate_limit_exceededToo many requests
500internal_errorServer 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

ParameterTypeRequiredDescription
keywordstringRequiredSearch query (e.g. "plumber near me"). Max 200 chars.
latitudenumberRequiredCenter latitude (-90 to 90)
longitudenumberRequiredCenter longitude (-180 to 180)
target_cidstringOptionalGoogle Maps CID of the business to track. Either target_cid or target_name required.
target_namestringOptionalBusiness name (used for fuzzy matching if no CID)
grid_sizenumberOptionalGrid resolution: 5, 7, or 13 (default: 13). Credits = grid_size²
radius_metersnumberOptionalScan radius in meters. 500-50000 (default: 5000)
tagsstring[]OptionalTags for organization (max 10, 50 chars each)
Launch a scan
bash
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"]
  }'
201Created
{
  "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"
}
Visibility score is the average rank across all grid nodes where your business was found. Lower is better. A score of 1.0 means rank #1 everywhere. Nodes where the business wasn't found (rank: null) are excluded from the score.

GET /scans#

List your scans with pagination and filters. Returns scan metadata without grid data (use GET /scans/:id for full data).

Query parameters

ParameterTypeRequiredDescription
limitnumberOptionalResults per page, 1-100 (default: 20)
offsetnumberOptionalSkip N results (default: 0)
statusstringOptionalFilter: completed, partial, failed, running, pending
keywordstringOptionalFilter by keyword (partial match)
tagstringOptionalFilter by tag (exact match)
sortstringOptionalSort: newest (default), oldest, score
List completed scans
bash
curl "https://your-domain.com/api/v1/scans?status=completed&limit=5&sort=newest" \
  -H "Authorization: Bearer gk_live_a1b2c3d4..."
200OK
{
  "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.

Get scan details
bash
curl https://your-domain.com/api/v1/scans/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer gk_live_a1b2c3d4..."
200OK
{
  "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.

Check balance
bash
curl https://your-domain.com/api/v1/credits \
  -H "Authorization: Bearer gk_live_a1b2c3d4..."
200OK
{
  "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"
      }
    ]
  }
}
Credit costs: 5x5 grid = 25 credits max, 7x7 = 49, 13x13 = 169. Cache hits are free, so actual cost is usually 30-70% of the maximum.

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

Create a key (session auth only)
bash
curl -X POST https://your-domain.com/api/v1/keys \
  -H "Content-Type: application/json" \
  -b "session_cookie=..." \
  -d '{ "name": "Production" }'
201Created
{
  "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."
}
The full API key is only returned once at creation time. It is stored as a SHA-256 hash and cannot be retrieved later. Maximum 5 active keys per account.

List API Keys#

List active keys
bash
curl https://your-domain.com/api/v1/keys \
  -b "session_cookie=..."
200OK
{
  "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.

Revoke a key
bash
curl -X DELETE https://your-domain.com/api/v1/keys/key_uuid \
  -b "session_cookie=..."
200OK
{
  "success": true,
  "id": "key_uuid"
}

Quickstart#

Get from zero to your first scan in 3 steps.

1

Get your API key

Go to Settings, scroll to "API Keys", create a key, and copy it.

2

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
  }'
3

Check your credits

curl https://your-domain.com/api/v1/credits \
  -H "Authorization: Bearer $GEOGRID_API_KEY"
A 5x5 grid costs a maximum of 25 credits. With cache hits, your first scan might cost as few as 10-15 credits.

Pagination#

List endpoints use offset-based pagination. The response always includes a pagination object.

Page through results
javascript
// 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.