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

# Get keyword cannibalization conflicts

`GET /site-health/cannibalization`

## Request

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

Keyword-cannibalization conflict groups recorded by the audit pipeline: one entry per keyword that more than one page competes for. Requires the `read:site-health` scope. Pass either `audit_id` (a specific completed audit) or `site_id` (resolves that site's most recent completed audit, ordered by created_at, and reports on it). If both are sent, `audit_id` wins and `site_id` is ignored; if neither is sent you get a 400 on `audit_id`. BOTH modes return the SAME payload: a plain ARRAY of conflict groups directly under `data` (not an object, and not the internal link graph an older build returned here). The array is empty when the audit ran but stored no conflicts. Read-only: no side effects beyond API-usage logging. OPTIONS is supported for CORS preflight (204, `Access-Control-Allow-Methods: GET, OPTIONS`). Note the two 404 bodies on this route are data-wrapped (`{"data":{"error":"..."}}`), not the Stripe-style error envelope used by 400/401/403/429.

## Query parameters

- `audit_id` string, uuid
- `site_id` string, uuid

## Response `200`

Keyword-cannibalization conflict groups for the resolved audit. Empty array when the audit stored none.

- object
  - `data` object[]
    - `keyword` string: The keyword the pages are competing for.
    - `severity` 'low' | 'medium' | 'critical' | 'unknown': The group's stored severity level. "unknown" when the persisted group carried no severity string (older audit rows), never a fabricated level.
    - `pages` object[]: The competing pages. Entries without a URL are dropped.
      - `url` string
      - `title` string: Empty string when the crawl recorded no title — always a string, never null.
      - `similarity` number, nullable: Content similarity to the other pages in the group. Null when the stored group carried no numeric similarity.

## Other responses

- `400`: Validation failed
- `401`: Authentication failed
- `403`: Forbidden - action not allowed on this resource
- `404`: Data-wrapped, NOT the standard error envelope. Two cases: `site_id` mode where the site has no completed audit ("No completed audit found for this site. Run an audit first."), and an `audit_id` (or resolved audit) your organization does not own / that does not exist ("Audit not found").
- `429`: Rate limit exceeded
- `500`: Unexpected failure while reading the audit. Data-wrapped, with a fixed message (`Failed to get cannibalization data`) — 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)
