---
title: "Get site health"
method: GET
path: "/site-health"
tags: ["Site Health"]
---

# Get site health

`GET /site-health`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/site-health`
- Auth: one of:
  - API key in header `X-API-KEY`
  - HTTP bearer

Per-site health rollup derived from the site's most recent audit, merged with sitemap-discovered pages that have never been audited. Pages that were never audited appear with is_audited=false and null scores.

## Query parameters

- `site_id` string, uuid, required

## Response `200`

Site health overview

- object
  - `data` object
    - `site` object
      - `id` string, uuid
      - `name` string
      - `domain` string
      - `last_audit_at` string, date-time, nullable
      - `last_audit_score` integer, nullable
    - `audit_summary` string, nullable: The audit's stored summary text. Null when the site has never been audited or the run produced none.
    - `audit_stats` object, nullable: Null when the site has never been audited.
      - `overall_score` integer, nullable
      - `pages_audited` integer, nullable
      - `critical_issues` integer, nullable
      - `warning_issues` integer, nullable
      - `total_issues` integer, nullable
      - `score_delta` integer, nullable
      - `ai_visibility_score` integer, nullable
    - `pages` object[]
      - `id` string
      - `url` string
      - `path` string
      - `title` string: Falls back to the page path when the crawl recorded no title, so it is always a string here — unlike pages[].title on GET /audits/{id}, which is the raw column and can be null.
      - `is_homepage` boolean
      - `is_audited` boolean: False for a page discovered from the sitemap but never audited. Its scores are null.
      - `last_audit` string, date-time, nullable
      - `issues_count` integer
      - `score` integer, nullable: The latest canonical score for this site and URL. Null means the required score inputs were not all measured.
      - `score_basis_version` string, nullable: page-v2 for current rows. Historical rows may report their legacy basis.
      - `score_coverage` object, nullable: Coverage recorded by page-v2. Null on historical rows that predate coverage metadata.
        - `required` string[]
        - `measured` string[]
        - `missing` string[]
        - `complete` boolean
      - `technical_score` integer, nullable: Derived from indexability directives, canonical, head metadata, social/viewport tags, structured data, image alt text and broken internal links. Uses the authoritative stored projection when present; for historical rows where that projection is absent, derives the same score at read time from evidence already stored on the row without modifying it. This endpoint and GET /audits/{id} therefore report the same number for the same page. Null when no technical check could run at all — the fetch was unreliable, or no head was parsed and no head-independent check found anything — which is not the same as 0. Does not include page speed or internal-link authority. BREAKING CHANGE: on an audited page this was previously always an integer, computed as 100 minus 10 per issue the display taxonomy called technical, so a page nobody could read scored a confident number. It is now null in exactly those cases (it was already null on a never-audited page, where is_audited is false). Guard for null before arithmetic.
      - `content_score` integer, nullable
      - `aeo_score` integer, nullable
      - `is_tracked` boolean
      - `authority_score` integer, nullable: PageRank over the site's internal link graph, min-max normalized across the crawled set, so it is not comparable between runs of different sizes.
      - `is_orphan` boolean, nullable: null = the link graph was not measured; false genuinely means the page is not an orphan.
      - `is_dead_end` boolean, nullable: null = the link graph was not measured; false genuinely means the page is not a dead end.
      - `click_depth` integer, nullable: Clicks from the site root. null = not measured (the crawl contained no site root). -1 = measured and unreachable. >= 0 = depth.
      - `internal_links_in` integer, nullable
      - `internal_links_out` integer, nullable
      - `link_metrics_scope` 'site' | 'run' | 'unknown', nullable: Which node set this page's link metrics were measured over. "site" = the site-wide internal-link count named by authority_summary.link_metrics_measured_at; "run" = this page was audited AFTER that count, so its is_orphan / is_dead_end are withheld (null) rather than reported against a set they were never measured on; "unknown" = the site has never been counted site-wide, and the values are exactly what they always were. Without this field, is_orphan: null carries both meanings with nothing to tell them apart. Additive.
      - `word_count` integer, nullable
    - `average_scores` object
      - `technical` integer, nullable: Null when no page in the resolved set had a readable technical measurement — including a site that has never been audited. BREAKING CHANGE: this was previously always an integer and returned 0 in exactly those cases, so a never-measured site was indistinguishable from a site scoring 0. Guard for null before arithmetic.
      - `content` integer: 0 when no page had a content measurement. Unchanged.
      - `aeo` integer: 0 when no page had an AEO measurement. Unchanged.
    - `issue_summary` object
    - `last_audit_id` string, uuid, nullable
    - `authority_summary` object, nullable: Null when the site has no link-graph data.
      - `average_authority_score` integer: Mean authority over the pages that carry one. Always a number when authority_summary itself is present — the object is only emitted once at least one page has an authority score.
      - `links_measured` boolean: Whether the internal link graph's EDGES were measured. Keyed on is_orphan / is_dead_end, which are derived from the extracted internal links alone and need no site root. False when the crawl extracted no edges at all, in which case orphan_pages_count and dead_end_pages_count are absences rather than zeros.
      - `depth_measured` boolean: Whether click depth was measured. Keyed on click_depth ONLY, because it is the sole traversal column that is genuinely NULL when unmeasured (is_orphan carries a schema DEFAULT of false, so no null-check on it can detect an unwalked graph). False when the crawl contained no site root to run the breadth-first walk from — every manually scoped audit is in that state — in which case deep_pages_count is an absence rather than a zero. Independent of links_measured: a scoped audit is routinely links_measured=true, depth_measured=false.
      - `link_metrics_scope` 'site' | 'run' | 'mixed' | 'unknown': Which page set orphan_pages_count and dead_end_pages_count were measured over. "mixed" is the ordinary state in the window after a crawl: some pages were audited since the last site-wide count and are NOT included in the two counts — a boolean cannot express that, which is how a half-counted site published a confident 0. "unknown" = never counted site-wide; nothing is withheld. Additive.
      - `link_graph_node_count` integer, nullable: URLs in the site-wide link graph. A NODE count — one node per normalized URL — NOT a page count: it sits below the audited-row count wherever query-string variants collapsed onto one node, and it counts excluded rows that pages[] omits. Comparing it with pages.length is a category error. null = never counted. Additive.
      - `link_metrics_measured_at` string, date-time, nullable: When that site-wide count was taken. null = never counted. Additive.
      - `orphan_pages_count` integer: Pages with no inbound internal link. 0 when links_measured is false means 'not measured', not 'no orphans'.
      - `dead_end_pages_count` integer: Pages with no outbound internal link. Read together with links_measured.
      - `deep_pages_count` integer: Pages further than the depth threshold from the site root. Read together with depth_measured — depth is the one figure here that needs a root, so this is the count most often 0-by-absence.
      - `top_authority_pages` object[]
        - `id` string
        - `url` string
        - `title` string: Falls back to the page path, so never null.
        - `authority_score` integer
        - `internal_links_in` integer
        - `internal_links_out` integer
    - `has_gsc` boolean
  - `request_id` string

## Other responses

- `400`: site_id is missing or not a valid UUID.
- `404`: Site not found in this organization.

---

[API](https://skmtc.dev/frase/apis/frase-api.md) · [All operations](https://skmtc.dev/frase/apis/frase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/frase/apis/frase-api/revisions/7fb75d89f7a4?raw)
