---
title: "Check a keyword's health"
method: GET
path: "/v1/keywords/{id}/health"
tags: ["Keywords"]
---

# Check a keyword's health

`GET /v1/keywords/{id}/health`

## Request

- Base URL: `https://api.rumoro.dev`
- URL: `https://api.rumoro.dev/v1/keywords/{id}/health`
- Auth: HTTP bearer

Checks whether the keyword earns its cost over `range` (default 30d). Returns a status (healthy, noisy, quiet, capped, paused or new) with plain-word reasons, numbers by platform and week, cost, the words and authors behind the noise, and suggestions. Send a suggestion's `patch` unchanged to PATCH /v1/keywords/{id}. Its effect comes from replaying the matcher's rules on the window's posts. `ai=true` adds a model-written context (cached a day, up to 20 model calls an hour per workspace). Read only and never billed. Reports are cached 5 minutes and rebuilt after a keyword change. Up to 30 reads a minute per workspace.

## Path parameters

- `id` string, required: The keyword's id (kw_...).

## Query parameters

- `range` '7d' | '30d' | '90d': How many UTC days back from today to read, by match time. One of 7d, 30d or 90d, 30d by default.
- `ai` boolean: true also asks a language model to rewrite the context. The answer is cached for a day per keyword and window, with at most 20 model calls an hour per workspace. With the default false, all suggestions come from the rules only.

## Response `200`

The health report for the keyword

- KeywordHealth
  - `keyword` object, required: The keyword this report covers.
    - `id` string, required: The keyword's id (kw_...).
    - `term` string, required: The term exactly as entered.
    - `group` GroupRef, required: The keyword's group.
      - `id` string, required: The group's id (grp_...).
      - `name` string, required: Name of the group.
      - `externalId` string, nullable, required: An id from your own system, or null.
      - `isDefault` boolean, required: True for the workspace's default group. New keywords go there when you don't name a group.
  - `window` object, required: The days the report covers, in UTC and by match time.
    - `from` string, required: Start date (UTC, YYYY-MM-DD), included.
    - `to` string, required: The last day included, which is today.
    - `days` integer, required: How many days the window spans.
  - `status` 'healthy' | 'noisy' | 'quiet' | 'capped' | 'paused' | 'new', required: The first that applies wins. paused when you, the wallet or the noise brake muted it. capped at the monthly mention cap. noisy with 20+ scored matches in the window and under 30% relevant. new when under 7 days old, or changed in the last 7 days with under 20 scored matches since. quiet when 7+ days old with nothing relevant in the window. Otherwise healthy. After a rules, platforms or context change, or an unmute, inside the window, only later matches count (stats.judgedSince).
  - `reasons` string[], required: Plain-word explanation, status first.
  - `stats` object, required: Based on this keyword's matches in the window.
    - `matches` integer, required: Match count for the window, relevant or not.
    - `relevant` integer, required: Matches with a relevance score of 40 or more.
    - `filtered` integer, required: Matches scored below 40, the noise. They are billed like any other match.
    - `unscored` integer, required: Matches not scored yet, or whose scoring failed. A failed one is never billed.
    - `noiseShare` number, nullable, required: Share of scored matches that were filtered. Null if none were scored.
    - `workspaceShare` number, nullable, required: The part of the workspace's matches in the window that came from this keyword. Null when the workspace had no matches.
    - `judgedSince` string, nullable, required: The last change to the keyword's matching rules, platforms or context, or its last unmute, if that falls inside the window. The status, reasons, noiseTerms, noiseAuthors and suggestions then only look at matches since this time. These numbers still cover the whole window. Null means everything covers the whole window.
    - `byPlatform` object[], required: A row for each platform with matches, the busiest first.
      - `platform` 'bluesky' | 'hackernews' | 'github' | 'stackoverflow' | 'devto' | 'reddit' | 'x' | 'youtube' | 'news' | 'linkedin' | 'tiktok' | 'instagram' | 'appstore' | 'googleplay' | 'trustpilot' | 'googlemaps' | 'rss', required: The platform. Review platforms are appstore, googleplay, trustpilot and googlemaps (a place's Google reviews).
      - `matches` integer, required: This platform's match count for the window.
      - `relevant` integer, required: How many of them scored 40 or more.
      - `filtered` integer, required: How many of them scored below 40, the noise.
      - `noiseShare` number, nullable, required: Share of scored matches that were filtered. Null if none were scored.
    - `weekly` object[], required: The trend in rows of 7 days, oldest first. The last row can be shorter.
      - `from` string, required: Week start as YYYY-MM-DD. Weeks run in 7-day steps from the start of the window.
      - `matches` integer, required: Matches during these 7 days.
      - `relevant` integer, required: How many of them were relevant.
    - `cost` object, required: List-price cost of this keyword in the window.
      - `keywordDays` integer, required: Charged days in the window.
      - `keywordCents` integer, required: What keywordDays cost at the keyword rate.
      - `billableMentions` integer, required: Billed matches in the window, dated by scoring time.
      - `mentionCents` integer, required: What billableMentions cost at the mention rate.
      - `billableComments` integer, required: Comments billed on the comments line: comments delivered under mentions and matched replies, each once per workspace.
      - `commentCents` integer, required: Those comments at $0.008 each, rounded once on the total.
      - `totalCents` integer, required: keywordCents plus mentionCents plus commentCents.
  - `sample` object, required: The posts used for noiseTerms, noiseAuthors and the effects. Only posts the keyword's current rules accept count, not ones an older rule let in. Review platforms match by app rather than text and are left out.
    - `noise` integer, required: Noise posts that were read, up to 200 of the newest in the window or since stats.judgedSince, from platforms matched by text. Zero when fewer than 20 matches were scored since a change.
    - `relevant` integer, required: Relevant posts that were read, chosen the same way.
  - `noiseTerms` object[], required: Up to 10 words or phrases that show up far more in noise than in relevant posts, strongest first. Words of the keyword itself are skipped.
    - `term` string, required: One word or a phrase of two words.
    - `noisePosts` integer, required: Noise posts in the sample that contain it.
    - `relevantPosts` integer, required: Relevant posts in the sample that contain it.
    - `lift` number, nullable, required: Ratio of the term's rate in noise to its rate in relevant posts, smoothed. Null without a relevant post.
  - `noiseAuthors` object[], required: Authors in the sample with no relevant post and 3 or more noise posts.
    - `name` string, required: The author's name as shown on the platform.
    - `entry` string, required: The entry matching.excludedAuthors would store for this author.
    - `noisePosts` integer, required: Noise posts in the sample written by this author.
  - `suggestions` KeywordSuggestion[], required: Suggested noise fixes, each a body for PATCH /v1/keywords/{id}. The list is empty if no fix helps, or if fewer than 20 matches were scored after the last change. A reason explains which.
    - `type` 'excluded_terms' | 'excluded_authors' | 'required_terms' | 'platforms' | 'context', required: What the suggestion changes. The excluded_* types add to the matching rules, required_terms swaps the required list, platforms drops the platforms that bring nearly only noise, and context rewrites the classifier's sentence.
    - `values` string[], required: The terms or authors to add, the platforms to remove, or the new context.
    - `why` string, required: Why, and what the change would have done, in plain words.
    - `patch` object, required: Send this body unchanged to PATCH /v1/keywords/{id} to apply the suggestion. Lists hold the full new list, with the current entries included.
      - `term` string: A new term for the keyword. It keeps its id, settings and the mentions the old term found, and the new term is tracked from now on, with no look-back. A 409 when its group already tracks the new term.
      - `kind` 'brand' | 'competitor' | 'topic': New kind (brand, competitor or topic).
      - `muted` boolean: Muting stops polling and matching. Existing mentions are kept.
      - `platforms` string[], nullable: Sets a new platform list. Null means all platforms. An empty list means none, which works when the keyword has reviewSources and only collects reviews.
      - `feeds` object[]: RSS or Atom feeds this keyword reads, at most 20, each { url }: a feed's address, or a page's (a forum, a community, a blog), whose advertised feed is used, else a usual address such as /feed or /rss. Every feed is checked when saved, and a 400 names each one that cannot be read and why. Feeds are read every hour; an item is a mention of this keyword when it holds the term (with its matching rules), and only of keywords that named the feed. A newly connected feed brings its newest 10 matching items of the last 30 days, billed like any mention. On PATCH the list replaces the old one, and [] disconnects them all (their mentions stay).
        - `url` string, required: A feed's address (https://forum.example.com/posts.rss), or a page's (https://forum.example.com), whose advertised feed is used.
      - `context` string, nullable: Up to 300 characters the classifier reads for this keyword only, in addition to the company profile or the group's description. Say what the term means for you and what to ignore, for example "Basil is our deploy tool, not the herb." Null clears it.
      - `matching` object: Fields you leave out stay as they are. An empty list clears a field.
        - `requiredTerms` string[]: Terms the post must contain as well. requiredMode says whether one or all of them must appear. An empty list sets no requirement.
        - `requiredMode` 'any' | 'all': any needs one of the required terms in the post. all needs every one.
        - `excludedTerms` string[]: Posts that contain one of these terms are dropped. Put `*` at the start or end of a term as a wildcard, so deploy* matches deployment and *bot matches nightlybot.
        - `excludedAuthors` string[]: Authors whose posts this keyword ignores. Each entry is a profile or post link, @handle, u/name, Bluesky DID or display name. Entries are normalized the same way as an alert's muted authors.
        - `caseSensitive` boolean: When true, the term only matches in the exact case you typed, so RAG never matches rag. Defaults to false.
      - `cap` object, nullable: Sets a new monthly mention cap, or null for no cap. A cap above this month's count resumes a capped keyword right away. A cap at or below the count pauses it.
        - `mentions` integer, required: How many matched mentions the keyword may collect per calendar month (UTC). Every match counts, relevant or not, including a new keyword's look-back, since every match is billed.
      - `comments` object: Comments under this keyword's mentions; omitted fields stay as they are (on create: off, 20 per mention).
        - `enabled` boolean: Read the comments under this keyword's relevant mentions, from 30 minutes after each post, while its platform's conversations last. Each comment delivered costs $0.008 on the comments line of the bill (comments of fewer than three words are dropped, never billed). Off by default.
        - `maxPerPost` integer: How many of a thread's newest comments you receive, 20 by default and 100 at most: the ceiling on what one mention's comments can cost. A comment that names the keyword is a mention too, but only among these.
      - `groupId` string: Target group (grp_...). 409 if it already has this term.
      - `reviewSources` object[]: Sets a new list of review pages for this keyword. An empty list disconnects them all, and their reviews are kept. A page or country you add gets the free 30-day look-back. Pages already listed are unchanged.
        - `url` string: Review page link, or send platform and id instead. Accepts App Store and Google Play app links (https://apps.apple.com/us/app/slack/id618783545, https://play.google.com/store/apps/details?id=com.Slack), Trustpilot pages (https://www.trustpilot.com/review/slack.com) and Google Maps places (full or maps.app.goo.gl link).
        - `platform` 'appstore' | 'googleplay' | 'trustpilot' | 'googlemaps': Where the reviews come from. appstore is the Apple App Store, googleplay is Google Play, trustpilot is a company's Trustpilot page and googlemaps is the Google reviews of a place.
        - `id` string: The platform's own id. On the App Store it is the number after "id" in the link, on Google Play the package name, on Trustpilot the company's domain (slack.com) and on Google Maps a Place ID (ChIJ...).
        - `countries` string[]: For App Store and Google Play. Up to 20 storefronts to read, as two-letter codes. Without it, the storefront in the link is used, or us. Each storefront adds one poll a day, and a review found in two storefronts is still one mention. Trustpilot and Google Maps have a single page and take no countries.
        - `language` string: For Google Play. The review language to read, such as en, es, de or pt-BR, since Google Play returns one language at a time. Without it, the link's hl is used, or en.
    - `effect` object, nullable, required: The result of replaying the change on the window's matches with the matcher's rules. Null for context suggestions, which alter scores rather than matches.
      - `noiseRemoved` integer, required: How many noise matches the change would have removed in the window (since stats.judgedSince if set). Sampled unless exact is true.
      - `relevantRemoved` integer, required: How many relevant matches the change would have cut from the window.
      - `centsSaved` integer, required: Money the change would have saved in the window, at the mention rate for each removed match.
      - `sample` object, required: Counts behind the estimate. The matcher's rules were run on each sampled post.
        - `noiseRemoved` integer, required: Noise posts in the sample that the change would reject.
        - `noise` integer, required: Noise posts in the sample.
        - `relevantRemoved` integer, required: Relevant posts in the sample that the change would reject.
        - `relevant` integer, required: Relevant posts in the sample.
      - `exact` boolean, required: True when the sample covered the whole window, so the counts are exact.
    - `source` 'rules' | 'ai', required: rules means it was computed from the posts in the window. ai means a language model wrote it (with ai=true).
  - `ai` object, required: The optional part of the report written by a language model.
    - `requested` boolean, required: True when the request had ai=true.
    - `status` 'off' | 'generated' | 'cached' | 'unavailable' | 'rate_limited', required: off means it was not requested. generated means it was written for this request. cached means it was written earlier today for the same keyword, window and settings. unavailable means the model failed, gave nothing usable or is not set up. rate_limited means the workspace used its 20 model calls for this hour.
  - `generatedAt` string, required: When the report was built. Reports are cached for 5 minutes, and changing the keyword builds a new one.

## Other responses

- `400`: A query parameter is not valid
- `401`: The API key is missing or not valid
- `404`: No keyword with this id
- `429`: Health was read over 30 times this minute (rate_limited)

---

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