REST API v1

Local Rank Guru Public API Documentation

Build integrations for account data, businesses, keywords, scan templates, credit-backed GeoGrid scans, reports, competitor summaries, and share links with scoped Bearer tokens.

Authentication

Create public API tokens in the member dashboard under API Tokens. Live tokens use the lrg_live_ prefix; test-mode tokens use lrg_test_. Tokens are shown once and stored only as hashes.

Required header

Authorization: Bearer lrg_live_YOUR_TOKEN

Optional write header

Idempotency-Key: scan-run-2026-05-11-001
Scope Use
account:readRead account and credit summary.
businesses:read / businesses:writeList, read, and create owned businesses.
keywords:read / keywords:writeList and create business keywords.
scan-templates:read / scan-templates:writeList, read, and create scan templates.
scans:read / scans:writePreview, list, create, read, and cancel scans.
reports:readRead complete report, grid-point, and competitor payloads.
share:writeCreate or return public scan share links.

Conventions

Every response uses a predictable envelope. List endpoints use cursor pagination where applicable. Mutating endpoints cache only successful 2xx responses for idempotent replay.

Success envelope

{
  "data": {},
  "meta": {},
  "links": {}
}

Error envelope

{
  "error": {
    "code": "validation_failed",
    "message": "The request payload is invalid.",
    "details": {}
  }
}

Cursor pagination

When meta.has_more is true, call the URL in links.next. Do not parse or construct cursor values yourself.

{
  "meta": { "limit": 25, "has_more": true },
  "links": {
    "next": "https://www.localrank.guru/api/v1/scans?limit=25&page_cursor=eyJpZCI6MTIzfQ=="
  }
}

Account

Use this endpoint to verify a token and inspect credit availability before creating scans.

GET /api/v1/account account:read
curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  https://www.localrank.guru/api/v1/account
{
  "data": {
    "id": 42,
    "name": "Acme Agency",
    "email": "[email protected]",
    "plan": { "name": "Pro", "slug": "pro" },
    "credits": { "balance": 1240 }
  }
}

Businesses

Businesses are always owner-scoped. A token can never access another member's business by guessing IDs.

GET /api/v1/businesses businesses:read

Query parameters: limit, page_cursor, query, place_id, include=keywords.

curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  "https://www.localrank.guru/api/v1/businesses?limit=10&include=keywords"
{
  "data": [
    {
      "id": 101,
      "name": "Austin Dental Studio",
      "address": "100 Congress Ave, Austin, TX 78701",
      "city": "Austin",
      "state": "TX",
      "zip": "78701",
      "country": "US",
      "phone": "+1 512 555 0100",
      "website": "https://example.com",
      "latitude": 30.2638,
      "longitude": -97.7431,
      "place_id": "ChIJ...",
      "gbp_category": "Dentist",
      "keywords": [
        { "id": 201, "business_id": 101, "keyword": "dentist austin", "is_active": true }
      ]
    }
  ],
  "meta": { "limit": 10, "has_more": false },
  "links": { "next": null }
}
POST /api/v1/businesses businesses:write Idempotent

Create a business from a Google Places place_id. Manual profile fields such as name, address, website, latitude, and longitude are intentionally rejected so Local Rank Guru can resolve a consistent business profile from Google Places.

curl -X POST "https://www.localrank.guru/api/v1/businesses" \
  -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: business-austin-dental-001" \
  -d '{
    "place_id": "ChIJPublicApiBusiness"
  }'
{
  "data": {
    "id": 101,
    "name": "Austin Dental Studio",
    "address": "100 Congress Ave, Austin, TX 78701",
    "latitude": 30.2638,
    "longitude": -97.7431
  },
  "meta": { "created": true }
}
GET /api/v1/businesses/{business} businesses:read
curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  https://www.localrank.guru/api/v1/businesses/101

Keywords

Keywords belong to a business and are de-duplicated case-insensitively during creation.

GET /api/v1/businesses/{business}/keywords keywords:read
curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  "https://www.localrank.guru/api/v1/businesses/101/keywords?limit=25"
POST /api/v1/businesses/{business}/keywords keywords:write Idempotent
curl -X POST "https://www.localrank.guru/api/v1/businesses/101/keywords" \
  -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: keywords-101-001" \
  -d '{ "keywords": ["dentist austin", "emergency dentist", "teeth whitening"] }'
{
  "data": [
    { "id": 201, "business_id": 101, "keyword": "dentist austin", "is_active": true },
    { "id": 202, "business_id": 101, "keyword": "emergency dentist", "is_active": true }
  ],
  "meta": { "created_count": 2, "reused_count": 1 }
}

Scan Templates

Scan templates store reusable grid settings per keyword. Creating a template does not start a scan or consume credits.

GET /api/v1/scan-templates scan-templates:read

Query parameters: business_id, limit, page_cursor.

curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  "https://www.localrank.guru/api/v1/scan-templates?business_id=101&limit=10"
{
  "data": [
    {
      "id": 301,
      "business_id": 101,
      "name": "dentist austin",
      "keyword": { "id": 201, "keyword": "dentist austin" },
      "grid_size": "7x7",
      "point_distance": 1,
      "distance_unit": "km",
      "grid_shape": "square",
      "grid_shape_payload": null,
      "center": { "latitude": 30.2638, "longitude": -97.7431 },
      "scans_count": 4,
      "latest_scan": { "id": 401, "status": "completed", "created_at": "2026-05-11T12:00:00+00:00" }
    }
  ],
  "meta": { "limit": 10, "has_more": false },
  "links": { "next": null }
}
POST /api/v1/scan-templates scan-templates:write Idempotent

The request can contain multiple keywords; Local Rank Guru creates one scan template per unique keyword. Grid settings are validated with the same grid preview engine used by scan creation. Custom polygons accept 3-100 vertices and reject very large areas before grid generation.

curl -X POST "https://www.localrank.guru/api/v1/scan-templates" \
  -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: templates-101-001" \
  -d '{
    "business_id": 101,
    "keywords": ["dentist austin", "emergency dentist"],
    "grid_size": "7x7",
    "point_distance": 1.0,
    "distance_unit": "km",
    "grid_shape": "square",
    "center": { "latitude": 30.2638, "longitude": -97.7431 }
  }'
{
  "data": [
    {
      "id": 301,
      "business_id": 101,
      "name": "dentist austin",
      "keyword": { "id": 201, "keyword": "dentist austin" },
      "grid_size": "7x7",
      "point_distance": 1,
      "distance_unit": "km",
      "grid_shape": "square"
    }
  ],
  "meta": { "created_count": 2 }
}
GET /api/v1/scan-templates/{scan_template} scan-templates:read
curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  https://www.localrank.guru/api/v1/scan-templates/301

Scan Preview

Preview point count and credit cost before queuing a credit-backed scan. This endpoint does not mutate data. Custom polygons accept 3-100 vertices and reject very large areas before grid generation.

POST /api/v1/scans/preview scans:read Expensive limit
curl -X POST "https://www.localrank.guru/api/v1/scans/preview" \
  -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": 101,
    "keywords": ["dentist austin"],
    "grid_size": "3x3",
    "point_distance": 1.0,
    "distance_unit": "km",
    "grid_shape": "square"
  }'
{
  "data": {
    "grid_shape": "square",
    "grid_size": "3x3",
    "point_distance": 1,
    "distance_unit": "km",
    "keyword_count": 1,
    "total_points": 9,
    "scannable_points": 9,
    "water_points": 0,
    "credits_required": 9,
    "water_filter": {
      "enabled": false,
      "applied": false,
      "fallback_used": false
    }
  }
}

Scans

Public API scan creation is credit-backed in v1. Browser-extension-backed free scan jobs are intentionally not exposed through the public contract.

GET /api/v1/scans scans:read

Query parameters: business_id, status, limit, page_cursor.

curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  "https://www.localrank.guru/api/v1/scans?business_id=101&limit=10"
POST /api/v1/scans scans:write Idempotent
curl -X POST "https://www.localrank.guru/api/v1/scans" \
  -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: scan-101-2026-05-11" \
  -d '{
    "business_id": 101,
    "keywords": ["dentist austin"],
    "grid_size": "3x3",
    "point_distance": 1.0,
    "distance_unit": "km",
    "grid_shape": "square",
    "scan_method": "credits"
  }'
{
  "data": {
    "status": "queued",
    "created_scans": [
      {
        "id": 401,
        "business_id": 101,
        "keyword": "dentist austin",
        "status": "pending",
        "credits_charged": 9,
        "report_url": "https://www.localrank.guru/businesses/101/scans/401"
      }
    ]
  },
  "meta": { "credits_charged_total": 9 }
}
GET /api/v1/scans/{scan} scans:read
curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  https://www.localrank.guru/api/v1/scans/401
{
  "data": {
    "id": 401,
    "business_id": 101,
    "keyword": "dentist austin",
    "status": "completed",
    "grid_size": "3x3",
    "point_distance": 1,
    "distance_unit": "km",
    "grid_shape": "square",
    "scan_method": "credits",
    "progress": {
      "percent": 100,
      "completed_grid_points": 9,
      "total_grid_points": 9
    },
    "summary": {
      "average_rank": 4.2,
      "top3_count": 3,
      "found_count": 8
    }
  }
}
POST /api/v1/scans/{scan}/cancel scans:write

Only non-terminal scans can be cancelled. Completed, failed, and already-cancelled scans return operation_not_allowed.

curl -X POST -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  https://www.localrank.guru/api/v1/scans/401/cancel

Reports

Report endpoints expose processed GeoGrid output for owned scans. Error details are redacted before serialization.

GET /api/v1/reports/scans/{scan} reports:read
curl -H "Authorization: Bearer lrg_live_YOUR_TOKEN" \
  https://www.localrank.guru/api/v1/reports/scans/401
{
  "data": {
    "scan": { "id": 401, "status": "completed", "keyword": "dentist austin" },
    "business": { "id": 101, "name": "Austin Dental Studio" },
    "ranking_summary": { "average_rank": 4.2, "top3_count": 3, "found_count": 8 },
    "grid_points": [
      {
        "id": 501,
        "latitude": 30.2638,
        "longitude": -97.7431,
        "rank": 2,
        "checked_at": "2026-05-11T12:04:00+00:00"
      }
    ],
    "competitors": []
  }
}
GET /api/v1/scans/{scan}/grid-points

Returns per-point rank, coordinates, status, and redacted provider error information.

GET /api/v1/scans/{scan}/competitors

Returns aggregated competitor appearances across the scan result set.

Errors and Safety Rules

The API uses stable error codes so integrations can handle retries and user-facing messages predictably.

HTTP Code Meaning
401authentication_requiredMissing Bearer token.
401invalid_tokenMalformed, expired, revoked, inactive, or unknown token.
403insufficient_scopeToken does not include the required scope.
404not_foundResource does not exist or is not owned by the token owner.
409idempotency_conflictThe same key was reused with a different request.
409idempotency_in_progressA duplicate retry arrived while the original request is still running.
422validation_failedPayload failed validation.
429rate_limit_exceededToken exceeded its request budget.