Skip to content
Developers

Issues reference

A crawl's issues, the diff against the previous crawl and issue totals.

Compare a crawl with the previous one

GET/crawls/{crawl_id}/diff

Scope:
read
Rate class:
heavy
Operation id:
get_crawl_diff

What changed between a completed crawl and the previous completed crawl of the same site in the same mode (or any other completed crawl of the site in the same mode: against). Counts of new, disappeared and changed pages and of new, fixed and persistent broken links. Only what this crawl saw counts as a change: a link is fixed when it was checked again and works, a page disappeared when it now answers an error or redirect or the crawl covered the whole site; not_rechecked and not_reached count the rest.

The summary of the previous-crawl comparison is counted once when the crawl is processed and served from there (source: stored). Pass include=lists for the first limit pages and links of each kind (it is counted for the request). Crawls of more than 100,000 pages are compared by their broken links alone (pages_compared: false; a comparison counted for a request does so above 25,000 pages). A crawl with no earlier crawl answers has_previous: false, not an error. A CI gate: curl .../crawls/$ID/diff | jq -e '.summary.new_broken == 0'.

Parameters

NameInTypeDescription
crawl_idpathstringrequiredThe crawl's id (a UUID).Range: at most 64 characters.
againstquerystringoptionalprevious (default), or the id of another completed crawl of the same site and mode.Range: at most 64 characters. Default: previous.
includequeryanyoptionallists: also the first limit items of each list.
limitqueryintegeroptionalItems per list with include=lists.Range: 1 to 200. Default: 50.

Answer 200

OK. The body is a CrawlDiff.

FieldTypeDescription
crawl_idstringThe crawl that was asked about (the current one).
has_previousbooleanFalse when there is no earlier completed crawl of the site in the same mode: everything else is then null.
currentobject
current.crawl_idstringThe crawl's id.
current.pagesintegerPages it checked.
current.completed_atstring or nullWhen it completed (UTC).
comparedstring or nullWhat was compared: pages (pages and links) or broken_links.
computed_atstring or nullWhen the summary was counted (UTC).
coverage_gapstring or nullWhy pages the current crawl did not reach are not listed as disappeared: stopped (it ended early) or scope (it covered less than the previous crawl).
listsobject or nullOnly with include=lists.
lists.changedarray of objectDefault: [].
lists.changed[].urlstring
lists.changed[].changesobjectBy field: status_code, title, meta_description, is_indexable, canonical_url.
lists.disappearedarray of objectDefault: [].
lists.disappeared[].urlstring
lists.disappeared[].previous_status_codeinteger or null
lists.disappeared[].redirect_tostring or nullWhere it redirects now, when it does; absent when redirects were not counted.
lists.disappeared[].status_codeinteger or nullWhat the page answers now; null when the crawl did not find it.
lists.disappeared[].titlestring or nullIts title in the previous crawl.
lists.fixed_brokenarray of stringDefault: [].
lists.new_brokenarray of stringDefault: [].
lists.new_pagesarray of objectDefault: [].
lists.new_pages[].urlstring
lists.new_pages[].status_codeinteger or null
lists.new_pages[].titlestring or null
pages_comparedboolean or nullFalse when only broken links were compared: a fast crawl, or crawls too big to compare page by page (pages_note says which).
pages_notestring or nullWhy pages were not compared.
previousobject or nullThe crawl it was compared with.
previous.crawl_idstringThe crawl's id.
previous.pagesintegerPages it checked.
previous.completed_atstring or nullWhen it completed (UTC).
sourcestring or nullstored (counted when the crawl was processed) or computed (counted for this request).
summaryobject or nullCounts of what changed. Only what the current crawl saw counts as a change: a link broken last time is fixed only when this crawl checked it again and it works; one it did not check again is not_rechecked.
summary.new_brokenintegerLinks broken now that were not broken in the previous crawl.
summary.fixed_brokenintegerLinks broken before that were checked again and work.
summary.not_recheckedintegerLinks broken before that this crawl did not check again: not counted as fixed.
summary.changedinteger or nullPages whose status, title, meta description, indexability or canonical changed. Null when pages were not compared.
summary.disappearedinteger or nullPages of the previous crawl that now answer an error or a redirect, or are gone from a crawl that covered the whole site. Null when pages were not compared.
summary.new_pagesinteger or nullPages not in the previous crawl. Null when pages were not compared.
summary.not_reachedinteger or nullPages of the previous crawl this one did not reach: not counted as gone. Null when pages were not compared.
summary.persistent_brokeninteger or nullLinks broken in both crawls.
summary.redirectedinteger or nullOf the disappeared pages, the ones that now redirect (often on purpose). Absent when redirects were not counted; null when pages were not compared.
truncatedobject or nullWith lists: which lists were cut at limit.
Example 200 answer of Compare a crawl with the previous one
{
  "crawl_id": "0b6c0f5e-6f3c-4a0e-9a53-2f6f3a3c7c11",
  "has_previous": true,
  "current": {
    "crawl_id": "0b6c0f5e-6f3c-4a0e-9a53-2f6f3a3c7c11",
    "pages": 1840,
    "completed_at": "2026-10-31T09:00:00Z"
  },
  "compared": "pages",
  "computed_at": "2026-10-31T09:14:02Z",
  "pages_compared": true,
  "previous": {
    "crawl_id": "8d1d7a2e-2a43-45a8-b7d1-6d0c3a8f9e02",
    "pages": 1832,
    "completed_at": "2026-10-30T09:00:00Z"
  },
  "source": "stored",
  "summary": {
    "new_broken": 2,
    "fixed_broken": 5,
    "not_rechecked": 0,
    "changed": 31,
    "disappeared": 0,
    "new_pages": 8,
    "not_reached": 0,
    "persistent_broken": 17
  }
}

Problems this call documents

List a crawl's issues

GET/crawls/{crawl_id}/issues

Scope:
read
Rate class:
heavy
Operation id:
list_crawl_issues

The issues of a completed crawl, most important first: broken links and resources (with the page and the HTML element they sit in, most linked first), page SEO issues, then AI readiness issues, each with a short fix. Filter by category, severity and code. Pages the site refused are counted as not checked, never as broken. A fast crawl has broken links only: asking for seo or ai_readiness answers an empty list.

Lists are cursor pages (next_cursor back as cursor, up to 100 per page). A page can hold fewer issues than limit while has_more is true: a filter that matches few issues is searched in bounded steps, so keep following the cursor until has_more is false. The first page also carries the crawl's totals, counted once when the crawl was processed (null with totals_status: pending until then).

Parameters

NameInTypeDescription
crawl_idpathstringrequiredThe crawl's id (a UUID).Range: at most 64 characters.
categoryquerystringoptionalOnly this category. Default: all of them.One of: broken, seo, ai_readiness.
severityquerystringoptionalOnly issues of this severity.One of: error, warning, info.
codequerystringoptionalOnly this issue code, for example missing_title.Range: at most 100 characters.
limitqueryintegeroptionalItems per page, 1 to 100.Range: 1 to 100. Default: 50.
cursorquerystringoptionalThe next_cursor of the previous page. Opaque: never build one.

Answer 200

OK. The body is a IssuePage.

FieldTypeDescription
dataarray of objectThe items of this page, in the endpoint's fixed order.
data[].idstringStable within the crawl: broken:<row>, seo:<page row>:<code> or ai:<page row>:<code>.
data[].categorystringbroken, seo or ai_readiness.
data[].codestringThe catalog code, for example broken_link, missing_title, js_shell. Unknown codes may appear.
data[].labelstringThe catalog's name for the code.
data[].severitystringerror, warning or info.
data[].fixstringWhat to do, in a sentence or two.
data[].detailstring or nullA short fact about this page's issue, for example max-snippet:50.
data[].elementobject or nullThe HTML element the issue sits in.
data[].element.selectorstring or nullPage issues: a CSS selector for the element to look at.
data[].element.tagstring or nullThe HTML tag of the element.
data[].element.textstring or nullBroken links: the anchor text (or alt text) of the link.
data[].element.xpathstring or nullBroken links: where the link sits in the page's HTML.
data[].occurrencesinteger or nullBroken links: how many pages link to the target (at least 1).
data[].page_urlstring or nullThe page the issue is on (for a broken link: the page that links to it).
data[].resource_typestring or nullBroken links: link, image, css, js, video...
data[].status_codeinteger or nullBroken links: the HTTP status the target answered, null when it did not answer.
data[].target_urlstring or nullBroken links: the URL that does not work.
has_morebooleanTrue when another page follows.
next_cursorstring or nullPass as cursor to get the next page; null on the last page.
totalsobject or nullFirst page only (no cursor): the crawl's issue totals; null when they are not stored (see totals_status) and on later pages.
totals.computed_atstringWhen they were counted (UTC).
totals.brokenintegerBroken links and resources. URLs the site refused are not counted as broken.
totals.not_checkedintegerURLs the site refused or that did not answer: neither working nor broken.
totals.ai_readinessobject or nullNull for a fast crawl.
totals.ai_readiness.totalintegerAI readiness issues found, one per page and code.
totals.ai_readiness.by_codeobjectPages per issue code (only codes that were found).
totals.seoobject or nullNull for a fast crawl (no page data), and while totals_status is incomplete.
totals.seo.pagesintegerPages the SEO checks read.
totals.seo.totalintegerPage issues found, one per page and code.
totals.seo.by_codeobjectPages per issue code (only codes that were found).
totals_statusstring or nullFirst page only. ready; incomplete (the SEO count ran out of time: seo is null); pending (the crawl's data is still being processed, or it is older than the totals: totals is null).
Example 200 answer of List a crawl's issues
{
  "data": [
    {
      "id": "broken:48213",
      "category": "broken",
      "code": "broken_link",
      "label": "Broken link",
      "severity": "error",
      "fix": "On https://shop.example/blog/care-guide the <a> at //*[@id=\"content\"]/p[3]/a points to https://shop.example/old-guide, which returns 404. Point it to the page that replaced it, or remove the link.",
      "element": {
        "tag": "a",
        "text": "Read the guide",
        "xpath": "//*[@id=\"content\"]/p[3]/a"
      },
      "occurrences": 12,
      "page_url": "https://shop.example/blog/care-guide",
      "resource_type": "link",
      "status_code": 404,
      "target_url": "https://shop.example/old-guide"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJ2IjoxfQ.opaque",
  "totals": {
    "computed_at": "2026-10-31T09:14:02Z",
    "broken": 29,
    "not_checked": 4,
    "ai_readiness": {
      "total": 65,
      "by_code": {
        "js_shell": 12,
        "noindex": 8
      }
    },
    "seo": {
      "pages": 64,
      "total": 265,
      "by_code": {
        "missing_og": 43,
        "missing_title": 9
      }
    }
  },
  "totals_status": "ready"
}

Problems this call documents