# SEO metrics and backlinks

The SEO figures measured for each site, and the backlink tracker with its bulk link editor.

## SEO metrics history of a site

`GET /sites/{site_id}/seo-metrics` · scope `sites:read`

The same figures as the site's SEO metrics charts tab: Trust Flow, Citation Flow, backlinks and referring domains at every reading, with the change since the reading before. `latest` is null until the first reading.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `limit` | query | integer | no | How many readings, newest first. Default: `90`. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/seo-metrics" \
  -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/sites/123/seo-metrics", headers=headers, timeout=120)
print(r.status_code, r.json())
```

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

### Response

`200`

```json
{
  "data": {
    "site_id": 123,
    "latest": {
      "measured_at": "2026-09-20T03:10:00Z",
      "trust_flow": 21,
      "citation_flow": 30,
      "backlinks": 1840,
      "referring_domains": 212,
      "trust_flow_change": 1,
      "citation_flow_change": 0,
      "backlinks_change": 35,
      "referring_domains_change": 4
    },
    "history": [
      {
        "measured_at": "2026-09-20T03:10:00Z",
        "trust_flow": 21,
        "citation_flow": 30,
        "backlinks": 1840,
        "referring_domains": 212,
        "trust_flow_change": 1,
        "citation_flow_change": 0,
        "backlinks_change": 35,
        "referring_domains_change": 4
      }
    ]
  }
}
```

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

## Domains your posts link to

`GET /backlink-tracker` · scope `sites:read`

The backlink tracker: every domain the posts on your sites link to, with how many links, posts and sites carry them (`your_site_id` when the domain is one of your own sites).

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/backlink-tracker" \
  -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/backlink-tracker", headers=headers, timeout=120)
print(r.status_code, r.json())
```

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

### Response

`200`

```json
{
  "data": [
    {
      "id": 77,
      "domain": "money-site.com",
      "your_site_id": null,
      "links": 41,
      "posts": 38,
      "sites": 12
    }
  ]
}
```

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

## Every link to one domain

`GET /backlink-tracker/{domain_id}` · scope `sites:read`

One backlink domain: each distinct link to it (address + text) with how many times, posts and sites carry it, and which of your sites each address is found on. A link `id` is what the link editor endpoints take.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `domain_id` | path | integer | yes | The backlink domain id (see GET /backlink-tracker). |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/backlink-tracker/domain_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/backlink-tracker/domain_id", headers=headers, timeout=120)
print(r.status_code, r.json())
```

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

### Response

`200`

```json
{
  "data": {
    "id": 77,
    "domain": "money-site.com",
    "your_site_id": null,
    "links": [
      {
        "id": 9031,
        "url": "https://money-site.com/page",
        "text": "best widgets",
        "anchor": "<a href=\"https://money-site.com/page\">best widgets</a>",
        "links": 6,
        "posts": 5,
        "sites": 3
      }
    ],
    "posts": 38,
    "sites": 12,
    "found_on": [
      {
        "url": "https://money-site.com/page",
        "site_id": 123,
        "site_domain": "example.com"
      }
    ]
  }
}
```

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

## Change a link everywhere

`PATCH /backlink-tracker/links/{anchor_id}` · scope `content:write`

The bulk link editor's "Edit": the link is searched and replaced in the posts of every site that carries it (one job per site, in each site's operations log).

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `anchor_id` | path | integer | yes | The link id (see GET /backlink-tracker/{domain_id}). |
| `url` | body | string | no | The new address (default: unchanged). |
| `text` | body | string | no | The new link text (default: unchanged). |

### Example

```bash
curl -s -X PATCH "https://app.pbn.ltd/api/v1/backlink-tracker/links/anchor_id" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

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

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

### Response

`202`

```json
{
  "data": {
    "queued": true,
    "sites": 3
  }
}
```

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

## Remove a link everywhere

`DELETE /backlink-tracker/links/{anchor_id}` · scope `content:write` · **destructive**

The bulk link editor's "Delete": the whole link, its text too, is removed from the posts of every site that carries it (one job per site). It cannot be undone except from a backup.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `anchor_id` | path | integer | yes | The link id (see GET /backlink-tracker/{domain_id}). |
| `confirm` | body | boolean | yes | Must be true: the link (with its text) is removed from every post. |

### Example

```bash
curl -s -X DELETE "https://app.pbn.ltd/api/v1/backlink-tracker/links/anchor_id" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

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

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

### Response

`202`

```json
{
  "data": {
    "queued": true,
    "sites": 3
  }
}
```

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