---
title: "Conventions"
description: "Paths, ids, methods, paging, filters, errors and versions. The patterns that hold across the whole Rumoro API."
canonical: https://docs.rumoro.dev/conventions
markdown: https://docs.rumoro.dev/conventions.mdx
---

# Conventions

Paths, ids, methods, paging, filters, errors and versions. The patterns that hold across the whole Rumoro API.

The API is at `https://api.rumoro.dev/v1`. Every resource in the [reference](/api) follows the patterns on this page.

## Resources and paths

Paths use plural nouns and one id, such as `/v1/mentions/{id}`, `/v1/keywords/{id}` or `/v1/alerts/{id}`. An id is a prefix followed by 32 hex characters.

| Prefix | Resource |
| --- | --- |
| `mm_` | mention |
| `aut_` | person |
| `kw_` | keyword |
| `grp_` | group |
| `vw_` | view |
| `seg_` | segment |
| `feed_` | alert rule |
| `dest_` | channel |
| `dlv_` | delivery |
| `att_` | attention item |
| `key_` | API key |
| `org_` | workspace |

An id from another workspace, or one that isn't valid, returns 404 and never 403.

Actions that aren't simple updates are `POST` requests on the resource, such as `/v1/people/{id}/merge`, `/v1/people/{id}/split`, `/v1/alerts/{id}/test`, `/v1/alerts/{id}/run`, `/v1/channels/{id}/test`, `/v1/channels/{id}/rotate-secret` and `/v1/attention/{id}/dismiss`.

Analytics come as fixed reports, `/v1/analytics/summary`, `series`, `breakdown`, `share-of-voice` and `reviews`. Each takes `range` (or `from` and `to`), `keywordIds`, `platforms`, `timezone` and `compare`, and returns its `window`. `series` and `breakdown` also take a `by` dimension.

Exports accept the same filters as their list. `/v1/mentions/export.csv` and `export.json` return up to 10,000 rows, `/v1/people/export.csv` up to 5,000.

## Methods and status codes

| Method | Use | Success |
| --- | --- | --- |
| `GET` | Read one item or a list | 200 |
| `POST` on a collection | Create | 201 and the new resource |
| `PATCH` | Change some fields. Fields you leave out stay as they are, `null` clears one. | 200 and the resource |
| `DELETE` | Delete | 204, no body |
| `POST` action | Run an action | 200 and the result |

There are two exceptions. `POST /v1/members/invitations` returns 200 with the existing invitation if the address already has one, and `POST /v1/billing/top-ups` returns 200.

To change anything, use `PATCH`. For a mention, that includes ignoring, closing, assigning, snoozing and adding notes, all through `PATCH /v1/mentions/{id}`.

## Envelopes

A single resource is returned as is. A list is wrapped in `data` with paging information.

```json
{ "data": [ ... ], "nextCursor": "eyJ..." }
```

On the last page `nextCursor` is `null`. For the next page, send it back as `cursor` with the same filters and sort. The cursor remembers the position in the sort order, not your filters.

People and keywords use `limit` and `offset` instead, and return a `total`.

```json
{ "data": [ ... ], "total": 418 }
```

Alerts, channels, views, segments, members and API keys come back as one full list.

## Shapes

Related fields sit in groups, such as `post`, `author`, `classification` and `triage` on a mention, or `reach`, `profile`, `stats` and `annotations` on a person. A missing group is `null`, for example `classification: null` before scoring.

Numbers Rumoro calculates are under `stats`. What you and your team add is under `triage` or `annotations`. A mention shows its `status` (`open`, `ignored`, `done`), `relevant` and `delivered`, never Rumoro's internal processing steps.

Times are ISO 8601 in UTC, like `"2026-10-04T08:21:37.512Z"`. Inputs such as `since`, `until` and `snoozedUntil` also accept epoch milliseconds.

Enum values are lowercase strings. The platforms are `bluesky`, `hackernews`, `github`, `stackoverflow`, `devto`, `reddit`, `x`, `youtube`, `news`, `linkedin`, `tiktok` and `instagram`, plus the review sites `appstore`, `googleplay`, `trustpilot` and `googlemaps`. A keyword's `platforms` lists where its term is searched (`[]` means nowhere) and never contains a review site. Reviews come from its `reviewSources` ([Reviews](/platforms/reviews)).

## Requests

Send JSON bodies with `Content-Type: application/json`, or you get `415 unsupported_media_type`. A body can be up to 128 KiB (`413 payload_too_large`) and must arrive within 5 seconds (`408 request_timeout`).

Sending a create request twice creates two resources, so check before you retry. Delivery and digest actions outside the reference take a `requestId` (a UUID) in the body. Repeating it returns the first answer, and reusing it with a different body returns `409 request_conflict`.

## Query parameters

Names are camelCase and booleans are `true` or `false`. A list can be repeated or comma-separated, so `?tags=vip,customer` is the same as `?tags=vip&tags=customer`. Parameters you leave out don't filter anything, and unknown parameters are ignored.

Most filters come in three forms. The single form (`platform=x`), the list form, which matches any value (`platforms=x,bluesky`), and the `not` form, which excludes all of them (`notPlatforms=news`). The single and list forms can be combined. Different filters narrow each other, so `platforms=x,bluesky&notSentiments=negative` means X or Bluesky, and nothing negative.

Mentions accept `platforms`, `sentiments`, `intents`, `keywordIds`, `tags`, `languages`, `ratings` and `linkHosts`, each with a `not` form, plus `keywordKinds`, `groupIds` and `notGroupIds` ([groups](/guides/groups)). People accept `platforms`, `tags` and `intents` the same way, plus `keywordKinds` and `neverKeywordKinds`.

A list filter takes up to 50 values. `tags` and `linkHosts` take up to 20. More than that returns `400 validation_error`.

### OR across groups (anyOf)

To combine conditions with OR, give mentions an `anyOf` list of 1 to 10 groups. Each group can hold the same conditions as a [saved view](/guides/views). A mention passes when at least one group matches, and your other filters still apply. Groups can't contain another `anyOf`, a time window or paging. An empty group, an empty `anyOf` or an unknown field returns `400`.

On `GET /v1/mentions` and the exports, send `anyOf` as URL-encoded JSON. This finds negative Reddit posts or questions since September 27.

```ts tab="TypeScript"
const { data: page } = await rumoro.searchMentions({
  query: {
    since: '2026-09-27T00:00:00Z',
    anyOf: JSON.stringify([{ platforms: ['reddit'], sentiments: ['negative'] }, { intents: ['question'] }]),
  },
});
```

```python tab="Python"
import json

page = rumoro.mentions.search(
    since="2026-09-27T00:00:00Z",
    any_of=json.dumps([{"platforms": ["reddit"], "sentiments": ["negative"]}, {"intents": ["question"]}]),
)
```

```bash tab="curl"
curl -G https://api.rumoro.dev/v1/mentions \
  -H "Authorization: Bearer $RUMORO_API_KEY" \
  --data-urlencode 'since=2026-09-27T00:00:00Z' \
  --data-urlencode 'anyOf=[{"platforms":["reddit"],"sentiments":["negative"]},{"intents":["question"]}]'
```

Saved views, alert rule filters and the MCP `search_mentions` tool take the same groups as plain JSON. This rule sends English or German mentions that show buying intent from accounts with 5,000 or more followers, or that are 1 star reviews.

```json
{
  "name": "Hot leads",
  "mode": "instant",
  "filter": {
    "languages": ["en", "de"],
    "anyOf": [
      { "intents": ["buy_intent"], "minFollowers": 5000 },
      { "ratings": [1] }
    ]
  }
}
```

## Errors

Every error uses the same envelope with a stable code. See [Errors](/errors).

```json
{ "error": { "code": "not_found", "message": "Mention not found", "requestId": "4b0e7d21-..." } }
```

API responses carry an `X-Request-Id` header, and errors repeat it as `error.requestId`. Include it when you contact support. You can send your own `X-Request-Id` of 8 to 64 characters (`A-Z`, `a-z`, `0-9`, `.`, `_`, `-`) and it is returned unchanged. Other values are replaced.

## Keys and scopes

Send `Authorization: Bearer <credential>` with an API key or OAuth token. A `read` credential can only make `GET` requests. Each credential works in one workspace, and `GET /v1/whoami` tells you which. See [Authentication](/authentication).

## Rate limits

Each workspace can make 600 requests a minute. Responses carry `X-RateLimit-*` headers, and requests over the limit get `429 rate_limited`. See [Rate limits](/rate-limits).

## Webhooks

Every event has the same envelope.

```json
{
  "id": "dlv_...",
  "event": "mention.matched",
  "createdAt": "2026-10-04T08:21:37.512Z",
  "alert": { "id": "feed_...", "name": "Pricing questions" },
  "data": { }
}
```

`data` is the resource as the API returns it. `X-Mentions-Signature-V2` is `v2=` followed by the hex HMAC-SHA256 of `<X-Mentions-Timestamp>.<raw body>`, signed with your channel secret. Because the timestamp is signed, an old request can't be sent again. `X-Mentions-Signature` signs only the body and is kept for older code. See [Webhooks](/webhooks).

## Versions

All paths start with `/v1`. New endpoints, optional fields, filters and enum values are added to `/v1` without notice, so ignore anything you don't recognize.

Changes that would break existing code come as a new version, `/v2`. Once `/v2` is out, `/v1` keeps working for at least six months. Endpoints that are going away will send `Deprecation` and `Sunset` headers, and the [changelog](/changelog) will say what changed and how to move. Clients generated from the OpenAPI document follow the new version when you regenerate them.

## Outside the reference

The dashboard's own endpoints for sign-in, setup and billing are not documented. A few endpoints you can call with a key have their own pages. These are connecting [Slack](/alerts/slack) and [Telegram](/alerts/telegram), and retrying or replaying deliveries ([Webhooks](/webhooks)). They wrap a single resource in `{ "data": ... }`, page with `{ "data", "meta": { "nextCursor" } }` and `after`, and return `202` when they queue work.
