List crawls
GET/crawls
- Scope:
- read
- Rate class:
- light
- Operation id:
- list_crawls
Your crawls, newest first. These are the crawls you started, scheduled or that started a new site (the dashboard's Scans list); the product's own "Check if fixed" runs are not crawls. Filter by site_id (the dashboard's per-site list: a root site also lists its sub-folder crawls), status, mode and since (crawls queued at or after this time, ISO 8601, UTC when it has no zone; write the zone as Z or %2B02:00). Paged with cursor. Each crawl carries its live progress and processing, so one call is enough to follow several crawls. Free; needs read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | query | integeroptional | Only crawls of this site (an id from GET /sites).Range: 1 to 2147483647. |
| status | query | stringoptional | Only crawls in this status.One of: pending, running, completed, failed, cancelled, partial. |
| mode | query | stringoptional | Only fast or only full (detailed) crawls.One of: fast, full. |
| since | query | stringoptional | Only crawls queued at or after this time (ISO 8601; UTC if no zone). |
| 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 CrawlPage.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | string | The crawl's id (a UUID). |
| data[].site_id | integer or null | The most specific site of yours this crawl belongs to; null when the site was removed. |
| data[].url | string | The address the crawl started from. |
| data[].mode | string | fast (links and status only) or full (SEO data and AI readiness too). |
| data[].status | string | pending, running, completed, failed, cancelled or partial. New values may appear. |
| data[].started_via | string | What started the crawl.One of: dashboard, schedule, mcp, api. |
| data[].created_at | string or null | When the crawl was queued. |
| data[].started_at | string or null | When the crawler started; null while queued. |
| data[].completed_at | string or null | When it ended; null while pending or running. |
| data[].duration_seconds | number or null | How long it ran, once it ended. |
| data[].max_pages | integer or null | The page limit the crawl was started with; null = no limit. |
| data[].pages_checked | integer | Pages read (final once the crawl ended; see progress while it runs). |
| data[].links_checked | integer | URLs checked, links and files included. |
| data[].broken_count | integer | Broken URLs found. |
| data[].working_count | integer | URLs that worked. |
| data[].results | object or null | The result buckets stored when the crawl ended; null while it runs and for crawls from before 2026-10-20. |
| data[].results.total | integer | Checked URLs: ok + redirects + broken + not_checked. |
| data[].results.ok | integer | URLs that answered normally. |
| data[].results.redirects | integer | URLs that redirected. |
| data[].results.broken | integer | URLs that are broken (404, 5xx, no answer...). |
| data[].results.not_checked | integer | URLs the crawler could not check (a bot wall, robots.txt, a timeout...); never billed, never broken. |
| data[].close_reason | string or null | Why the crawler stopped: finished, closespider_pagecount, blocked, rate_limited, credit_limit... New values may appear. |
| data[].crawl_scope | object or null | What the crawl covered (checks on or off, sitemap, render mode, folder, list mode...). Keys may be added. |
| data[].error | string or null | Why a crawl that stopped early stopped, in a sentence; null otherwise. |
| data[].credits_charged | number or null | Credits this crawl cost so far; null when nothing was charged (queued, free, or nothing checked). |
| data[].progress | object or null | Live counters while the crawl is pending or running; null otherwise. |
| data[].progress.pages_checked | integer | Pages read so far. |
| data[].progress.links_checked | integer | Links and files checked so far. |
| data[].progress.broken_count | integer | Broken URLs found so far. |
| data[].progress.max_pages | integer or null | The page limit of the crawl; null = no limit. |
| data[].progress.updated_at | string or null | When the crawler last reported (a heartbeat each minute). |
| data[].processing | object | Where the crawl's data stands after the crawl itself. A crawl is completed before its pages and fixes are ready. |
| data[].processing.inventory | string | Site Pages (GET /sites/{site_id}/pages) hold this crawl's data. pending = on its way, failed = the sync failed (it is retried for 48 hours), not_applicable = no site of yours takes this crawl in.One of: pending, done, failed, not_applicable. |
| data[].processing.fixes | string | The fix list was refreshed from this crawl. skipped = the inventory sync failed, so no refresh ran; not_applicable = Fixes are off or no site takes the crawl in. The scan_data_ready webhook is the exact moment.One of: pending, done, skipped, not_applicable. |
| data[].report_url | string | The same crawl in the dashboard. |
| 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": "5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90",
"site_id": 7,
"url": "https://www.example.com/",
"mode": "full",
"status": "completed",
"started_via": "schedule",
"created_at": "2026-10-30T02:00:00Z",
"started_at": "2026-10-30T02:00:04Z",
"completed_at": "2026-10-30T02:14:51Z",
"duration_seconds": 887.2,
"max_pages": 5000,
"pages_checked": 1840,
"links_checked": 9215,
"broken_count": 12,
"working_count": 9020,
"results": {
"total": 9215,
"ok": 9020,
"redirects": 160,
"broken": 12,
"not_checked": 23
},
"close_reason": "finished",
"crawl_scope": {
"check_external": false,
"check_internal": true,
"crawl_sitemap": true,
"folder": "",
"list_mode": false,
"render_js": false
},
"credits_charged": 3680,
"processing": {
"inventory": "done",
"fixes": "done"
},
"report_url": "https://dashboard.perfbee.com/#/brokenLinks?scan=5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90"
}
],
"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 crawl
GET/crawls/{crawl_id}
- Scope:
- read
- Rate class:
- light
- Operation id:
- get_crawl
One crawl: status, counts, what it covered, credits charged, live progress while it runs and processing, which says whether the crawl's pages and fixes are ready yet (a crawl is completed before they are). Add wait (1 to 25 seconds) to hold the call while the crawl is pending or running: it returns as soon as the crawl is over, or after wait seconds with the current state; repeat until status is final. A wait holds one server thread but no database connection; when this server has too many waits open it answers at once with Retry-After instead of waiting. A crawl of another workspace is 404. Free; needs read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| crawl_id | path | stringrequired | The crawl's id (a UUID): from GET /crawls or the answer of POST /crawls. |
| wait | query | integeroptional | Seconds to wait for a pending or running crawl to end, 0 to 25.Range: 0 to 25. Default: 0. |
Answer 200
The crawl. Retry-After is set when a wait was asked for but this server has too many waits open: the answer is the current state, poll again after that many seconds. Headers: Retry-After.
| Field | Type | Description |
|---|---|---|
| id | string | The crawl's id (a UUID). |
| site_id | integer or null | The most specific site of yours this crawl belongs to; null when the site was removed. |
| url | string | The address the crawl started from. |
| mode | string | fast (links and status only) or full (SEO data and AI readiness too). |
| status | string | pending, running, completed, failed, cancelled or partial. New values may appear. |
| started_via | string | What started the crawl.One of: dashboard, schedule, mcp, api. |
| created_at | string or null | When the crawl was queued. |
| started_at | string or null | When the crawler started; null while queued. |
| completed_at | string or null | When it ended; null while pending or running. |
| duration_seconds | number or null | How long it ran, once it ended. |
| max_pages | integer or null | The page limit the crawl was started with; null = no limit. |
| pages_checked | integer | Pages read (final once the crawl ended; see progress while it runs). |
| links_checked | integer | URLs checked, links and files included. |
| broken_count | integer | Broken URLs found. |
| working_count | integer | URLs that worked. |
| results | object or null | The result buckets stored when the crawl ended; null while it runs and for crawls from before 2026-10-20. |
| results.total | integer | Checked URLs: ok + redirects + broken + not_checked. |
| results.ok | integer | URLs that answered normally. |
| results.redirects | integer | URLs that redirected. |
| results.broken | integer | URLs that are broken (404, 5xx, no answer...). |
| results.not_checked | integer | URLs the crawler could not check (a bot wall, robots.txt, a timeout...); never billed, never broken. |
| close_reason | string or null | Why the crawler stopped: finished, closespider_pagecount, blocked, rate_limited, credit_limit... New values may appear. |
| crawl_scope | object or null | What the crawl covered (checks on or off, sitemap, render mode, folder, list mode...). Keys may be added. |
| error | string or null | Why a crawl that stopped early stopped, in a sentence; null otherwise. |
| credits_charged | number or null | Credits this crawl cost so far; null when nothing was charged (queued, free, or nothing checked). |
| progress | object or null | Live counters while the crawl is pending or running; null otherwise. |
| progress.pages_checked | integer | Pages read so far. |
| progress.links_checked | integer | Links and files checked so far. |
| progress.broken_count | integer | Broken URLs found so far. |
| progress.max_pages | integer or null | The page limit of the crawl; null = no limit. |
| progress.updated_at | string or null | When the crawler last reported (a heartbeat each minute). |
| processing | object | Where the crawl's data stands after the crawl itself. A crawl is completed before its pages and fixes are ready. |
| processing.inventory | string | Site Pages (GET /sites/{site_id}/pages) hold this crawl's data. pending = on its way, failed = the sync failed (it is retried for 48 hours), not_applicable = no site of yours takes this crawl in.One of: pending, done, failed, not_applicable. |
| processing.fixes | string | The fix list was refreshed from this crawl. skipped = the inventory sync failed, so no refresh ran; not_applicable = Fixes are off or no site takes the crawl in. The scan_data_ready webhook is the exact moment.One of: pending, done, skipped, not_applicable. |
| report_url | string | The same crawl in the dashboard. |
{
"id": "5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90",
"site_id": 7,
"url": "https://www.example.com/",
"mode": "full",
"status": "completed",
"started_via": "schedule",
"created_at": "2026-10-30T02:00:00Z",
"started_at": "2026-10-30T02:00:04Z",
"completed_at": "2026-10-30T02:14:51Z",
"duration_seconds": 887.2,
"max_pages": 5000,
"pages_checked": 1840,
"links_checked": 9215,
"broken_count": 12,
"working_count": 9020,
"results": {
"total": 9215,
"ok": 9020,
"redirects": 160,
"broken": 12,
"not_checked": 23
},
"close_reason": "finished",
"crawl_scope": {
"check_external": false,
"check_internal": true,
"crawl_sitemap": true,
"folder": "",
"list_mode": false,
"render_js": false
},
"credits_charged": 3680,
"processing": {
"inventory": "done",
"fixes": "done"
},
"report_url": "https://dashboard.perfbee.com/#/brokenLinks?scan=5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90"
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error
Get a crawl's AI readiness report
GET/crawls/{crawl_id}/ai-readiness
- Scope:
- read
- Rate class:
- heavy
- Operation id:
- get_crawl_ai_readiness
The AI Search Readiness report of a detailed crawl, as the dashboard shows it: which AI bots can reach the site (from robots.txt), the site-level checks, and exact page counts per issue. A fast crawl and a crawl that never got the report are 404 ai_readiness_unavailable (reason: fast_mode or missing); a crawl that is running, or whose report is still being written, is 409 crawl_not_ready. Free; needs read; counts as a heavy call (the page counts are worked out from the crawl's pages on every call).
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| crawl_id | path | stringrequired | The crawl's id (a UUID): from GET /crawls or the answer of POST /crawls. |
Answer 200
OK. The body is a AiReadiness.
| Field | Type | Description |
|---|---|---|
| status | string | ready. (A crawl whose report is still being written answers 409 crawl_not_ready.) |
| bots | array of object | One row per AI bot: can it reach the site, from robots.txt. |
| bots[].token | string | The bot's robots.txt name. |
| bots[].vendor | string | Who runs it. |
| bots[].purpose | string | search, user (fetches a page when a person asks) or training. |
| bots[].feeds | string | What it feeds (ChatGPT search, AI Overviews...). |
| bots[].kind | string | crawler, or token (a robots.txt name another crawler obeys). |
| bots[].robots_binding | boolean | False when the vendor says the agent may ignore robots.txt (a Disallow is then a request). |
| bots[].robots_note | string | A note on how the vendor treats robots.txt; may be empty. |
| bots[].robots | object or null | What the site's robots.txt says for this bot; null when it was not read. |
| bots[].reachable | boolean or null | False when robots.txt disallows the homepage for it; null when unknown. |
| robots | object or null | The site's robots.txt as read: issues, Content-Signal lines, CDN hints. |
| probe | object or null | The sample-page fetch of the report (sections as stored); may be null. |
| render | array of object | Sample pages: what the raw HTML holds compared with the rendered page. |
| site | object or null | Site-level checks: brand identity, llms.txt and the like (sections as stored). |
| checked_at | string or null | When the report was made. |
| pages | object | |
| pages.total | integer | Pages that answered 200. |
| pages.quotable | integer | Pages without an issue that keeps them out of AI answers. |
| pages.issues | array of object | The issues found, with exact page counts. |
| pages.issues[].code | string | The issue's stable code (noindex, nosnippet, js_shell...). |
| pages.issues[].label | string | The issue in words. |
| pages.issues[].severity | string | error or warning. |
| pages.issues[].help | string | What it means and how to fix it. |
| pages.issues[].count | integer | Pages with the issue (distinct URLs). |
{
"status": "ready",
"bots": [
{
"token": "OAI-SearchBot",
"vendor": "OpenAI",
"purpose": "search",
"feeds": "ChatGPT search",
"kind": "crawler",
"robots_binding": true,
"robots_note": "",
"robots": {
"home_allowed": true
},
"reachable": true
}
],
"robots": {
"issues": []
},
"render": [],
"site": {
"llms_txt": false
},
"checked_at": "2026-10-30T02:16:10+00:00",
"pages": {
"total": 1620,
"quotable": 1588,
"issues": [
{
"code": "noindex",
"label": "Not indexable",
"severity": "error",
"help": "noindex in the robots meta tag or X-Robots-Tag header.",
"count": 32
}
]
}
}Problems this call documents
- 404 No AI readiness report:
ai_readiness_unavailable - 404 Not found:
not_found - 409 Crawl not ready:
crawl_not_ready - 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
Download a crawl's report as CSV or Excel
GET/crawls/{crawl_id}/exports/{slug}.{file_format}
- Scope:
- read
- Rate class:
- heavy
- Operation id:
- export_crawl
The dashboard's export of one crawl, byte for byte the same code: slug is the report (broken-links, all-urls, seo, ai-suggestions, duplicates, css-js, images, extraction, custom-search, orphans, redirect-chains, canonical-chains, elements, and links and anchor-text while the link graph is on), the format is csv or xlsx. Needs a plan with exports (Starter and up), and for ai-suggestions and elements the plan features the dashboard asks for. The whole report is served: no row selection and no filter. A report with more rows than a request may build (250,000 for CSV, 150,000 for Excel) is refused with 413 export_too_large, never cut; a crawl that is still running, or a report this crawl cannot make (links needs link capture), is 409 crawl_not_ready. Counts as a heavy call.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| crawl_id | path | stringrequired | The crawl's id (a UUID): from GET /crawls or the answer of POST /crawls. |
| slug | path | stringrequired | The report to download, for example broken-links: the operation description lists them. |
| file_format | path | stringrequired | csv or xlsx.One of: csv, xlsx. |
Answer 200
The file, built by the dashboard's own export code: same columns, same file name (Content-Disposition), same content as the dashboard's download. Content type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, text/csv.
Problems this call documents
- 403 Plan upgrade required:
plan_required - 404 Not found:
not_found - 409 Crawl not ready:
crawl_not_ready - 413 Export too large:
export_too_large - 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 the URLs a crawl checked
GET/crawls/{crawl_id}/links
- Scope:
- read
- Rate class:
- heavy
- Operation id:
- list_crawl_links
Every URL the crawl checked (pages, links, images, scripts...), in crawl order, paged with cursor: the status, whether it is broken, why it was not checked, its type, depth, how many pages link to it and where a redirect leads. Filters: is_broken, resource_type, status (the word) and status_code. include=count adds total (one extra count). v1 lists no source pages per link. A crawl that is still pending or running answers 409 crawl_not_ready. Free; needs read; counts as a heavy call.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| crawl_id | path | stringrequired | The crawl's id (a UUID): from GET /crawls or the answer of POST /crawls. |
| is_broken | query | booleanoptional | True: only broken URLs. False: everything else. |
| resource_type | query | stringoptional | Only this kind of URL.One of: link, image, css, js, video, iframe, audio, embed, object. |
| status | query | stringoptional | Only this status word.One of: working, redirect, broken, not_checked. |
| status_code | query | integeroptional | Only this HTTP status code.Range: 100 to 599. |
| include | query | anyoptional | count adds total: how many URLs match. |
| 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 LinkPage.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | The row's id, ascending in crawl order. Lists are paged by it; never build a cursor from it. |
| data[].url | string | The URL that was checked. |
| data[].status_code | integer or null | The HTTP status; null when there was no answer. |
| data[].status | string | working, redirect (3xx), broken, or not_checked (the crawler could not check it: see block_reason).One of: working, redirect, broken, not_checked. |
| data[].is_broken | boolean | True when the crawler found it broken. |
| data[].block_reason | string or null | Why a not_checked URL was not checked (bot_protection, rate_limited, access_denied, robots_txt...). |
| data[].resource_type | string or null | link, image, css, js, video, iframe, audio, embed or object. |
| data[].link_type | string or null | internal, external, or out_of_scope (on your host, outside the folder the crawl stayed in). |
| data[].depth | integer or null | Clicks from the start page; null when no crawled page links to it. |
| data[].inbound_links | integer | How many pages of the crawl link to it. |
| data[].redirect_target | string or null | Where a redirect leads. |
| data[].error | string or null | The crawler's short note on a failed check. |
| data[].response_time_ms | number or null | How long the answer took. |
| 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. |
| total | integer or null | How many URLs match the filters; only with include=count, otherwise null. |
{
"data": [
{
"id": 48213,
"url": "https://www.example.com/old-guide",
"status_code": 404,
"status": "broken",
"is_broken": true,
"resource_type": "link",
"link_type": "internal",
"depth": 2,
"inbound_links": 14,
"response_time_ms": 182.4
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 404 Not found:
not_found - 409 Crawl not ready:
crawl_not_ready - 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 your sites
GET/sites
- Scope:
- read
- Rate class:
- light
- Operation id:
- list_sites
Your workspace's sites (My Sites in the dashboard), by id, each with its newest crawl, its next scheduled crawl, whether you verified the host, its crawl profile and whether the fast lane is on. Paged with cursor. Secrets of the site (crawl token, RUM token) are never part of the answer. Free; needs read.
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 SiteList.
| Field | Type | Description |
|---|---|---|
| data | array of object | The items of this page, in the endpoint's fixed order. |
| data[].id | integer | The site's id (64-bit integer): pass it as site_id. |
| data[].url | string | The address the site was added with. |
| data[].name | string or null | The name given to the site, if any. |
| data[].host | string | The site's host without www.: www and the bare domain are one site. |
| data[].crawl_profile | string | standard, or allowlisted: PerfBee asked the site's owner to let our crawler in and crawls it fast. |
| data[].verified | boolean | True when your workspace proved it controls the host (Settings > My Sites). |
| data[].owned_rate | boolean | True when crawls of this site are billed at the half owned-site rate (verified or allowlisted). |
| data[].fast_lane | boolean | True when the owner's self-serve fast lane is on for this verified host. |
| data[].last_crawl | object or null | The newest crawl of this site, whatever its status; null before the first. |
| data[].last_crawl.id | string | The crawl's id (a UUID): pass it to GET /crawls/{crawl_id}. |
| data[].last_crawl.url | string | The address the crawl started from. |
| data[].last_crawl.status | string | pending, running, completed, failed, cancelled or partial. New values may appear. |
| data[].last_crawl.mode | string | fast (links and status only) or full (SEO data and AI readiness too). |
| data[].last_crawl.started_at | string or null | When the crawler started; null while the crawl waits in the queue. |
| data[].last_crawl.completed_at | string or null | When the crawl ended; null while it is pending or running. |
| data[].last_crawl.pages_checked | integer | Pages the crawl read. |
| data[].last_crawl.broken_count | integer | Broken URLs the crawl found. |
| data[].last_crawl.report_url | string | The same crawl in the dashboard. |
| data[].schedule | object or null | The site's next scheduled crawl; null when none is active. |
| data[].schedule.frequency | string | daily, weekly or monthly. |
| data[].schedule.mode | string | fast or full: the mode the scheduled crawl runs in. |
| data[].schedule.next_run_at | string or null | When it runs next; null while it is paused. |
| data[].schedule.hour | integer or null | Hour of the day it runs, 0 to 23, in the schedule's own time zone. |
| data[].schedule.minute | integer | Minute of the hour, 0 to 59. |
| data[].schedule.day | integer or null | Day of the week (0 to 6) for weekly, of the month (1 to 31) for monthly. |
| data[].schedule.timezone | string or null | IANA time zone of hour and day; null = a fixed offset. |
| data[].schedule.timezone_offset_minutes | integer | The fixed offset from UTC in minutes, used when timezone is null. |
| data[].created_at | string or null | When the site was added. |
| 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": 7,
"url": "https://www.example.com/",
"name": "Example shop",
"host": "example.com",
"crawl_profile": "standard",
"verified": true,
"owned_rate": true,
"fast_lane": false,
"last_crawl": {
"id": "5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90",
"url": "https://www.example.com/",
"status": "completed",
"mode": "full",
"started_at": "2026-10-30T02:00:04Z",
"completed_at": "2026-10-30T02:14:51Z",
"pages_checked": 1840,
"broken_count": 12,
"report_url": "https://dashboard.perfbee.com/#/brokenLinks?scan=5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90"
},
"schedule": {
"frequency": "weekly",
"mode": "full",
"next_run_at": "2026-11-06T02:00:00Z",
"hour": 2,
"minute": 0,
"day": 4,
"timezone": "Europe/Berlin",
"timezone_offset_minutes": 0
},
"created_at": "2026-09-12T10:21:00Z"
}
],
"has_more": true,
"next_cursor": "eyJ2IjoxfQ.opaque"
}Problems this call documents
- 400 Invalid cursor:
invalid_cursor - 422 Validation failed:
validation_error
Get one site
GET/sites/{site_id}
- Scope:
- read
- Rate class:
- light
- Operation id:
- get_site
One of your sites with its newest crawl, schedule, verification, crawl profile and fast-lane flag. A site of another workspace, a removed site and an unknown id are all 404. Free; needs read.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
| site_id | path | integerrequired | The site's id (from GET /sites).Range: 1 to 2147483647. |
Answer 200
OK. The body is a Site.
| Field | Type | Description |
|---|---|---|
| id | integer | The site's id (64-bit integer): pass it as site_id. |
| url | string | The address the site was added with. |
| name | string or null | The name given to the site, if any. |
| host | string | The site's host without www.: www and the bare domain are one site. |
| crawl_profile | string | standard, or allowlisted: PerfBee asked the site's owner to let our crawler in and crawls it fast. |
| verified | boolean | True when your workspace proved it controls the host (Settings > My Sites). |
| owned_rate | boolean | True when crawls of this site are billed at the half owned-site rate (verified or allowlisted). |
| fast_lane | boolean | True when the owner's self-serve fast lane is on for this verified host. |
| last_crawl | object or null | The newest crawl of this site, whatever its status; null before the first. |
| last_crawl.id | string | The crawl's id (a UUID): pass it to GET /crawls/{crawl_id}. |
| last_crawl.url | string | The address the crawl started from. |
| last_crawl.status | string | pending, running, completed, failed, cancelled or partial. New values may appear. |
| last_crawl.mode | string | fast (links and status only) or full (SEO data and AI readiness too). |
| last_crawl.started_at | string or null | When the crawler started; null while the crawl waits in the queue. |
| last_crawl.completed_at | string or null | When the crawl ended; null while it is pending or running. |
| last_crawl.pages_checked | integer | Pages the crawl read. |
| last_crawl.broken_count | integer | Broken URLs the crawl found. |
| last_crawl.report_url | string | The same crawl in the dashboard. |
| schedule | object or null | The site's next scheduled crawl; null when none is active. |
| schedule.frequency | string | daily, weekly or monthly. |
| schedule.mode | string | fast or full: the mode the scheduled crawl runs in. |
| schedule.next_run_at | string or null | When it runs next; null while it is paused. |
| schedule.hour | integer or null | Hour of the day it runs, 0 to 23, in the schedule's own time zone. |
| schedule.minute | integer | Minute of the hour, 0 to 59. |
| schedule.day | integer or null | Day of the week (0 to 6) for weekly, of the month (1 to 31) for monthly. |
| schedule.timezone | string or null | IANA time zone of hour and day; null = a fixed offset. |
| schedule.timezone_offset_minutes | integer | The fixed offset from UTC in minutes, used when timezone is null. |
| created_at | string or null | When the site was added. |
{
"id": 7,
"url": "https://www.example.com/",
"name": "Example shop",
"host": "example.com",
"crawl_profile": "standard",
"verified": true,
"owned_rate": true,
"fast_lane": false,
"last_crawl": {
"id": "5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90",
"url": "https://www.example.com/",
"status": "completed",
"mode": "full",
"started_at": "2026-10-30T02:00:04Z",
"completed_at": "2026-10-30T02:14:51Z",
"pages_checked": 1840,
"broken_count": 12,
"report_url": "https://dashboard.perfbee.com/#/brokenLinks?scan=5b0a5e4c-2f1e-4b8e-9c47-1d3f6a7b8c90"
},
"schedule": {
"frequency": "weekly",
"mode": "full",
"next_run_at": "2026-11-06T02:00:00Z",
"hour": 2,
"minute": 0,
"day": 4,
"timezone": "Europe/Berlin",
"timezone_offset_minutes": 0
},
"created_at": "2026-09-12T10:21:00Z"
}Problems this call documents
- 404 Not found:
not_found - 422 Validation failed:
validation_error