# Local rankings

Where a business sits in Google's map results and local results, town by town.

## Local rankings: plan and usage

`GET /local-rankings` · scope `local:read`

Your plan, what it covers, how much of this month's checks you have used, and how your businesses are doing on the map overall.

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/local-rankings" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/local-rankings", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "plan": "Local Pro",
    "unlimited": false,
    "paid_until": "2026-10-31",
    "businesses": 3,
    "keywords": 96,
    "in_map_pack": 31,
    "average_map_position": 4.8,
    "checks_used": 214,
    "checks_included": 700,
    "price_from": "9.00"
  }
}
```

Errors: `rate_limited`, `scope_missing`, `unauthorized`

## List your businesses

`GET /local-rankings/businesses` · scope `local:read`

Every business you track, with the facts of its Google business profile as we last read them and how it is doing in the map results.

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/local-rankings/businesses" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/local-rankings/businesses", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings/businesses", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "id": 12,
      "name": "Top Notch Plumbing",
      "domain": "topnotchplumbing.co.uk",
      "category": "Plumber",
      "address": "150 Bayham St, London NW1 0AU",
      "phone": "+442036709508",
      "rating": 4.7,
      "reviews": 26,
      "claimed": true,
      "photos": 2,
      "keywords": 24,
      "in_map_pack": 9,
      "average_map_position": 4.2,
      "last_checked_at": "2026-09-22T06:12:00Z"
    }
  ]
}
```

Errors: `rate_limited`, `scope_missing`, `unauthorized`

## One business

`GET /local-rankings/businesses/{business_id}` · scope `local:read`

One business: its profile facts, the towns it is searched from, and every change we have seen to its listing.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `business_id` | path | integer | yes | The business id. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/local-rankings/businesses/business_id" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/local-rankings/businesses/business_id", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings/businesses/business_id", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 12,
    "name": "Top Notch Plumbing",
    "domain": "topnotchplumbing.co.uk",
    "category": "Plumber",
    "address": "150 Bayham St, London NW1 0AU",
    "phone": "+442036709508",
    "rating": 4.7,
    "reviews": 26,
    "claimed": true,
    "photos": 2,
    "keywords": 24,
    "in_map_pack": 9,
    "average_map_position": 4.2,
    "last_checked_at": "2026-09-22T06:12:00Z",
    "towns": [
      {
        "id": 4,
        "town": "Camden,England,United Kingdom",
        "device": "mobile"
      }
    ],
    "recent_changes": [
      {
        "what": "Number of reviews",
        "from": "24",
        "to": "26",
        "at": "2026-09-20T04:10:00Z"
      }
    ]
  }
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## List your local keywords

`GET /local-rankings/keywords` · scope `local:read`

Every keyword you track, where it sits in the map results and in the ordinary results, and how it moved since the check before.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `business_id` | query | integer | no | Only keywords of this business. |
| `state` | query | string (one of: pack, map, nomap) | no | pack = in the top 3 of the map, map = anywhere in the map results, nomap = not in them at all. |
| `search` | query | string | no | Part of the keyword, business or town. |
| `limit` | query | integer | no | Items per page. Default: `50`. |
| `cursor` | query | string | no | next_cursor of the previous page. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/local-rankings/keywords" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/local-rankings/keywords", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings/keywords", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "id": 301,
      "keyword": "emergency plumber",
      "town": "Camden,England,United Kingdom",
      "device": "mobile",
      "map_position": 2,
      "map_change": 1,
      "organic_position": 14,
      "organic_change": -3,
      "best_map_position": 1,
      "checked_every": "weekly",
      "last_checked_at": "2026-09-22T06:12:00Z"
    }
  ],
  "next_cursor": null,
  "has_more": false
}
```

Errors: `rate_limited`, `scope_missing`, `unauthorized`

## The history of one keyword

`GET /local-rankings/keywords/{keyword_id}/history` · scope `local:read`

Every check we have made of that keyword, so you can chart it yourself.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `keyword_id` | path | integer | yes | The keyword id. |
| `limit` | query | integer | no | How many checks to return, newest first. Default: `90`. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/local-rankings/keywords/keyword_id/history" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/local-rankings/keywords/keyword_id/history", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings/keywords/keyword_id/history", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "checked_at": "2026-09-22T06:12:00Z",
      "map_position": 2,
      "organic_position": 14
    }
  ]
}
```

Errors: `not_found`, `rate_limited`, `scope_missing`, `unauthorized`

## Track more local keywords

`POST /local-rankings/keywords` · scope `local:write`

Adds keywords to one of your towns. Refused before anything is spent if the month would not fit inside your plan.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `town_id` | body | integer | yes | The id of one of your towns (see local.business). |
| `keywords` | body | array | yes | The keywords to track there. |
| `frequency` | body | string (one of: daily, weekly, monthly) | no | How often to check them. Lowered silently if your plan checks less often. Default: `weekly`. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/local-rankings/keywords" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/local-rankings/keywords", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings/keywords", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`201`

```json
{
  "data": {
    "added": 12,
    "town": "Camden,England,United Kingdom"
  }
}
```

Errors: `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## Check keywords now

`POST /local-rankings/check` · scope `local:write`

Puts those keywords at the front of the queue. They are checked within a few minutes and each one uses a check from this month's allowance.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `keyword_ids` | body | array | yes | The keyword ids to check (up to 100). |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/local-rankings/check" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/local-rankings/check", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/local-rankings/check", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "queued": 12
  }
}
```

Errors: `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`
