Skip to content
Developers

Crawl control reference

Start and cancel a crawl.

Start a crawl

POST/crawls

Scope:
crawl
Rate class:
start
Operation id:
start_crawl

Starts a crawl of one of your sites (My Sites) and returns at once with 202 Accepted, the crawl object and a Location header; follow it with GET /crawls/{id}. It goes through the same checks as the dashboard (public addresses only, blocked domains, your plan's rates, the owned-site rate for a verified site).

Credits. Credits are checked before the crawl exists: the most it can cost (max_pages times the page rate) must fit what the workspace has left this month, less what your other running API crawls may still spend (402 insufficient_credits otherwise; credits.available is already net of those holds). You are charged when the crawl ends, for the pages it checked.

Limits. A key's daily_credit_cap bounds the worst-case cost of the crawls it starts per UTC day (429 daily_credit_cap_reached); a key made without one may crawl at most 500 pages. Crawls of one site through the API must be 10 minutes apart (429 crawl_too_soon; a crawl cancelled before it started does not count). A key may start 10 crawls a day and the workspace 20; 2 may run at once. A site that already has a pending or running crawl answers 409 crawl_in_progress with that crawl's id.

Idempotency. Send Idempotency-Key (1 to 255 characters) to make a retry safe: the same key with the same body returns the crawl the first request started (200 and Idempotent-Replayed: true); the same key with another body is 422 idempotency_key_reused; a request still being made answers 409 idempotency_in_progress. Keys are remembered for 24 hours. Use something unique per intended crawl, for example a commit hash.

Needs the crawl scope. Until starting crawls through the API is switched on, this route answers 404.

Parameters

NameInTypeDescription
Idempotency-KeyheaderstringoptionalMakes a retry of this request safe: the same key and body return the crawl already started.Range: at most 255 characters.

Request body

application/json, required.

FieldTypeDescription
max_pagesintegeroptionalPages to check at most, 1 to 10,000. A key made without a daily credit cap (before the REST API) may ask for at most 500. The most the crawl can cost is max_pages times the page rate, and that must fit the workspace's credits and the key's daily credit cap.Range: 1 to 10000. Default: 100.
modestringoptionalfast: links only, the cheapest. full: links plus SEO data, structured data and AI readiness for every page (more credits per page).One of: fast, full. Default: fast.
preset_idinteger or nulloptionalA scan preset (a PerfBee preset or one of your workspace's): its checks, exclusions, depth and rules apply. mode, max_pages and render_js always come from this request.Range: at least 1.
render_jsbooleanoptionalRender pages that need JavaScript (smart rendering): rendered pages cost more.Default: false.
site_idinteger or nulloptionalA site of your workspace (My Sites): its id from the sites list.Range: at least 1.
urlstring or nulloptionalA site in My Sites, as an http:// or https:// URL. A URL with a path inside a registered site crawls that folder only. A URL that is not in My Sites is refused: add the site in PerfBee first.Range: at most 2000 characters.
Example request body of Start a crawl
{
  "max_pages": 500,
  "mode": "fast",
  "render_js": false,
  "site_id": 12
}

Answer 200

A retry of a start the Idempotency-Key had already made: the same crawl, nothing new was started. Headers: Idempotent-Replayed, Location.

FieldTypeDescription
idstringThe crawl's id (a UUID).
site_idinteger or nullThe most specific site of yours this crawl belongs to; null when the site was removed.
urlstringThe address the crawl started from.
modestringfast (links and status only) or full (SEO data and AI readiness too).
statusstringpending, running, completed, failed, cancelled or partial. New values may appear.
started_viastringWhat started the crawl.One of: dashboard, schedule, mcp, api.
created_atstring or nullWhen the crawl was queued.
started_atstring or nullWhen the crawler started; null while queued.
completed_atstring or nullWhen it ended; null while pending or running.
duration_secondsnumber or nullHow long it ran, once it ended.
max_pagesinteger or nullThe page limit the crawl was started with; null = no limit.
pages_checkedintegerPages read (final once the crawl ended; see progress while it runs).
links_checkedintegerURLs checked, links and files included.
broken_countintegerBroken URLs found.
working_countintegerURLs that worked.
resultsobject or nullThe result buckets stored when the crawl ended; null while it runs and for crawls from before 2026-10-20.
results.totalintegerChecked URLs: ok + redirects + broken + not_checked.
results.okintegerURLs that answered normally.
results.redirectsintegerURLs that redirected.
results.brokenintegerURLs that are broken (404, 5xx, no answer...).
results.not_checkedintegerURLs the crawler could not check (a bot wall, robots.txt, a timeout...); never billed, never broken.
close_reasonstring or nullWhy the crawler stopped: finished, closespider_pagecount, blocked, rate_limited, credit_limit... New values may appear.
crawl_scopeobject or nullWhat the crawl covered (checks on or off, sitemap, render mode, folder, list mode...). Keys may be added.
errorstring or nullWhy a crawl that stopped early stopped, in a sentence; null otherwise.
credits_chargednumber or nullCredits this crawl cost so far; null when nothing was charged (queued, free, or nothing checked).
progressobject or nullLive counters while the crawl is pending or running; null otherwise.
progress.pages_checkedintegerPages read so far.
progress.links_checkedintegerLinks and files checked so far.
progress.broken_countintegerBroken URLs found so far.
progress.max_pagesinteger or nullThe page limit of the crawl; null = no limit.
progress.updated_atstring or nullWhen the crawler last reported (a heartbeat each minute).
processingobjectWhere the crawl's data stands after the crawl itself. A crawl is completed before its pages and fixes are ready.
processing.inventorystringSite 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.fixesstringThe 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_urlstringThe same crawl in the dashboard.
render_jsbooleanTrue when pages are rendered with JavaScript.
creditsobject or nullThe credit estimate made when this request started the crawl; null on a replay.
credits.per_pagenumberCredits per page at the rate this crawl is billed (owned-site and render rates included).
credits.max_costnumberThe most the crawl can cost: max_pages times per_page. It is charged when the crawl ends, for the pages actually checked.
credits.remaining_beforenumber or nullCredits the workspace had left this month before this crawl; null = unlimited.
credits.held_by_running_crawlsnumberCredits the workspace's other running API crawls may still spend.
Example 200 answer of Start a crawl
{
  "id": "3f2b8c1e-5d47-4a0b-9a6e-1c7d2e8f4b90",
  "site_id": 12,
  "url": "https://www.example.com",
  "mode": "fast",
  "status": "pending",
  "started_via": "api",
  "created_at": "2026-10-31T09:12:44Z",
  "max_pages": 500,
  "pages_checked": 0,
  "links_checked": 0,
  "broken_count": 0,
  "working_count": 0,
  "processing": {
    "inventory": "pending",
    "fixes": "pending"
  },
  "report_url": "https://dashboard.perfbee.com/#/brokenLinks?scan=3f2b8c1e-5d47-4a0b-9a6e-1c7d2e8f4b90",
  "render_js": false,
  "credits": {
    "per_page": 0.5,
    "max_cost": 250,
    "remaining_before": 8759.5,
    "held_by_running_crawls": 0
  }
}

Answer 202

The crawl was queued. Location is its URL; Retry-After is when to look at it first. Headers: Location, Retry-After.

FieldTypeDescription
idstringThe crawl's id (a UUID).
site_idinteger or nullThe most specific site of yours this crawl belongs to; null when the site was removed.
urlstringThe address the crawl started from.
modestringfast (links and status only) or full (SEO data and AI readiness too).
statusstringpending, running, completed, failed, cancelled or partial. New values may appear.
started_viastringWhat started the crawl.One of: dashboard, schedule, mcp, api.
created_atstring or nullWhen the crawl was queued.
started_atstring or nullWhen the crawler started; null while queued.
completed_atstring or nullWhen it ended; null while pending or running.
duration_secondsnumber or nullHow long it ran, once it ended.
max_pagesinteger or nullThe page limit the crawl was started with; null = no limit.
pages_checkedintegerPages read (final once the crawl ended; see progress while it runs).
links_checkedintegerURLs checked, links and files included.
broken_countintegerBroken URLs found.
working_countintegerURLs that worked.
resultsobject or nullThe result buckets stored when the crawl ended; null while it runs and for crawls from before 2026-10-20.
results.totalintegerChecked URLs: ok + redirects + broken + not_checked.
results.okintegerURLs that answered normally.
results.redirectsintegerURLs that redirected.
results.brokenintegerURLs that are broken (404, 5xx, no answer...).
results.not_checkedintegerURLs the crawler could not check (a bot wall, robots.txt, a timeout...); never billed, never broken.
close_reasonstring or nullWhy the crawler stopped: finished, closespider_pagecount, blocked, rate_limited, credit_limit... New values may appear.
crawl_scopeobject or nullWhat the crawl covered (checks on or off, sitemap, render mode, folder, list mode...). Keys may be added.
errorstring or nullWhy a crawl that stopped early stopped, in a sentence; null otherwise.
credits_chargednumber or nullCredits this crawl cost so far; null when nothing was charged (queued, free, or nothing checked).
progressobject or nullLive counters while the crawl is pending or running; null otherwise.
progress.pages_checkedintegerPages read so far.
progress.links_checkedintegerLinks and files checked so far.
progress.broken_countintegerBroken URLs found so far.
progress.max_pagesinteger or nullThe page limit of the crawl; null = no limit.
progress.updated_atstring or nullWhen the crawler last reported (a heartbeat each minute).
processingobjectWhere the crawl's data stands after the crawl itself. A crawl is completed before its pages and fixes are ready.
processing.inventorystringSite 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.fixesstringThe 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_urlstringThe same crawl in the dashboard.
render_jsbooleanTrue when pages are rendered with JavaScript.
creditsobject or nullThe credit estimate made when this request started the crawl; null on a replay.
credits.per_pagenumberCredits per page at the rate this crawl is billed (owned-site and render rates included).
credits.max_costnumberThe most the crawl can cost: max_pages times per_page. It is charged when the crawl ends, for the pages actually checked.
credits.remaining_beforenumber or nullCredits the workspace had left this month before this crawl; null = unlimited.
credits.held_by_running_crawlsnumberCredits the workspace's other running API crawls may still spend.
Example 202 answer of Start a crawl
{
  "id": "3f2b8c1e-5d47-4a0b-9a6e-1c7d2e8f4b90",
  "site_id": 12,
  "url": "https://www.example.com",
  "mode": "fast",
  "status": "pending",
  "started_via": "api",
  "created_at": "2026-10-31T09:12:44Z",
  "max_pages": 500,
  "pages_checked": 0,
  "links_checked": 0,
  "broken_count": 0,
  "working_count": 0,
  "processing": {
    "inventory": "pending",
    "fixes": "pending"
  },
  "report_url": "https://dashboard.perfbee.com/#/brokenLinks?scan=3f2b8c1e-5d47-4a0b-9a6e-1c7d2e8f4b90",
  "render_js": false,
  "credits": {
    "per_page": 0.5,
    "max_cost": 250,
    "remaining_before": 8759.5,
    "held_by_running_crawls": 0
  }
}

Problems this call documents

Cancel a crawl

POST/crawls/{crawl_id}/cancel

Scope:
crawl
Rate class:
light
Operation id:
cancel_crawl

Asks for one of your workspace's crawls to stop. Only a crawl that is pending or running can be cancelled (409 crawl_not_active otherwise). A crawl that is queued, or waiting between two parts, ends at once; a running crawl is told to stop and the crawler does so within a few seconds, so the answer is 202 Accepted and the crawl's status becomes cancelled shortly after: watch GET /crawls/{id}.

Pages checked so far stay billed. The crawl is charged for the pages it checked before it stopped, as after a cancel in the dashboard; a crawl cancelled before it started costs nothing. Cancelling does not give credits back.

Needs the crawl scope (a viewer's key cannot). Until starting crawls through the API is switched on, this route answers 404.

Parameters

NameInTypeDescription
crawl_idpathstringrequiredThe crawl's id.Range: at most 64 characters.

Answer 202

Accepted. The body is a CrawlCancelRequested.

FieldTypeDescription
idstringThe crawl's id.
statusstringcancelled when the crawl ended at once (it was queued or waiting for its next part); pending or running while the crawler is stopping it.
cancelledbooleanTrue when the crawl is already cancelled; false when the crawler has been told and stops within a few seconds.
messagestringWhat happens next, in a sentence.
Example 202 answer of Cancel a crawl
{
  "id": "3f2b8c1e-5d47-4a0b-9a6e-1c7d2e8f4b90",
  "status": "running",
  "cancelled": false,
  "message": "The crawler was told to stop. It saves the pages it checked, which stay billed. Poll the crawl until its status is cancelled."
}

Problems this call documents