Client reports
White-label reports for an agency's clients, built from the SEO tools the account already has.
Client reports: plan and usage
GET/api/v1/client-reportsScope reports:read
Your plan, how many clients and reports it covers and how many you have used this month.
Example
curl -s "https://app.pbn.ltd/api/v1/client-reports" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/client-reports", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/client-reports", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"plan": "Reports Pro",
"unlimited": false,
"paid_until": "2026-10-31",
"clients": 12,
"clients_included": 25,
"reports_used": 9,
"reports_included": 60,
"share_link_days": 60,
"own_sender": true,
"price_from": "12.00"
}
}
Errors: rate_limited, scope_missing, unauthorized
List your clients
GET/api/v1/client-reports/clientsScope reports:read
Every client you report on, what of theirs a report covers and when the next one is due.
Example
curl -s "https://app.pbn.ltd/api/v1/client-reports/clients" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/client-reports/clients", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/client-reports/clients", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": [
{
"id": 5,
"name": "Acme Plumbing",
"domains": [
"acmeplumbing.co.uk"
],
"cadence": "monthly",
"next_report_on": "2026-10-01",
"reports": 7
}
]
}
Errors: rate_limited, scope_missing, unauthorized
List the reports you have made
GET/api/v1/client-reports/reportsScope reports:read
Every report made on this account, newest first. The share link itself is only returned by the single-report endpoint.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
client_id | query | integer | no | Only reports for this client. |
limit | query | integer | no | Items per page. Default: 25. |
cursor | query | string | no | next_cursor of the previous page. |
Example
curl -s "https://app.pbn.ltd/api/v1/client-reports/reports" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/client-reports/reports", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/client-reports/reports", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": [
{
"id": 88,
"client": "Acme Plumbing",
"period_start": "2026-08-22",
"period_end": "2026-09-22",
"share_live": true,
"views": 3,
"created_at": "2026-09-22T09:00:00Z"
}
],
"next_cursor": null,
"has_more": false
}
Errors: rate_limited, scope_missing, unauthorized
One report
GET/api/v1/client-reports/reports/{report_id}Scope reports:read
The whole report: every section as it was frozen when it was made, the plain "what changed" summary, and the share link while it is live.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
report_id | path | integer | yes | The report id. |
Example
curl -s "https://app.pbn.ltd/api/v1/client-reports/reports/report_id" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/client-reports/reports/report_id", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/client-reports/reports/report_id", {
method: "GET",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"id": 88,
"client": "Acme Plumbing",
"period_start": "2026-08-22",
"period_end": "2026-09-22",
"sections": [
"rank",
"local",
"index"
],
"share_url": "https://app.pbn.ltd/client-reports/r/\u2026",
"share_expires_at": "2026-10-22T09:00:00Z",
"share_live": true,
"views": 3,
"summary": [
{
"good": true,
"text": "In the top 10 went up from 11 to 14 (google rankings)."
}
],
"created_at": "2026-09-22T09:00:00Z"
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized
Make a report now
POST/api/v1/client-reports/reportsScope reports:write
Builds the report from what the account has already collected - it never spends any of your search credits - and returns it with its share link.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
client_id | body | integer | yes | The client. |
send | body | boolean | no | Also e-mail it to that client's addresses. Default: False. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/client-reports/reports" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/client-reports/reports", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/client-reports/reports", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
201
{
"data": {
"id": 88,
"client": "Acme Plumbing",
"period_start": "2026-08-22",
"period_end": "2026-09-22",
"sections": [
"rank",
"local",
"index"
],
"share_url": "https://app.pbn.ltd/client-reports/r/\u2026",
"share_expires_at": "2026-10-22T09:00:00Z",
"share_live": true,
"views": 3,
"summary": [
{
"good": true,
"text": "In the top 10 went up from 11 to 14 (google rankings)."
}
],
"created_at": "2026-09-22T09:00:00Z"
}
}
Errors: payment_required, rate_limited, scope_missing, unauthorized, validation_failed
Turn a share link off
POST/api/v1/client-reports/reports/{report_id}/revokeScope reports:write · destructive
The link stops working at once for everybody who has it. The report itself is kept and you can turn the link back on from the panel.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
report_id | path | integer | yes | The report id. |
Example
curl -s -X POST "https://app.pbn.ltd/api/v1/client-reports/reports/report_id/revoke" \
-H "Authorization: Bearer $PBN_API_KEY"
import os
import requests
headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/client-reports/reports/report_id/revoke", headers=headers, timeout=120)
print(r.status_code, r.json())
const res = await fetch("https://app.pbn.ltd/api/v1/client-reports/reports/report_id/revoke", {
method: "POST",
headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
Response
200
{
"data": {
"revoked": true,
"id": 88
}
}
Errors: not_found, rate_limited, scope_missing, unauthorized