---
title: "List aggregated site health issues"
method: GET
path: "/site-health/issues"
tags: ["Site Health"]
---

# List aggregated site health issues

`GET /site-health/issues`

## Request

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

Every issue found on a site's most recent audit, aggregated across pages: one entry per issue title with the pages it affects, plus keyword-cannibalization groups folded in as issues of their own. Requires the `read:site-health` scope. Issues whose title was marked fixed in the audit's metadata are excluded, and the list is sorted by severity (critical, high, medium, low). `summary` is recounted over the returned (deduplicated, filtered) issues, so it will not always match `audit_stats`, which reports the raw audit run. `total_occurrences` counts per-page occurrences rather than deduplicated issues, so it is normally larger than `summary.total`. Read-only: no side effects beyond API-usage logging. OPTIONS is supported for CORS preflight (204, `Access-Control-Allow-Methods: GET, OPTIONS`). Note the 404 and 500 bodies are data-wrapped (`{"data":{"error":"..."}}`), not the Stripe-style error envelope used by 400/401/403/429.

## Query parameters

- `site_id` string, uuid, required

## Response `200`

Aggregated issues for the site

- object
  - `data` object
    - `issues` object[]: Sorted critical first. Empty when the site has no unfixed issues (including a site that has never been audited).
      - `id` string: The underlying audit issue's id for standard issues; `cannibalization-<groupId>` for cannibalization issues. Stable only within a single audit run.
      - `title` string: The aggregation key — issues sharing a title across pages are merged into one entry.
      - `description` string: Page-specific counts are stripped once a group spans more than one page, so the text never claims one page's figures for the whole group.
      - `severity` 'critical' | 'high' | 'medium' | 'low'
      - `category` 'technical' | 'content' | 'aeo' | 'cannibalization' | 'agent_readiness'
      - `issue_type` 'standard' | 'cannibalization': Defaults to "standard" when the underlying issue carried no type.
      - `pages_affected` integer: Number of distinct pages in `page_refs`.
      - `page_refs` object[]
        - `page_id` string
        - `page_title` string: Falls back to the page URL for cannibalization entries that have no title.
        - `page_url` string
        - `target_keyword` string, nullable: Cannibalization issues only; always null on standard issues.
        - `similarity` number, nullable: Cannibalization issues only; always null on standard issues.
        - `word_count` integer, nullable: Cannibalization issues only; always null on standard issues.
        - `internal_links` integer, nullable: Cannibalization issues only; always null on standard issues.
      - `fix_suggestion` string, nullable: For cannibalization issues this is the recommendation's message. Null when the issue carries no suggestion.
      - `keyword` string, nullable: The cannibalized keyword. Null on standard issues.
      - `recommendation` object, nullable: Present on cannibalization issues; null on standard issues.
        - `type` 'merge' | 'differentiate' | 'consolidate' | 're-optimize'
        - `message` string
        - `primary_page_url` string, nullable: The page the recommendation would keep as primary, when it names one.
        - `actions` string[]: Ordered, human-readable steps.
    - `summary` object: Counts recomputed over the returned issues (deduplicated, fixed-issues removed). Keys are single words and are NOT snake_cased.
      - `critical` integer
      - `high` integer
      - `medium` integer
      - `low` integer
      - `total` integer
    - `total_occurrences` integer: Raw occurrence count: the audit's own issue total plus every page in every cannibalization group. Counts occurrences, not deduplicated issues, and is not filtered by the fixed-issues list — so it is normally larger than `summary.total`.
    - `audit_summary` string, nullable: The latest completed audit's stored summary text. Null when no crawl has completed or the run produced none.
    - `audit_stats` object, nullable: Figures from the latest completed audit run. Null when the site has no completed audit. Only these four fields are exposed here (the site-health overview publishes more).
      - `overall_score` integer, nullable
      - `pages_audited` integer, nullable
      - `critical_issues` integer, nullable
      - `total_issues` integer, nullable
    - `has_gsc` boolean: Whether Google Search Console data backed this site's analysis. False when unknown.
    - `last_audit_id` string, uuid, nullable: The audit these issues were drawn from. Null when the site has no completed audit.
    - `site_id` string, uuid, nullable: Echoes the requested site.

## Other responses

- `400`: Validation failed
- `401`: Authentication failed
- `403`: Forbidden - action not allowed on this resource
- `404`: Site not found — unknown id, deleted, or owned by another organization. Data-wrapped, NOT the standard error envelope.
- `429`: Rate limit exceeded
- `500`: The issues could not be assembled. Data-wrapped, with a fixed message (`Failed to list site health issues`) — v1 routes never leak the underlying error.

---

[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)
