GMB Ranking Developers Open app →

Developer documentation

Read a business's Google Business Profile performance — profile views, calls, website clicks, directions, bookings and messages — plus local rankings, reviews and citations, into your own website. One API key, JSON out. No OAuth, no Google API keys to manage.

The only way in: create an API key in your account, send it as a bearer token, and call https://www.gmbranking.com/api/v1/…. Keys are read-only and can be limited to a single location.

Overview

GMB Ranking pulls the numbers from Google and exposes them as JSON. Six metric series per location:

Metric keyLabelGoogle source
viewsProfile viewsSearch + Maps impressions (desktop & mobile, summed)
callsCallsCALL_CLICKS
websiteWebsite clicksWEBSITE_CLICKS
directionsDirectionsBUSINESS_DIRECTION_REQUESTS
bookingsBookingsBUSINESS_BOOKINGS
messagesMessagesBUSINESS_CONVERSATIONS

Every series is a monthly array, oldest first, aligned to the same labels array — everything you need to draw cards and charts.

Quick start

  1. Create an API key. In GMB Ranking open your profile menu → Developer API → Create key. Copy it immediately: it is shown once. (Plans that include API access only.)
  2. List your locations.
    curl https://www.gmbranking.com/api/v1/businesses \
      -H "Authorization: Bearer YOUR_KEY" \
      -H "Accept: application/json"
  3. Fetch one location's performance.
    curl "https://www.gmbranking.com/api/v1/businesses/42/performance?months=6" \
      -H "Authorization: Bearer YOUR_KEY" \
      -H "Accept: application/json"

Create a key

Keys are self-service:

https://www.gmbranking.com/account/api

Give it a name you will recognise, copy the value straight away, and revoke it whenever you want. You can hold up to 10, each with its own name and scope, and every key carries a single read ability: it can read data and change nothing.

Limit a key to one location

Leave Which locations? on All locations and the key reads the whole account. Pick a single location instead and the key can read only that one — useful when handing a client's developer access without exposing the rest:

Authentication

Every request needs the key as a bearer token:

Authorization: Bearer YOUR_KEY
Accept: application/json
Keep the key on your server. Call the API from your backend and send only the results to the page. A key inside browser JavaScript is a key every visitor can read.

No OAuth, no signing, no session — one header on an HTTPS GET.

Endpoints

EndpointReturns
GET/api/v1/meAccount, plan, location count, and the key's scope
GET/api/v1/businessesEvery location: address, phone, rating, categories, 30-day view/call/click counts
GET/api/v1/businesses/{id}One location in full, plus saved reports (checklist, AI visibility, backlink summary)
GET/api/v1/businesses/{id}/performanceMonthly series, ?months=1|3|6|12
GET/api/v1/businesses/{id}/rankingsLatest grid scan and history per keyword. Add ?include_grid=1 for the full grid
GET/api/v1/businesses/{id}/reviewsStored Google reviews plus external ones
GET/api/v1/businesses/{id}/citationsCitation / NAP status per directory

All endpoints are HTTPS GET, return JSON, and only ever expose the locations on your own account.

Performance

GET/api/v1/businesses/{id}/performance

Pick the period — 1M / 3M / 6M / 1Y

Pass the same labels the Performance page shows, or the number of months. Both mean the same thing; pick whichever suits your code.

RangeQueryMonths
1M?range=1M or ?months=11
3M?range=3M or ?months=33
6M?range=6M or ?months=66 (default)
1Y?range=1Y or ?months=1212
curl "https://www.gmbranking.com/api/v1/businesses/42/performance?range=1Y" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"

The response echoes what you asked for in range (and months), so a dashboard can render the selected 1M / 3M / 6M / 1Y button straight from it. An unknown value is refused rather than quietly switched to 6 months:

HTTP/1.1 422 Unprocessable Content

{
  "message": "Unknown range \"2Y\". Use one of: 1M, 3M, 6M, 1Y (or months=1, 3, 6, 12).",
  "code": "invalid_range",
  "available_ranges": { "1M": 1, "3M": 3, "6M": 6, "1Y": 12 }
}
{
  "business": { "id": 42, "name": "Acme Plumbing", "google_connected": true },
  "range": "6M",
  "months": 6,
  "available_ranges": { "1M": 1, "3M": 3, "6M": 6, "1Y": 12 },
  "labels": ["May", "Jun", "Jul", "Aug", "Sep", "Oct"],
  "metrics": {
    "views":      { "label": "Profile views",  "data": [263, 296, 241, 259, 273, 208] },
    "calls":      { "label": "Calls",          "data": [6, 1, 4, 4, 8, 2] },
    "website":    { "label": "Website clicks", "data": [28, 23, 14, 16, 14, 13] },
    "directions": { "label": "Directions",     "data": [23, 33, 79, 31, 55, 27] },
    "bookings":   { "label": "Bookings",       "data": [0, 0, 0, 0, 0, 0] },
    "messages":   { "label": "Messages",       "data": [0, 0, 0, 0, 0, 0] }
  },
  "error": null,
  "generated_at": "2026-10-24T16:42:11+00:00"
}
Not linked to Google? The endpoint still answers 200 with metrics: {} and a reason in error ("Connect this business to a Google Business Profile to see performance data."). Show that message instead of empty cards.

Rankings, reviews & citations

GET/api/v1/businesses/{id}/rankings?include_grid=1

{
  "count": 1,
  "data": [{
    "keyword": "plumber near me",
    "avg_position": 3.42,
    "found_in": 41,
    "grid_size": 7,
    "radius_km": 4,
    "scanned_at": "2026-10-20T11:04:00+00:00",
    "history": [
      { "avg_position": 3.42, "found_in": 41, "scanned_at": "2026-10-20T11:04:00+00:00" }
    ],
    "grid": [{ "lat": 51.5072, "lng": -0.1276, "position": 3, "top": [] }]
  }]
}

avg_position is the average across the whole grid, so it carries a decimal — Ø 1.5 means the business sits around position 1–2 across the scanned area.

GET/api/v1/businesses/{id}/reviews returns rating, reviews_count, the stored data array and any external reviews. GET /api/v1/businesses/{id}/citations returns one row per directory with status, the NAP it actually found, and when it was last checked.

Response fields

FieldTypeMeaning
business.idintegerLocation id inside GMB Ranking
business.namestringBusiness name as published on Google
business.google_connectedbooleanfalse = no Google location linked, so metrics stay empty
rangestringThe window you asked for, as the UI labels it: 1M, 3M, 6M or 1Y
monthsintegerThe same window in months (1, 3, 6, 12)
available_rangesobjectEvery range you may ask for — handy for building the buttons
labelsstring[]Month labels, oldest first — one per value in every series
metrics.{key}.labelstringDisplay name
metrics.{key}.datainteger[]One value per label; 0 is a real zero
errorstring|nullnull on success, otherwise why metrics are unavailable
generated_atISO 8601When the document was assembled
key_scopeinteger|nullLocation id the key is limited to (/me only)

Errors

StatusWhenWhat to do
401 unauthenticatedNo key, or the key was revokedCheck the Authorization: Bearer header
403 plan_requiredKey valid, subscription endedRenew — the same key works again, no new key needed
403 limited to one locationKey scoped to a different locationCreate a key for that location, or one for all locations
404Unknown id, or not on your accountList locations first
422 invalid_rangeUnknown range/months valueUse 1M, 3M, 6M, 1Y
422Google call failed for this locationRead message; retry later
429More than 120 requests a minute for this keyCache and back off

Code examples

cURL

curl "https://www.gmbranking.com/api/v1/businesses/42/performance?months=6" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"

PHP

<?php
$key = 'YOUR_KEY';
$url = 'https://www.gmbranking.com/api/v1/businesses/42/performance?months=6';

$context = stream_context_create(['http' => [
    'header' => "Authorization: Bearer {$key}\r\nAccept: application/json\r\n",
    'timeout' => 15,
]]);

$data = json_decode(file_get_contents($url, false, $context), true);

foreach ($data['metrics'] as $metric) {
    echo $metric['label'] . ': ' . array_sum($metric['data']) . PHP_EOL;
}

Node.js (server, Node 18+)

const KEY  = process.env.GMB_API_KEY;
const BASE = 'https://www.gmbranking.com';

const res = await fetch(`${BASE}/api/v1/businesses`, {
  headers: { Authorization: `Bearer ${KEY}`, Accept: 'application/json' },
});

if (!res.ok) {
  const err = await res.json();
  throw new Error(`${res.status}: ${err.message}`);
}

const { data } = await res.json();

for (const business of data) {
  const perf = await fetch(
    `${BASE}/api/v1/businesses/${business.id}/performance?months=6`,
    { headers: { Authorization: `Bearer ${KEY}`, Accept: 'application/json' } },
  ).then((r) => r.json());

  console.log(business.name, perf.metrics?.views?.data ?? perf.error);
}

Python (server)

# pip install requests
import os
import requests

KEY  = os.environ["GMB_API_KEY"]
BASE = "https://www.gmbranking.com"
HEADERS = {"Authorization": f"Bearer {KEY}", "Accept": "application/json"}

res = requests.get(f"{BASE}/api/v1/businesses", headers=HEADERS, timeout=15)

if res.status_code == 403 and res.json().get("code") == "plan_required":
    raise SystemExit("GMB Ranking plan has ended — renew to restore data")

res.raise_for_status()

for business in res.json()["data"]:
    perf = requests.get(
        f"{BASE}/api/v1/businesses/{business['id']}/performance",
        params={"months": 6}, headers=HEADERS, timeout=15,
    ).json()

    views = perf.get("metrics", {}).get("views", {}).get("data")

    print(business["name"], views or perf.get("error"))

Any other language — three ingredients

No SDK, no OAuth. Whatever the language, it is the same three things:

1 · URLhttps://www.gmbranking.com/api/v1/…
2 · HeaderAuthorization: Bearer YOUR_KEY and Accept: application/json
3 · BodyJSON in, JSON out — UTF-8

That covers PHP, Node.js, Python, Go, Ruby, Java, C#/.NET, Rust, Kotlin, Swift — anything that can send an HTTPS GET with a header. For typed clients, feed openapi.json to openapi-generator, Postman, Insomnia or Swagger Codegen.

Server-side only. Every example above runs on your server. A key inside browser JavaScript is a key every visitor can read.

Drawing a chart

Chart.js in a dozen lines — the series map straight onto it:

new Chart(document.getElementById('views'), {
  type: 'line',
  data: {
    labels: data.labels,
    datasets: [{
      label: data.metrics.views.label,
      data: data.metrics.views.data,
      borderColor: '#223a86',
      backgroundColor: 'rgba(34,58,134,.15)',
      fill: true,
      tension: .35
    }]
  },
  options: { plugins: { legend: { display: false } } }
});

Troubleshooting

What you seeWhyFix
401No key, wrong key, or revokedCheck Authorization: Bearer KEY
403 plan_requiredKey fine, subscription endedRenew — the same key works again
403 limited to one locationKey scoped elsewhereCreate a key for this location
200 but empty metricsLocation not imported from GoogleUse Import from Google on the Performance page once
429Over 120 requests/minuteCache the response; data changes a few times a day
Last month looks lowGoogle is 2–3 days behind and the month is partialCompare whole months
Same numbers for hoursResponses cached 30 minutes server-sideExpected — do not poll faster

When a plan ends

The API comes with the paid plans that include API access (they are listed on the pricing page; plan names below are examples). The free trial does not include it. If the subscription lapses, every call keeps authenticating but is refused:

HTTP/1.1 403 Forbidden

{
  "message": "This account's GMB Ranking plan does not include API access. A Pro or Agency subscription is required; existing keys work again as soon as the plan is renewed.",
  "code": "plan_required",
  "status": "plan_required",
  "plan": "free",
  "required_plans": ["pro", "agency"],
  "upgrade_url": "https://www.gmbranking.com/billing",
  "docs_url": "https://developers.gmbranking.com/#plan"
}

required_plans and the plan names inside message come from the account's plan configuration, so they match whatever the plans are called. Branch on code, never on the wording.

Renewing needs no new key. Access is checked on every request, so the same key starts returning data again as soon as the plan is active. Keep the key; just re-enable your polling.

Caching & data freshness

LayerDelay
Google's own reporting2–3 days — nothing can make it faster
GMB Ranking cacheUp to 30 minutes per location and window
Rate limit120 requests per minute per key

Label the numbers “updated with a 2–3 day delay” in your UI, and cache on your side — polling faster than 30 minutes cannot show new data.

Security

Changelog

DateChange
2026-10-24Performance accepts ?range=1M|3M|6M|1Y (same as the app's buttons); unknown values return 422
2026-10-24Docs streamlined to a single integration path (API keys). Read-only keys can be limited to one location.
2026-10-24API access limited to paid plans; lapsed plans return 403 with code: "plan_required"
2026-10-24Runnable Node.js and Python examples, plus an “any language” recipe
2026-10-24Self-service API keys and read-only /api/v1/*