List the live links
GET/feeds
- Scope:
- read
- Rate class:
- light
- Operation id:
- list_feeds
The workspace's live links, newest first, revoked and expired ones included (owners and admins see every link, other members the ones they made). A link's address is never listed: it was shown once, when the link was made. Needs the read scope and a plan with exports; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| limit | query | integeroptional | Items per page, 1 to 200.Range: 1 to 200. Default: 50. |
| cursor | query | stringoptional | The next_cursor of the previous page. Opaque: never build one. |
Answer 200
OK. The body is a FeedList.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | |
| data[].name | string | |
| data[].dataset | string | |
| data[].dataset_label | string | |
| data[].site_id | integer | |
| data[].site_url | string | |
| data[].prefix | string | The first characters of the link's secret (pbf_xxxxxxxx): enough to recognise it. |
| data[].status | string | active, revoked or expired. |
| data[].created_at | string or null | |
| data[].expires_at | string or null | A link stops working after a year. |
| data[].last_used_at | string or null | When a sheet last read it. |
| data[].use_count | integer | How many times it was read. |
| has_more | boolean | True when another page follows. |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"id": 9,
"name": "Broken links: example.com",
"dataset": "broken-links",
"dataset_label": "Broken links",
"site_id": 12,
"site_url": "https://www.example.com",
"prefix": "pbf_k3x9a7bq",
"status": "active",
"created_at": "2026-10-31T09:12:00Z",
"expires_at": "2027-10-31T09:12:00Z",
"use_count": 0
}
],
"has_more": false
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 403 Plan upgrade required:
plan_required - 404 Not found:
not_found - 422 Validation failed:
validation_error
Make a live link
POST/feeds
- Scope:
- read
- Rate class:
- light
- Operation id:
- create_feed
Makes a live CSV link for one of the workspace's sites and one table: crawl-history (one row per finished crawl), broken-links, all-urls, seo (the newest crawl's tables, followed on every read) or site-pages (the Site Pages list with the filters you give). Paste formula into Google Sheets. The link is in this answer only: it is a secret, anyone who has it can read the data, and it is never shown again (revoke it with DELETE /feeds/{feed_id}). It stops working after a year. Refused: a site with staging access, a table above the size a live link holds, and a workspace at its limit. Needs the read scope and a plan with exports; a viewer's key cannot make one. Free of credits.
Request body
application/json, required.
| Field | Type | Description |
|---|---|---|
| dataset | stringrequired | Which table the link serves: crawl-history, broken-links, all-urls, seo, site-pages.One of: crawl-history, broken-links, all-urls, seo, site-pages. |
| site_id | integerrequired | One of the workspace's sites (GET /sites).Range: at least 1. |
| filters | object or nulloptional | Only for the site-pages dataset. |
| filters.change | string or nulloptional | One of: new, changed. |
| filters.indexability | string or nulloptional | One of: indexable, non_indexable, unknown. |
| filters.issue | string or nulloptional | An issue code from GET /issue-types.Range: at most 40 characters. |
| filters.order | stringoptional | One of: asc, desc. Default: asc. |
| filters.prefix | stringoptional | Only URLs that start with this.Range: at most 2000 characters. |
| filters.q | stringoptional | Only URLs that contain this.Range: at most 200 characters. |
| filters.resolved | stringoptional | One of: show, hide, only. Default: show. |
| filters.sort | stringoptional | One of: url, status, inlinks, depth, response_time, word_count, last_seen, first_seen. Default: url. |
| filters.status | string or nulloptional | One of: ok, redirect, broken, gone, not_checked, unknown. |
| filters.type | string or nulloptional | One of: page, document, other, image, css, js, font, video, iframe, audio, embed. |
| filters.view | stringoptional | pages, resources, links_out or missing.One of: pages, resources, links_out, missing. Default: pages. |
| name | string or nulloptional | A name to recognise the link by; the dataset and site by default.Range: at most 100 characters. |
{
"dataset": "site-pages",
"site_id": 12,
"filters": {
"status": "broken",
"view": "pages"
},
"name": "Broken pages"
}Answer 201
Created. The body is a FeedCreated.
| Field | Type | Description |
|---|---|---|
| id | integer | |
| name | string | |
| dataset | string | |
| dataset_label | string | |
| site_id | integer | |
| site_url | string | |
| prefix | string | The first characters of the link's secret (pbf_xxxxxxxx): enough to recognise it. |
| status | string | active, revoked or expired. |
| created_at | string or null | |
| expires_at | string or null | A link stops working after a year. |
| last_used_at | string or null | When a sheet last read it. |
| use_count | integer | How many times it was read. |
| url | string | The link: anyone who has it can read the data until it is revoked. |
| formula | string | The same link as a Google Sheets formula. |
{
"id": 9,
"name": "Broken links: example.com",
"dataset": "broken-links",
"dataset_label": "Broken links",
"site_id": 12,
"site_url": "https://www.example.com",
"prefix": "pbf_k3x9a7bq",
"status": "active",
"created_at": "2026-10-31T09:12:00Z",
"expires_at": "2027-10-31T09:12:00Z",
"use_count": 0,
"url": "https://dashboard.perfbee.com/feeds/pbf_k3x9a7bq_QmZ0a9xVn2pYf3Lr8sTq1uWb4cXd6eHg7iJ.csv",
"formula": "=IMPORTDATA(\"https://dashboard.perfbee.com/feeds/pbf_k3x9a7bq_QmZ0a9xVn2pYf3Lr8sTq1uWb4cXd6eHg7iJ.csv\")"
}Problems this call documents
- 403 Plan upgrade required:
plan_required - 404 Not found:
not_found - 409 Live link limit reached:
feed_limit_reached - 409 Site has staging access:
feed_site_has_staging_access - 413 Live link too large:
feed_too_large - 422 Validation failed:
validation_error
Revoke a live link
DELETE/feeds/{feed_id}
- Scope:
- read
- Rate class:
- light
- Operation id:
- revoke_feed
Revokes a live link: the address stops working at once and cannot be brought back (make a new link). Revoking twice answers the same. Returns the feed as it is now. Needs the read scope and a plan with exports; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| feed_id | path | integerrequired | The live link's id (GET /feeds).Range: 1 to 9223372036854776000. |
Answer 200
OK. The body is a Feed.
| Field | Type | Description |
|---|---|---|
| id | integer | |
| name | string | |
| dataset | string | |
| dataset_label | string | |
| site_id | integer | |
| site_url | string | |
| prefix | string | The first characters of the link's secret (pbf_xxxxxxxx): enough to recognise it. |
| status | string | active, revoked or expired. |
| created_at | string or null | |
| expires_at | string or null | A link stops working after a year. |
| last_used_at | string or null | When a sheet last read it. |
| use_count | integer | How many times it was read. |
{
"id": 9,
"name": "Broken links: example.com",
"dataset": "broken-links",
"dataset_label": "Broken links",
"site_id": 12,
"site_url": "https://www.example.com",
"prefix": "pbf_k3x9a7bq",
"status": "active",
"created_at": "2026-10-31T09:12:00Z",
"expires_at": "2027-10-31T09:12:00Z",
"use_count": 0
}Problems this call documents
- 403 Plan upgrade required:
plan_required - 404 Not found:
not_found - 422 Validation failed:
validation_error
List the issue catalog
GET/issue-types
- Scope:
- read
- Rate class:
- light
- Operation id:
- list_issue_types
Every issue code a page can carry (issues of a page, the issue filter, fixes) with its label, severity, category, what it looks at, why it matters and how to fix it. The catalog grows: codes are never renumbered or reused, so ignore codes you do not know. It is one page: no limit or cursor. Needs the read scope; free of credits.
Answer 200
OK. A page of IssueType objects, with next_cursor and has_more.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].code | string | The stable code. Codes are never renumbered or reused; new ones appear, so ignore codes you do not know. |
| data[].label | string | The issue in words. |
| data[].severity | string | error, warning or info. |
| data[].category | string | indexing, content, social, asset, security or links. |
| data[].help | string | What the check looks at. |
| data[].why | string | Why it matters. |
| data[].fix | string | How to fix it. |
| has_more | boolean | True when another page follows. |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"code": "missing_title",
"label": "Missing title",
"severity": "error",
"category": "content",
"help": "The page has no <title> element.",
"why": "Search results show the title; without one they guess.",
"fix": "Give every page a short, unique title."
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}List a site's fixes
GET/sites/{site_id}/fixes
- Scope:
- read
- Rate class:
- light
- Operation id:
- list_site_fixes
The site's fix list, the Fixes view of the dashboard: each issue grouped by page template (one change fixes every page built from it), the whole site or separate pages, and every broken link, best score first. status picks open fixes (the default), those the team set aside, resolved ones or all. The score and rank only order the list: the weights are still being tuned. It is a keyset: pass next_cursor back as cursor. include=count adds the counts per status and severity. The view is behind a switch in the dashboard and these routes are 404 wherever it is hidden. Needs the read scope; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | path | integerrequired | The site's id (GET /sites).Range: 1 to 9223372036854776000. |
| status | query | stringoptional | open, set_aside (accepted, won't fix or snoozed), resolved or all.One of: open, set_aside, resolved, all. Default: open. |
| severity | query | array of stringoptional | Only these severities; repeat the parameter. |
| kind | query | stringoptional | Only fixes of this kind.One of: site, template, pages, link. |
| code | query | stringoptional | Only fixes of this issue code.Range: at most 64 characters. |
| q | query | stringoptional | Only fixes whose issue, template or URL contains this.Range: at most 200 characters. |
| include | query | anyoptional | count adds counts. |
| limit | query | integeroptional | Items per page, 1 to 200.Range: 1 to 200. Default: 50. |
| cursor | query | stringoptional | The next_cursor of the previous page. Opaque: never build one. |
Answer 200
OK. The body is a FixList.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | |
| data[].code | string | The issue code. Page issue codes are in GET /issue-types; link fixes use broken_link, broken_external_link and broken_sitemap_url. Ignore codes you do not know. |
| data[].label | string | The issue in words. |
| data[].severity | string | error, warning or info. |
| data[].kind | string | site (the whole site), template, pages (separate pages) or link (one broken URL). |
| data[].status | string | open, regressed (it came back), checking, looks_fixed, resolved or stale. |
| data[].pages | integer | Pages the fix touches (for a link fix: pages that link to the broken URL). |
| data[].template_pages | integer or null | Pages of the template, when the fix is a template's. |
| data[].score | number | Ranks the fix against the site's others; for ordering only. |
| data[].rank | integer or null | Its place in the site's open list; null for a fix that is not open or is set aside. |
| data[].scope | string | Where the fix is, in one line. |
| data[].template | object or null | |
| data[].template.id | integer | |
| data[].template.label | string | The URL pattern most of the template's pages share. |
| data[].template.example_url | string or null | One page built from the template. |
| data[].template.pages | integer | Pages built from the template. |
| data[].template.source | string | How the pages were grouped: skeleton (page structure) or url (URL pattern). |
| data[].target | string or null | For a link fix, the broken URL. |
| data[].target_page_id | integer or null | That URL's id for GET /sites/{site_id}/pages/{page_id}; null when it left the inventory. |
| data[].first_seen_at | string or null | |
| data[].resolved_at | string or null | |
| data[].resolved_by | string or null | scan, check or marked. |
| data[].decision | string or null | What the team decided in the dashboard: accepted, wont_fix or snoozed (until the next crawl); null = nothing. |
| data[].decided_at | string or null | |
| data[].decision_note | string or null | |
| data[].last_check | object or null | The latest "Check if fixed" (started in the dashboard). |
| data[].last_check.scope | string or null | sample or all. |
| data[].last_check.pages | integer or null | Pages the check reads. |
| data[].last_check.started_at | string or null | |
| data[].last_check.checked | integer or null | Pages it could read; null while it runs. |
| data[].last_check.fixed | integer or null | Of those, pages where the issue is gone. |
| data[].last_check.finished_at | string or null | |
| has_more | boolean | True when another page follows. |
| counts | object or null | Only with include=count. |
| counts.status | object | Fixes per status tab. |
| counts.status.open | integer | |
| counts.status.set_aside | integer | Accepted, won't-fix or snoozed. |
| counts.status.resolved | integer | |
| counts.severity | object | Fixes of the chosen status per severity. |
| counts.severity.error | integer | |
| counts.severity.warning | integer | |
| counts.severity.info | integer | |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"id": 311,
"code": "missing_meta_description",
"label": "Missing meta description",
"severity": "warning",
"kind": "template",
"status": "open",
"pages": 1840,
"template_pages": 2010,
"score": 21.4,
"rank": 1,
"scope": "Template /shoes/{slug} · 1,840 of 2,010 pages",
"template": {
"id": 17,
"label": "/shoes/{slug}",
"example_url": "https://www.example.com/shoes/trail-runner",
"pages": 2010,
"source": "skeleton"
},
"first_seen_at": "2026-09-02T04:11:09Z"
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 404 Not found:
not_found - 422 Validation failed:
validation_error
Get one fix
GET/sites/{site_id}/fixes/{fix_id}
- Scope:
- read
- Rate class:
- light
- Operation id:
- get_site_fix
One fix with the issue in words (what is wrong, why it matters, how to fix it, naming an example page) and its history, the latest 50 events newest first. People behind decisions are not named. Needs the read scope; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | path | integerrequired | The site's id (GET /sites).Range: 1 to 9223372036854776000. |
| fix_id | path | integerrequired | The fix's id (GET /sites/{site_id}/fixes).Range: 1 to 9223372036854776000. |
Answer 200
OK. The body is a FixDetail.
| Field | Type | Description |
|---|---|---|
| fix | object | One thing to fix: an issue on a template, on the whole site, on separate pages, or one broken URL. |
| fix.id | integer | |
| fix.code | string | The issue code. Page issue codes are in GET /issue-types; link fixes use broken_link, broken_external_link and broken_sitemap_url. Ignore codes you do not know. |
| fix.label | string | The issue in words. |
| fix.severity | string | error, warning or info. |
| fix.kind | string | site (the whole site), template, pages (separate pages) or link (one broken URL). |
| fix.status | string | open, regressed (it came back), checking, looks_fixed, resolved or stale. |
| fix.pages | integer | Pages the fix touches (for a link fix: pages that link to the broken URL). |
| fix.template_pages | integer or null | Pages of the template, when the fix is a template's. |
| fix.score | number | Ranks the fix against the site's others; for ordering only. |
| fix.rank | integer or null | Its place in the site's open list; null for a fix that is not open or is set aside. |
| fix.scope | string | Where the fix is, in one line. |
| fix.template | object or null | |
| fix.template.id | integer | |
| fix.template.label | string | The URL pattern most of the template's pages share. |
| fix.template.example_url | string or null | One page built from the template. |
| fix.template.pages | integer | Pages built from the template. |
| fix.template.source | string | How the pages were grouped: skeleton (page structure) or url (URL pattern). |
| fix.target | string or null | For a link fix, the broken URL. |
| fix.target_page_id | integer or null | That URL's id for GET /sites/{site_id}/pages/{page_id}; null when it left the inventory. |
| fix.first_seen_at | string or null | |
| fix.resolved_at | string or null | |
| fix.resolved_by | string or null | scan, check or marked. |
| fix.decision | string or null | What the team decided in the dashboard: accepted, wont_fix or snoozed (until the next crawl); null = nothing. |
| fix.decided_at | string or null | |
| fix.decision_note | string or null | |
| fix.last_check | object or null | The latest "Check if fixed" (started in the dashboard). |
| fix.last_check.scope | string or null | sample or all. |
| fix.last_check.pages | integer or null | Pages the check reads. |
| fix.last_check.started_at | string or null | |
| fix.last_check.checked | integer or null | Pages it could read; null while it runs. |
| fix.last_check.fixed | integer or null | Of those, pages where the issue is gone. |
| fix.last_check.finished_at | string or null | |
| issue | object | |
| issue.code | string | |
| issue.label | string | |
| issue.severity | string | |
| issue.category | string | |
| issue.help | string | What is wrong. |
| issue.why | string | Why it matters. |
| issue.fix | string | How to fix it, with the example page named where the text refers to it. |
| example_url | string or null | A page to look at first: the template's example or the broken URL. |
| history | array of object | The latest 50 events, newest first. |
| history[].kind | string | What happened: opened, grew, shrank, resolved, regressed, checked, accepted, wont_fix, snoozed, reopened... |
| history[].pages | integer | Pages affected at that moment. |
| history[].at | string or null | |
| history[].crawl_id | string or null | The crawl that found it, when a crawl did. |
| history[].note | string or null |
{
"fix": {
"id": 311,
"code": "missing_meta_description",
"label": "Missing meta description",
"severity": "warning",
"kind": "template",
"status": "open",
"pages": 1840,
"template_pages": 2010,
"score": 21.4,
"rank": 1,
"scope": "Template /shoes/{slug} · 1,840 of 2,010 pages",
"template": {
"id": 17,
"label": "/shoes/{slug}",
"example_url": "https://www.example.com/shoes/trail-runner",
"pages": 2010,
"source": "skeleton"
},
"first_seen_at": "2026-09-02T04:11:09Z"
},
"issue": {
"code": "missing_meta_description",
"label": "Missing meta description",
"severity": "warning",
"category": "content",
"help": "The page has no meta description.",
"why": "Search results then show whatever text they pick.",
"fix": "Write a meta description of about 150 characters for https://www.example.com/shoes/trail-runner."
},
"example_url": "https://www.example.com/shoes/trail-runner",
"history": [
{
"kind": "opened",
"pages": 1840,
"at": "2026-09-02T04:30:00Z",
"crawl_id": "7d1c0f94-2b8e-4a73-9a10-5f3d8e6c1b02"
}
]
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error
List the pages of a fix
GET/sites/{site_id}/fixes/{fix_id}/pages
- Scope:
- read
- Rate class:
- heavy
- Operation id:
- list_site_fix_pages
The pages a fix is about. For a fix of pages, templates or the whole site these are the affected pages, most linked first, as a keyset: pass next_cursor back as cursor (a sort over the whole set, so a very large fix can answer 503 query_too_slow: narrow it with q). For a link fix they are the pages that link to the broken URL with the anchor text and the HTML element: a crawl stores at most 200 of them, so that list is short and sorted by URL. kind says which. q keeps URLs containing it. Needs the read scope; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | path | integerrequired | The site's id (GET /sites).Range: 1 to 9223372036854776000. |
| fix_id | path | integerrequired | The fix's id (GET /sites/{site_id}/fixes).Range: 1 to 9223372036854776000. |
| q | query | stringoptional | Only URLs that contain this.Range: at most 200 characters. |
| limit | query | integeroptional | Items per page, 1 to 200.Range: 1 to 200. Default: 50. |
| cursor | query | stringoptional | The next_cursor of the previous page. Opaque: never build one. |
Answer 200
OK. The body is a FixPageList.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].url | string | |
| data[].page_id | integer or null | The page's id in the site's inventory; null when it is not in it. |
| data[].inbound_links | integer or null | Pages linking to an affected page (null for a linking page). |
| data[].anchor_text | string or null | Link fixes: the broken link's anchor text. |
| data[].element_xpath | string or null | Link fixes: the HTML element holding the broken link. |
| has_more | boolean | True when another page follows. |
| kind | string | pages: the URLs are affected pages, most linked first. links: the URLs are pages linking to the broken URL (at most 200 are stored, sorted by URL). |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"url": "https://www.example.com/shoes/trail-runner",
"page_id": 48120,
"inbound_links": 37
}
],
"has_more": false,
"kind": "pages"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 404 Not found:
not_found - 422 Validation failed:
validation_error - 429 Too many heavy calls at once:
too_many_heavy_requests - 503 Service busy:
capacity_busy - 503 Query too slow:
query_too_slow
List a site's pages
GET/sites/{site_id}/pages
- Scope:
- read
- Rate class:
- heavy
- Operation id:
- list_site_pages
The site's page inventory, the list behind Site Pages in the dashboard: one row per URL the crawls found, with its status, its SEO data (from the newest detailed crawl) and the issue codes it carries. Choose a view and narrow it with the filters of the dashboard. The order is always by URL, and the list is a keyset: pass next_cursor back as cursor. inventory says which crawl the list shows and whether it covered the whole site. A filter that matches few rows can take long; past 15 seconds the answer is 503 query_too_slow, so narrow it with prefix or a view. include=count adds the counts behind the dashboard's chips (one more, heavier query). For other sort orders download an export. Needs the read scope; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | path | integerrequired | The site's id (GET /sites).Range: 1 to 9223372036854776000. |
| include | query | anyoptional | count adds counts. |
| view | query | stringoptional | pages: HTML pages. resources: images, scripts, styles, fonts, media and the site's own files. links_out: pages and files outside the site or its folder that the site links to. missing: URLs a later complete crawl no longer found.One of: pages, resources, links_out, missing. Default: pages. |
| status | query | stringoptional | Only URLs with this status class.One of: ok, redirect, broken, gone, not_checked, unknown. |
| type | query | stringoptional | Only URLs of this type.One of: page, document, other, image, css, js, font, video, iframe, audio, embed. |
| indexability | query | stringoptional | Only pages with this indexability.One of: indexable, non_indexable, unknown. |
| issue | query | stringoptional | Only URLs carrying this issue code (GET /issue-types).Range: at most 40 characters. |
| change | query | stringoptional | Only URLs new or changed in the newest crawl.One of: new, changed. |
| resolved | query | stringoptional | show, hide or list only the URLs someone marked resolved.One of: show, hide, only. Default: show. |
| prefix | query | stringoptional | Only URLs that start with this text.Range: at most 2000 characters. |
| q | query | stringoptional | Only URLs that contain this text.Range: at most 200 characters. |
| sort | query | anyoptional | URL order is the only one the list offers: for other orders download an export.Default: url. |
| limit | query | integeroptional | Items per page, 1 to 200.Range: 1 to 200. Default: 50. |
| cursor | query | stringoptional | The next_cursor of the previous page. Opaque: never build one. |
Answer 200
OK. The body is a SitePageList.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | The page's id inside the site (a 64-bit integer: store it as one). |
| data[].url | string | The URL exactly as the crawl stored it. |
| data[].url_type | string | page, document, other, image, css, js, font, video, iframe, audio or embed. |
| data[].link_scope | string or null | internal, outside_folder (same host, outside the folder of a sub-path site) or external. |
| data[].status_class | string or null | ok, redirect, broken, gone (HTTP 410), not_checked (the crawler was refused, see block_reason) or unknown. |
| data[].status_code | integer or null | The HTTP status the crawl got; null when there was none. |
| data[].block_reason | string or null | Why a URL was not checked (bot wall, robots.txt...); set only with status_class not_checked. |
| data[].error_message | string or null | The connection error, when the request failed before an HTTP status. |
| data[].redirect_target | string or null | Where a redirect points. |
| data[].content_type | string or null | The response's Content-Type. |
| data[].response_time_ms | integer or null | How long the response took, in milliseconds. |
| data[].found_on_page | string or null | A page that links here (the first one the crawl met). |
| data[].found_via | string or null | link, sitemap or list: how the crawl first reached it. |
| data[].inbound_links | integer or null | How many pages link here; null when the crawl did not cover the whole site. |
| data[].in_sitemap | boolean or null | Listed in the sitemap; null when the sitemap was not read in full. |
| data[].crawl_depth | integer or null | Clicks from the start page. |
| data[].title | string or null | The page title; the SEO columns are filled only by a detailed crawl. |
| data[].meta_description | string or null | The meta description. |
| data[].h1 | string or null | The first H1. |
| data[].word_count | integer or null | Words in the main text. |
| data[].title_pixel_width | integer or null | Estimated width of the title in a search result, in pixels. |
| data[].meta_description_pixel_width | integer or null | Estimated width of the meta description, in pixels. |
| data[].canonical_url | string or null | The canonical URL the page names. |
| data[].meta_robots | string or null | The robots meta directives. |
| data[].indexability | string or null | indexable, non_indexable or unknown; null for anything but a page. |
| data[].indexability_reason | string or null | Why: noindex, canonicalised, a status class, robots_txt, not_checked or needs_detailed_scan. |
| data[].issues | array of string | The issue codes the page carries, most severe first. Look them up in GET /issue-types; ignore codes you do not know. |
| data[].seo_crawl_id | string or null | The detailed crawl the SEO columns and issues come from. |
| data[].marked_resolved_at | string or null | When someone marked the URL resolved in the dashboard. |
| data[].marked_resolved_note | string or null | The note left with that mark. |
| data[].rechecked_at | string or null | When the URL was last rechecked on its own. |
| data[].first_seen_at | string or null | When a crawl first saw the URL (ISO 8601 UTC). |
| data[].last_seen_at | string or null | When a crawl last saw it. |
| data[].missing_since | string or null | Set when a later complete crawl no longer found the URL. |
| data[].previous_status_code | integer or null | The status before the latest change. |
| data[].status_changed_at | string or null | When the status last changed. |
| data[].content_inlinks | integer or null | Inlinks from the main content, not menus or footers. Present only while the link graph is switched on for the platform; null = not measured. |
| data[].external_outlinks | integer or null | Links to other sites. Present only while the link graph is switched on for the platform; null = not measured. |
| data[].link_score | integer or null | PerfBee Link Score, 1 to 100. Present only while the link graph is switched on for the platform; null = not measured. |
| data[].nofollow_inlinks | integer or null | Inlinks that carry rel=nofollow. Present only while the link graph is switched on for the platform; null = not measured. |
| data[].outlinks | integer or null | Links on the page. Present only while the link graph is switched on for the platform; null = not measured. |
| data[].unique_outlinks | integer or null | Distinct link targets on the page. Present only while the link graph is switched on for the platform; null = not measured. |
| has_more | boolean | True when another page follows. |
| inventory | object | Which crawl the inventory shows and how much of the site it covers: read it before trusting a list. |
| inventory.crawl | object or null | The newest crawl written into the inventory; null before the first crawl. |
| inventory.crawl.id | string | The crawl's id (a UUID). |
| inventory.crawl.completed_at | string or null | When the crawl finished. |
| inventory.crawl.mode | string or null | fast or full. |
| inventory.crawl.close_reason | string or null | Why it ended, when not because it finished its work. |
| inventory.coverage | string or null | complete: the crawl covered the site and marked URLs it no longer found as missing. update: it only refreshed what it saw. |
| inventory.coverage_reason | string or null | Why the newest crawl only updated, in words; null when it was complete. |
| inventory.seo_crawl | object or null | The newest detailed crawl the SEO columns come from. |
| inventory.seo_crawl.id | string | The crawl's id (a UUID). |
| inventory.seo_crawl.completed_at | string or null | When the crawl finished. |
| inventory.seo_crawl.mode | string or null | fast or full. |
| inventory.seo_crawl.close_reason | string or null | Why it ended, when not because it finished its work. |
| inventory.sync | object | |
| inventory.sync.status | string or null | ok, running or failed: whether the inventory is being written or the write failed. |
| inventory.sync.error | string or null | A short reason when status is failed. |
| inventory.sync.synced_at | string or null | When the inventory was last written. |
| inventory.url_count | integer | URLs in the inventory, every view together. |
| counts | object or null | Only with include=count. |
| counts.views | object | Every view over the whole site, whatever the filters. |
| counts.views.pages | integer | HTML pages of the site. |
| counts.views.resources | integer | Images, scripts, stylesheets, fonts, media and the site's own files. |
| counts.views.links_out | integer | Pages and files outside the site or its folder that the site links to. |
| counts.views.missing | integer | URLs a later complete crawl no longer found. |
| counts.in_view | integer | Rows in the chosen view after prefix and q, before the other filters. |
| counts.matched | integer | Rows the list holds: the view with every filter applied. |
| counts.status | object | in_view split by status class. |
| counts.status.ok | integer | |
| counts.status.redirect | integer | |
| counts.status.broken | integer | |
| counts.status.gone | integer | |
| counts.status.not_checked | integer | |
| counts.status.unknown | integer | |
| counts.indexability | object | in_view split by indexability. |
| counts.indexability.indexable | integer | |
| counts.indexability.non_indexable | integer | |
| counts.indexability.unknown | integer | |
| counts.issues | object | in_view rows carrying each issue code (codes with none are left out). |
| counts.change | object | |
| counts.change.new | integer | Seen for the first time by the newest crawl. |
| counts.change.changed | integer | Whose status changed in the newest crawl. |
| counts.resolved | integer | Rows in in_view that someone marked resolved. |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"id": 48120,
"url": "https://www.example.com/shoes/trail-runner",
"url_type": "page",
"link_scope": "internal",
"status_class": "ok",
"status_code": 200,
"content_type": "text/html; charset=utf-8",
"response_time_ms": 412,
"found_on_page": "https://www.example.com/shoes",
"found_via": "link",
"inbound_links": 37,
"in_sitemap": true,
"crawl_depth": 2,
"title": "Trail Runner | Example Shoes",
"h1": "Trail Runner",
"word_count": 214,
"title_pixel_width": 392,
"canonical_url": "https://www.example.com/shoes/trail-runner",
"indexability": "indexable",
"issues": [
"missing_meta_description"
],
"seo_crawl_id": "0b6f1d52-8a41-4f0e-9d7b-3c5e21a7f9aa",
"first_seen_at": "2026-09-02T04:11:09Z",
"last_seen_at": "2026-10-30T03:52:41Z"
}
],
"has_more": true,
"inventory": {
"crawl": {
"id": "7d1c0f94-2b8e-4a73-9a10-5f3d8e6c1b02",
"completed_at": "2026-10-30T03:52:41Z",
"mode": "full"
},
"coverage": "complete",
"seo_crawl": {
"id": "7d1c0f94-2b8e-4a73-9a10-5f3d8e6c1b02",
"completed_at": "2026-10-30T03:52:41Z",
"mode": "full"
},
"sync": {
"status": "ok",
"synced_at": "2026-10-30T03:58:12Z"
},
"url_count": 5120
},
"next_cursor": "eyJ2IjoxfQ.opaque"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 404 Not found:
not_found - 422 Validation failed:
validation_error - 429 Too many heavy calls at once:
too_many_heavy_requests - 503 Service busy:
capacity_busy - 503 Query too slow:
query_too_slow
Get one page of a site
GET/sites/{site_id}/pages/{page_id}
- Scope:
- read
- Rate class:
- light
- Operation id:
- get_site_page
One row of the site's inventory with the issues on it in words (labels from GET /issue-types) and the pages that link to it in the newest crawl that saw it. The crawl stores at most 200 linking pages per URL and this answer lists the first 50 by URL; linking_pages says the cap, how many are listed and how many pages link here in all. Needs the read scope; free of credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | path | integerrequired | The site's id (GET /sites).Range: 1 to 9223372036854776000. |
| page_id | path | integerrequired | The page's id (GET /sites/{site_id}/pages).Range: 1 to 9223372036854776000. |
Answer 200
OK. The body is a SitePageDetail.
| Field | Type | Description |
|---|---|---|
| page | object | One row of the site's page inventory (Site Pages in the dashboard): a URL, what the newest crawl saw there, and what it found wrong. |
| page.id | integer | The page's id inside the site (a 64-bit integer: store it as one). |
| page.url | string | The URL exactly as the crawl stored it. |
| page.url_type | string | page, document, other, image, css, js, font, video, iframe, audio or embed. |
| page.link_scope | string or null | internal, outside_folder (same host, outside the folder of a sub-path site) or external. |
| page.status_class | string or null | ok, redirect, broken, gone (HTTP 410), not_checked (the crawler was refused, see block_reason) or unknown. |
| page.status_code | integer or null | The HTTP status the crawl got; null when there was none. |
| page.block_reason | string or null | Why a URL was not checked (bot wall, robots.txt...); set only with status_class not_checked. |
| page.error_message | string or null | The connection error, when the request failed before an HTTP status. |
| page.redirect_target | string or null | Where a redirect points. |
| page.content_type | string or null | The response's Content-Type. |
| page.response_time_ms | integer or null | How long the response took, in milliseconds. |
| page.found_on_page | string or null | A page that links here (the first one the crawl met). |
| page.found_via | string or null | link, sitemap or list: how the crawl first reached it. |
| page.inbound_links | integer or null | How many pages link here; null when the crawl did not cover the whole site. |
| page.in_sitemap | boolean or null | Listed in the sitemap; null when the sitemap was not read in full. |
| page.crawl_depth | integer or null | Clicks from the start page. |
| page.title | string or null | The page title; the SEO columns are filled only by a detailed crawl. |
| page.meta_description | string or null | The meta description. |
| page.h1 | string or null | The first H1. |
| page.word_count | integer or null | Words in the main text. |
| page.title_pixel_width | integer or null | Estimated width of the title in a search result, in pixels. |
| page.meta_description_pixel_width | integer or null | Estimated width of the meta description, in pixels. |
| page.canonical_url | string or null | The canonical URL the page names. |
| page.meta_robots | string or null | The robots meta directives. |
| page.indexability | string or null | indexable, non_indexable or unknown; null for anything but a page. |
| page.indexability_reason | string or null | Why: noindex, canonicalised, a status class, robots_txt, not_checked or needs_detailed_scan. |
| page.issues | array of string | The issue codes the page carries, most severe first. Look them up in GET /issue-types; ignore codes you do not know. |
| page.seo_crawl_id | string or null | The detailed crawl the SEO columns and issues come from. |
| page.marked_resolved_at | string or null | When someone marked the URL resolved in the dashboard. |
| page.marked_resolved_note | string or null | The note left with that mark. |
| page.rechecked_at | string or null | When the URL was last rechecked on its own. |
| page.first_seen_at | string or null | When a crawl first saw the URL (ISO 8601 UTC). |
| page.last_seen_at | string or null | When a crawl last saw it. |
| page.missing_since | string or null | Set when a later complete crawl no longer found the URL. |
| page.previous_status_code | integer or null | The status before the latest change. |
| page.status_changed_at | string or null | When the status last changed. |
| page.content_inlinks | integer or null | Inlinks from the main content, not menus or footers. Present only while the link graph is switched on for the platform; null = not measured. |
| page.external_outlinks | integer or null | Links to other sites. Present only while the link graph is switched on for the platform; null = not measured. |
| page.link_score | integer or null | PerfBee Link Score, 1 to 100. Present only while the link graph is switched on for the platform; null = not measured. |
| page.nofollow_inlinks | integer or null | Inlinks that carry rel=nofollow. Present only while the link graph is switched on for the platform; null = not measured. |
| page.outlinks | integer or null | Links on the page. Present only while the link graph is switched on for the platform; null = not measured. |
| page.unique_outlinks | integer or null | Distinct link targets on the page. Present only while the link graph is switched on for the platform; null = not measured. |
| crawl_id | string or null | The newest crawl that saw this URL. |
| issue_details | array of object | page.issues decoded with the catalog, same order. |
| issue_details[].code | string | |
| issue_details[].label | string | The issue in words. |
| issue_details[].severity | string | error, warning or info. |
| issue_details[].category | string | indexing, content, social, asset, security or links. |
| linking_pages | object | |
| linking_pages.total | integer | Pages that link here, as the newest crawl counted them (can exceed what is stored). |
| linking_pages.stored_max | integer | The most linking pages a crawl stores per URL, whatever the list shows. |
| linking_pages.limit | integer | The most this answer lists. The list is capped; it has no cursor. |
| linking_pages.items | array of object | Sorted by the linking page's URL. |
| linking_pages.items[].source_url | string or null | The page that links to this URL. |
| linking_pages.items[].anchor_text | string or null | The link's anchor text (an image's alt text). |
| linking_pages.items[].element_xpath | string or null | Where in the page the link sits: the HTML element to edit. |
| linking_pages.items[].link | object or null | What the first link from that page is. Present only while the link graph is switched on for the platform; null = not measured. |
{
"page": {
"id": 48120,
"url": "https://www.example.com/shoes/trail-runner",
"url_type": "page",
"link_scope": "internal",
"status_class": "ok",
"status_code": 200,
"content_type": "text/html; charset=utf-8",
"response_time_ms": 412,
"found_on_page": "https://www.example.com/shoes",
"found_via": "link",
"inbound_links": 37,
"in_sitemap": true,
"crawl_depth": 2,
"title": "Trail Runner | Example Shoes",
"h1": "Trail Runner",
"word_count": 214,
"title_pixel_width": 392,
"canonical_url": "https://www.example.com/shoes/trail-runner",
"indexability": "indexable",
"issues": [
"missing_meta_description"
],
"seo_crawl_id": "0b6f1d52-8a41-4f0e-9d7b-3c5e21a7f9aa",
"first_seen_at": "2026-09-02T04:11:09Z",
"last_seen_at": "2026-10-30T03:52:41Z"
},
"crawl_id": "7d1c0f94-2b8e-4a73-9a10-5f3d8e6c1b02",
"issue_details": [
{
"code": "missing_meta_description",
"label": "Missing meta description",
"severity": "warning",
"category": "content"
}
],
"linking_pages": {
"total": 37,
"stored_max": 200,
"limit": 50,
"items": [
{
"source_url": "https://www.example.com/shoes",
"anchor_text": "Trail Runner",
"element_xpath": "/html/body/main/ul/li[3]/a"
}
]
}
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error
List the webhook events
GET/webhook-events
- Scope:
- read
- Rate class:
- light
- Operation id:
- list_webhook_events
Every event a webhook endpoint can subscribe to, with the part of the product it belongs to and when it fires. It is one page: no limit or cursor. Needs the read scope; free of credits.
Answer 200
OK. A page of WebhookEventType objects, with next_cursor and has_more.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].event | string | The event name a webhook endpoint subscribes to. |
| data[].group | string | The part of the product it belongs to. |
| data[].label | string | The event in words. |
| data[].description | string | When it fires. |
| has_more | boolean | True when another page follows. |
| next_cursor | string or null | Pass as cursor to get the next page; null on the last page. |
{
"data": [
{
"event": "scan_completed",
"group": "Site Crawler",
"label": "Scan completed",
"description": "Every finished crawl, with or without broken links."
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}