---
title: "Keyword groups"
description: "Organise keywords by customer, campaign or product, track the same word for several of them, and see each group's cost."
canonical: https://docs.rumoro.dev/guides/groups
markdown: https://docs.rumoro.dev/guides/groups.mdx
---

# Keyword groups

Organise keywords by customer, campaign or product, track the same word for several of them, and see each group's cost.

Groups let one workspace work for several customers, campaigns or products. If two of them need the same word, each gets its own keyword with its own rules, cap and line on the bill. If you only track your own brand, you don't need groups.

A keyword is always in one group. Each workspace starts with a **Default** group, which gets keywords created without a `groupId`. You can rename it but not delete it. A term can appear once per group (case doesn't matter), so two groups can each have their own `atlas` keyword.

## Create a group and add keywords

`name` is required, 1 to 80 characters and unique. `externalId` (up to 128 characters) is optional and also unique. Use it for your own id, such as a customer id, and find the group again with `GET /v1/groups?externalId=`.

```ts tab="TypeScript"
const { data: group } = await rumoro.createGroup({ body: { name: 'Northwind Bikes', externalId: 'acct_4471' } });
```

```python tab="Python"
group = rumoro.groups.create(name="Northwind Bikes", externalId="acct_4471")
```

```bash tab="curl"
curl -X POST https://api.rumoro.dev/v1/groups \
  -H "Authorization: Bearer $RUMORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Northwind Bikes", "externalId": "acct_4471" }'
```

```json
{
  "id": "grp_...",
  "name": "Northwind Bikes",
  "externalId": "acct_4471",
  "isDefault": false,
  "context": null,
  "stats": { "keywords": 0, "active": 0 },
  "createdAt": "2026-10-04T10:41:09.317Z",
  "updatedAt": "2026-10-04T10:41:09.317Z"
}
```

```ts tab="TypeScript"
await rumoro.createKeyword({ body: { term: 'atlas', kind: 'brand', groupId: 'grp_...', cap: { mentions: 400 } } });
```

```python tab="Python"
rumoro.keywords.create(term="atlas", kind="brand", groupId="grp_...", cap={"mentions": 400})
```

```bash tab="curl"
curl -X POST https://api.rumoro.dev/v1/keywords \
  -H "Authorization: Bearer $RUMORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "term": "atlas", "kind": "brand", "groupId": "grp_...", "cap": { "mentions": 400 } }'
```

A second `atlas` in the same group returns `409 duplicate_keyword`. To move a keyword, send a new `groupId` with `PATCH /v1/keywords/{id}`. You get the same 409 if the target group already has that term. A group's `stats` count its `keywords` and how many of them are `active`.

## A company description per group

The classifier scores mentions against your workspace's company profile. When the workspace serves several businesses, give each group its own `context` of up to 4,000 characters. Say who the business is, what it sells, to whom, and what it is not.

For that group's keywords, this text replaces the whole workspace profile. So rules that should apply to the whole group, such as "ignore job ads", belong here too. Keyword `context` stays a short note for one term. Set the group's `context` to `null` to go back to the workspace profile. The default group has no `context` of its own (`400 default_group_context`).

```ts tab="TypeScript"
await rumoro.updateGroup({
  path: { id: 'grp_...' },
  body: {
    context: 'Northwind Bikes builds cargo e-bikes in Utrecht and sells them online and through dealers. Atlas is their family cargo bike. Not the moving company, not the database.',
  },
});
```

```python tab="Python"
rumoro.groups.update(
    "grp_...",
    context="Northwind Bikes builds cargo e-bikes in Utrecht and sells them online and through dealers. Atlas is their family cargo bike. Not the moving company, not the database.",
)
```

```bash tab="curl"
curl -X PATCH https://api.rumoro.dev/v1/groups/grp_... \
  -H "Authorization: Bearer $RUMORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "context": "Northwind Bikes builds cargo e-bikes in Utrecht and sells them online and through dealers. Atlas is their family cargo bike. Not the moving company, not the database." }'
```

Matches stored after the change use the new text. Earlier ones keep the old text, even if they aren't scored yet. Nothing is scored again.

## Where the group shows up

- Keywords have a `group` object (`id`, `name`, `externalId`, `isDefault`). `GET /v1/keywords?groupId=` takes one id or a comma-separated list.
- Mentions have `keyword.group`, in the API and in webhooks. `groupIds` and `notGroupIds` filter `GET /v1/mentions` and the exports, and the CSV has `group` and `group_external_id` columns.
- Alert rules take `groupIds`, so you can have one rule per customer that sends to that customer's channel.
- Each keyword row in the analytics reports carries its group.
- In emails and digests, a keyword outside the default group appears as `atlas (Northwind Bikes)`.

## What a group cost

`GET /v1/usage/breakdown?by=group` shows each group's keyword-days, matched and billed mentions, and total at list price for a month.

```ts tab="TypeScript"
const { data: breakdown } = await rumoro.getUsageBreakdown({ query: { by: 'group', month: '2026-10' } });
```

```python tab="Python"
breakdown = rumoro.usage.breakdown(by="group", month="2026-10")
```

```bash tab="curl"
curl "https://api.rumoro.dev/v1/usage/breakdown?by=group&month=2026-10" \
  -H "Authorization: Bearer $RUMORO_API_KEY"
```

Keyword-days count for the group the keyword was in on that day. Mention charges count for its current group, and those of a deleted keyword show under `totals.unattributedBillable`. A group deleted during the month keeps its row, named "Deleted group" with `group.removed: true`.

## Deleting a group

`DELETE /v1/groups/{id}` returns 204 and deletes the group with its keywords and their mentions. Past charges stay on record. Check `stats.keywords` first if you want to know how many keywords will go. A rule that only named this group is turned off rather than widened, and a view that names it no longer matches anything.

## In the dashboard

The **Keywords** page has a **Groups** button and a group picker. As soon as a second group exists, each keyword shows its group, and the keyword editor gets a group field.
