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. The public API is separate from the browser extension API and uses 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.
citation-spy:* / webhooks:*Reserved for planned modules; no public v1 endpoint is exposed yet.

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 manual business with full location data, or pass a place_id and let Local Rank Guru enrich details when available.

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 '{
    "name": "Austin Dental Studio",
    "address": "100 Congress Ave, Austin, TX 78701",
    "city": "Austin",
    "state": "TX",
    "zip": "78701",
    "country": "US",
    "latitude": 30.2638,
    "longitude": -97.7431,
    "website": "https://example.com"
  }'
{
  "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.

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.

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, non-internal scans. Provider errors 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.

Smoke Test Script

The repository includes a standard-library Python script that prompts for an API token and walks through the public endpoints. By default it performs read-only checks plus a non-mutating scan preview.

python3 scripts/public_api_smoke_test.py
python3 scripts/public_api_smoke_test.py --business-id 101
python3 scripts/public_api_smoke_test.py --write
python3 scripts/public_api_smoke_test.py --write --create-scan

--create-scan is the only mode that queues a 3x3 credit-backed scan and can consume credits. Citation Spy checks are currently skipped because Citation Spy public endpoints are not exposed in API v1.

Roadmap and MCP Readiness

The REST API remains the stable integration contract. MCP tools will map onto the same service boundaries, scopes, rate limits, idempotency behavior, and owner-scoped authorization. Reserved scopes such as citation-spy:read, citation-spy:write, webhooks:read, and webhooks:write are intentionally present for future modules but are not active public endpoints in v1.