---
title: "Create a keyword"
method: POST
path: "/v1/keywords"
tags: ["Keywords"]
---

# Create a keyword

`POST /v1/keywords`

## Request

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

Starts monitoring a word or phrase. Matching, scoring and delivery start with the next poll. A workspace with balance can have up to 500 keywords, each costing $5 a month, taken from the balance day by day. `matching` narrows what counts as a match, with required and excluded terms, excluded authors and case, before anything is stored, so rejected posts are never billed. `context` is a sentence only this keyword's classifier reads. `cap` limits matched mentions per month. At the cap the keyword stops matching until the 1st of next month (UTC) or until you raise the cap, and its daily charge continues. `feeds` reads RSS or Atom feeds (a feed's address or a site's), checked when saved and read every hour; `platforms: []` with review pages or feeds tracks only those.

## Request body

- object
  - `term` string, required: The word or phrase to monitor. It matches as a whole phrase, ignoring case.
  - `kind` 'brand' | 'competitor' | 'topic': brand is for your own names, competitor for a rival's, and topic for your market. Share of voice and segments use it.
  - `platforms` string[], nullable: The platforms to search for the term. Leave it out or send null for all platforms. An empty list searches nowhere, for a keyword that only collects reviews and so needs reviewSources.
  - `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: Monthly limit on matched mentions. Omit or null means none.
    - `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: The group to put it in (grp_...). Without it, the workspace's default group is used. Each group can hold a term only once.
  - `reviewSources` object[]: Review pages for this keyword, at most 10 (App Store and Google Play apps, Trustpilot pages, Google Maps places). Every new review on them is a mention, whatever it says. Pages are read daily, per country on the app stores. A newly added page imports its last 30 days, up to 100 newest reviews per country, free and without instant alerts. Later reviews are billed like other mentions.
    - `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.

## Response `201`

The new keyword

- Keyword
  - `id` string, required: The keyword's id (kw_...).
  - `term` string, required
  - `kind` 'brand' | 'competitor' | 'topic', required
  - `muted` boolean, required: True when the keyword is neither polled nor matched. You, the wallet (pausedForBalance) or the noise brake (pausedForNoise) can mute it. Reaching the mention cap does not mute it (pausedForCap).
  - `pausedForBalance` boolean, required: Muted because the balance ran out. A top-up resumes it. Unmuting it yourself also needs balance.
  - `pausedForNoise` boolean, required: Set when the noise brake muted the keyword. That happens on welcome credit once 20 or more matches are scored and under 30% are relevant. Unmuting it, or changing its required or excluded terms, platforms or context, resumes it when the balance covers another day. A top-up alone does not.
  - `pausedForCap` boolean, required: Hit its monthly mention cap. Matching resumes on the 1st (UTC) or when you raise the cap. The keyword isn't muted and is still charged daily.
  - `cap` object, nullable, required: Monthly limit on matched mentions. Null means none.
    - `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.
    - `welcome` boolean, required: Rumoro sets this while the workspace uses its welcome credit. It limits each keyword to 200 mentions a month until the first top-up.
    - `own` integer, nullable, required: The cap you chose. While welcome is true it is the cap restored at the first top-up (null for none), and sending `mentions: 200` changes nothing. Otherwise it equals mentions.
  - `comments` object, required: Comments under this keyword's mentions. When enabled, the thread of every new mention scored relevant is read from 30 minutes after the post, several times while its platform's conversations last (a day on Reddit, Hacker News and Bluesky, a week on GitHub, Stack Overflow and DEV, a month on YouTube): new comments only, newest first, at most maxPerPost per mention, and comments of fewer than three words are dropped. List them with GET /v1/mentions/{id}/comments.
    - `enabled` boolean, required: 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, required: 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.
  - `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.
  - `platforms` string[], nullable, required: The platforms searched for the term. Null means all of them. An empty list means none, and the keyword only collects reviews.
  - `feeds` KeywordFeed[], required: The RSS or Atom feeds this keyword reads, in the order given.
    - `url` string, required: The feed itself, after redirects: the address that is read.
    - `title` string, nullable, required: The feed's own title, when it has one.
    - `connectedAt` string, required: When this keyword started reading the feed. Nothing published before it is a live mention.
  - `reviewSources` ReviewSource[], required: The review pages the keyword reads, such as App Store and Google Play apps, Trustpilot pages and Google Maps places. Empty when there are none.
    - `platform` 'appstore' | 'googleplay' | 'trustpilot' | 'googlemaps', required: 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, required: The platform's own id. An App Store app id, a Google Play package name, a Trustpilot domain, or a Google Place ID or cid.
    - `url` string, required: URL of the review page, such as an app's store listing, a company's Trustpilot page or a place on Google Maps.
    - `countries` string[], required: Two-letter storefront codes, lowercase. Empty for Trustpilot and Google Maps.
    - `language` string, nullable, required: The review language on Google Play. Null on other platforms.
    - `connectedAt` string, required: When the keyword began collecting reviews from this page.
  - `context` string, nullable, required: 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, required: Rules a post must pass before it is stored as a mention. A post they reject is never billed.
    - `requiredTerms` string[], required: 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', required: any needs one of the required terms in the post. all needs every one.
    - `excludedTerms` string[], required: 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[], required: 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, required: When true, the term only matches in the exact case you typed, so RAG never matches rag. Defaults to false.
  - `stats` object, required: Counted from this workspace's matches only.
    - `mentions` integer, required: All matches so far, relevant or not. Billing counts this number.
    - `relevant` integer, required: Matches whose relevance score reached the threshold.
    - `last7d` integer, required: Matches with a post date in the past 7 days.
    - `thisMonth` integer, required: Matches recorded in the current calendar month (UTC). The cap is checked against this count.
    - `lastMentionAt` string, nullable, required: Time of the latest matched post. Null before the first match.
    - `feedback` object, required: Your team's relevance marks, set with PATCH /v1/mentions/{id}.
      - `relevant` integer, required: Marked relevant by your team.
      - `notRelevant` integer, required: Mentions someone on your team marked not relevant. This is noise the classifier missed.
    - `noise` object, required: Relevance of the scored matches from the past 14 days. A keyword you narrow today drops the flag within two weeks.
      - `scored` integer, required: How many of the last 14 days' matches are scored.
      - `relevant` integer, required: How many of them were scored relevant.
      - `noisy` boolean, required: True when the past 14 days have 20 or more scored matches and fewer than 30% are relevant. Narrow the keyword with required terms, excluded terms or context, because every match is billed, relevant or not.
    - `health` 'healthy' | 'noisy' | 'quiet' | 'capped' | 'paused' | 'new', required: Health over the 14 days `noise` covers, by the same rule as GET /v1/keywords/{id}/health. The first that applies wins. paused (muted), capped (at its cap), noisy (20+ scored matches, under 30% relevant), new (under 7 days old, or changed in the last 7 days with under 20 scored since), quiet (7+ days, nothing relevant), otherwise healthy. After a change to matching rules, platforms or context, or an unmute, within the 14 days, only later matches count, while `noise` keeps all 14 days. The endpoint defaults to 30 days, so the two can differ, and it explains why and what to change.
    - `cost` object, required: List-price cost this calendar month (UTC), equal to this keyword's row in GET /v1/usage/breakdown?month=<this month>. The ledger settles daily and can differ only by cumulative rounding.
      - `keywordDays` integer, required: Charged days this month. A day is charged when the keyword is unmuted at the daily run, so a new keyword shows 0 until tomorrow.
      - `keywordCents` integer, required: keywordDays at 500/30 cents each, which is $5 a month. Only the total is rounded.
      - `billableMentions` integer, required: Matches billed this month. A match counts when it is scored, which is the time the ledger settles by. So this can lag behind thisMonth while matches are still being scored, and a match that failed to score never counts.
      - `mentionCents` integer, required: Those matches priced at $0.008 each, with the total rounded once.
      - `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.
  - `polling` object[], required: Polling status for each platform that is polled on a schedule. Live feeds such as Bluesky are not listed.
    - `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).
    - `lastPolledAt` string, nullable, required: When the term was last polled on this platform. Null before the first poll.
    - `emptyPolls` integer, required: How many polls in a row found nothing new. The higher it gets, the less often the term is polled.
  - `createdAt` string, required: UTC time in ISO 8601.

## Other responses

- `401`: The API key is missing or not valid
- `402`: The balance cannot cover one more keyword-day (insufficient_balance), or the workspace already has 500 keywords (keyword_limit_reached)
- `409`: The group already has a keyword with this term after normalizing

---

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