---
title: "Keyword health"
description: "Find the keywords that cost more than they bring, see what their noise is made of, and apply the fix with one PATCH."
canonical: https://docs.mentio.dev/guides/keyword-health
markdown: https://docs.mentio.dev/guides/keyword-health.mdx
---

# Keyword health

Find the keywords that cost more than they bring, see what their noise is made of, and apply the fix with one PATCH.

Every match bills, relevant or not. A keyword that matches a common word ("baker", "profound", "arc") can spend most of its budget on posts about something else. `GET /v1/keywords/{id}/health` says whether a keyword earns what it costs, what its noise is made of, and which change would cut it, measured on its own posts.

```bash
curl https://api.mentio.dev/v1/keywords/kw_60d9.../health?range=30d \
  -H "Authorization: Bearer $MENTIO_API_KEY"
```

`range` is `7d`, `30d` (the default) or `90d`, by match time, ending today. The report is read only, never billed, and cached for 5 minutes; a change to the keyword starts a fresh one.

## Status

| Status    | When                                                                                                                                 |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `paused`  | The keyword is muted: by you, by the wallet (the balance ran out) or by the noise brake.                                             |
| `capped`  | At its monthly mention cap; it matches again on the 1st (UTC) or once the cap is raised.                                             |
| `noisy`   | 20 or more scored matches in the window and under 30% of them relevant (scored 40 or more).                                          |
| `new`     | Under 7 days old and not noisy yet, or changed in the last 7 days with under 20 scored matches since the change: too early to judge. |
| `quiet`   | 7 days or older with no relevant match in the window.                                                                                |
| `healthy` | Anything else.                                                                                                                       |

The order is the precedence: a muted keyword reads `paused` whatever its noise.

## Judged since your last change

Once you change a keyword's matching rules (excluded or required terms, excluded authors, case), its platforms or its context, or unmute it, the noise from before the change says nothing about the keyword you have now. So the status, the `reasons`, `noiseTerms`, `noiseAuthors` and the suggestions read only the matches since that change, when it falls inside the window, and `stats.judgedSince` says when that was (null when they read the whole window). This is the same starting point the noise brake reads from. With under 20 scored matches since a change made this week, the keyword reads `new` ("judging again") and gets no suggestions, so a report never proposes again what you just applied. The numbers in `stats` (matches, cost, platforms, weeks) always cover the whole window. `reasons` says why in plain words, and adds a line for a platform that is almost all noise and for a keyword that takes half the workspace's matches or more.

The `noisy` line is the one the noise brake and `stats.noise.noisy` already use. On Mentio's own production data, the median keyword with enough volume is about 35% noise; the 70% line catches the keywords whose term mostly means something else, not competitors whose noise is the price of tracking a common name.

Every keyword in `GET /v1/keywords` also carries `stats.health`, the same rule over the 14 days `stats.noise` reads (since the last change when that is inside the 14 days, while `stats.noise` keeps all 14), so a list can show a badge without one call per keyword. The endpoint reads 30 days by default, so a keyword can be noisy on the list and healthy in its report (or the other way round) until the two windows agree.

## Numbers

`stats` holds the window's `matches`, `relevant`, `filtered` (the noise), `unscored`, `noiseShare`, the keyword's share of the workspace's matches (`workspaceShare`), one row per platform (`byPlatform`), one row per 7 days (`weekly`) and what it cost (`cost`, the same numbers as its row in [`GET /v1/usage/breakdown`](/billing#cost-per-keyword) for the same range).

## What the noise is made of

Mentio reads the newest posts of the window (or since your last change), up to 200 scored as noise and 200 scored relevant, after the keyword's current rules and platforms (a post an older rule let in, or one from a platform it no longer tracks, is not counted). App store and review platforms are matched by app, not by text, and stay out.

* `noiseTerms`: up to 10 words or two-word phrases far more common in the noise than in the relevant posts (`noisePosts`, `relevantPosts`, and `lift`, how many times more common). The keyword's own words are left out.
* `noiseAuthors`: people with 3 or more noise posts and no relevant one, with the `entry` that `matching.excludedAuthors` would store: their profile link, or their name when the link does not name one person (a GitHub App's).

## Suggestions

Each suggestion is a change in the keyword's own vocabulary, and its `patch` is the body for `PATCH /v1/keywords/{id}` as is: a list carries the whole new list, the current entries kept.

| `type`             | What it does                                                                                                                                                                                                                |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platforms`        | Drops the platforms where 90% or more of 10 or more scored matches were noise. A keyword on every platform gets an explicit list, so a platform Mentio adds later is not added.                                             |
| `excluded_terms`   | Adds the noise terms that never (or almost never) appear in relevant posts (2% at most, for the whole list together), as few as cover the noise. Only with 20 or more relevant posts to check them against, or none at all. |
| `excluded_authors` | Adds the authors who only brought noise.                                                                                                                                                                                    |
| `required_terms`   | When the keyword has none: words that appear in 90% or more of the relevant posts and leave out at least half of the noise.                                                                                                 |
| `context`          | A rewritten classifier context. It changes how new matches are scored, not what is matched or billed.                                                                                                                       |

Every suggestion but `context` carries an `effect`: the matcher's own rules run over the sampled posts, scaled to the window when the sample is smaller than it (`exact` says it was not). It reads as "would have removed `noiseRemoved` noise matches and `relevantRemoved` relevant ones", with `centsSaved` at the mention rate.

```json
{
  "type": "excluded_terms",
  "values": ["sourdough", "detective"],
  "why": "Exclude \"sourdough\", \"detective\": common in its noise, absent from its relevant matches. In the last 30 days it would have removed 45 noise matches and 0 relevant ones.",
  "patch": { "matching": { "excludedTerms": ["sourdough", "detective"] } },
  "effect": { "noiseRemoved": 45, "relevantRemoved": 0, "centsSaved": 36, "sample": { "noiseRemoved": 45, "noise": 45, "relevantRemoved": 0, "relevant": 20 }, "exact": true },
  "source": "rules"
}
```

Apply it:

```bash
curl -X PATCH https://api.mentio.dev/v1/keywords/kw_60d9... \
  -H "Authorization: Bearer $MENTIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "matching": { "excludedTerms": ["sourdough", "detective"] } }'
```

Rules apply to new mentions from the next poll; stored mentions stay.

## A context written by a model

Without anything else, the `context` suggestion appends "Ignore posts about ..." with the top noise terms. `ai=true` asks a language model instead, shown the term, the current context and excerpts of both kinds of posts. `ai.status` says what happened: `generated`, `cached` (the same keyword, window and settings within a day), `unavailable` (the model failed or answered nothing usable; the rules' context stands in) or `rate_limited` (20 model calls an hour per workspace, counted for every caller).

## Limits

30 reads a minute per workspace for API keys, OAuth tokens and MCP. In the CLI it is `mentio keywords:health <id> --range 30d`, and in MCP the read-only tool `get_keyword_health`, whose suggestions go to `update_keyword` as is.
