---
title: "Conventions"
description: "The rules every endpoint follows, so one resource teaches you all of them."
canonical: https://docs.mentio.dev/conventions
markdown: https://docs.mentio.dev/conventions.mdx
---

# Conventions

The rules every endpoint follows, so one resource teaches you all of them.

The API lives under `/v1`. Everything below holds for every resource in the reference, so the shape of one endpoint tells you the shape of the rest.

## Resources and paths

Plural nouns, one id form: `/v1/mentions/{id}`, `/v1/people/{id}`, `/v1/segments/{id}`, `/v1/keywords/{id}`, `/v1/alerts/{id}`, `/v1/channels/{id}`, `/v1/api-keys/{id}`. Ids are prefixed strings (`mm_`, `aut_`, `seg_`, `kw_`, `feed_`, `dest_`, `key_`) and only resolve inside your workspace: an id from another workspace is a 404, never a 403.

Actions that are not plain updates are `POST` verbs on the resource: `/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`.

Analytics are named reports, not a query language: `/v1/analytics/summary`, `/v1/analytics/series`, `/v1/analytics/breakdown` and `/v1/analytics/share-of-voice`. They share one window grammar (`range` or `from` and `to`, `keywordIds`, `platforms`, `timezone`, `compare`), each returns a typed shape with a `window`, and `series` and `breakdown` take a `by` dimension. Exports are `GET .../export.csv` on the resource they list (`/v1/mentions/export.csv`, `/v1/people/export.csv`) and take the same filters as the list.

## Methods and status codes

| Method                 | Meaning                                                             | Success                       |
| ---------------------- | ------------------------------------------------------------------- | ----------------------------- |
| `GET`                  | Read one, or list                                                   | 200                           |
| `POST` on a collection | Create                                                              | 201 with the created resource |
| `PATCH`                | Partial update: omitted fields are untouched, `null` clears a field | 200 with the updated resource |
| `DELETE`               | Remove                                                              | 204, no body                  |
| `POST` action          | Do something                                                        | 200 with the outcome          |

There is one way to change a resource: `PATCH` it. A mention is ignored, closed, assigned, snoozed or annotated through the same `PATCH /v1/mentions/{id}`.

## Envelopes

Single resources come back bare. Lists come back as `data` plus paging:

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

`nextCursor` is `null` on the last page. Pass it back as `cursor` with the same filters and sort. Lists that are aggregated rather than streamed (people) page by `offset` and carry `total` instead:

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

Small configuration lists (keywords, alerts, channels, segments, API keys) are not paged: `data` holds everything.

## Shapes

Fields are nested by concern. A mention has `post`, `author`, `classification` and `triage`; a person has `reach`, `profile`, `stats` and `annotations`. When a whole concern is absent it is `null` as a whole (`author: null` for an anonymous post, `classification: null` while the classifier has not run), never a scatter of nulls.

Computed numbers live under `stats`; what you or your teammates wrote lives under `triage` or `annotations`. Pipeline internals never appear: a mention has a `status` (`open`, `ignored`, `done`) and booleans `relevant` and `delivered`, not the stages it went through.

Every timestamp is an ISO 8601 string in UTC, for example `"2026-09-03T10:04:44.881Z"`. Fields and filters that take an instant (`since`, `until`, `snoozedUntil`) accept ISO 8601 or epoch milliseconds.

Enumerations are lowercase strings and are listed in each field's description in the reference. Platforms are `bluesky`, `hackernews`, `github`, `stackoverflow`, `devto`, `reddit`, `x`, `youtube`, `news`, `linkedin`.

## Query parameters

camelCase names. Booleans are `true` or `false`. A list parameter is repeatable or comma-separated: `?tags=vip,customer` and `?tags=vip&tags=customer` are the same. A parameter you omit imposes no constraint.

A value filter comes in three forms: the singular (`platform=x`), the plural, which keeps any of them (`platforms=x,linkedin`), and the negative, which drops all of them (`notPlatforms=youtube`). The singular and plural combine, so `platform=x&platforms=reddit` reads as either. Different filters always narrow each other: `platforms=x,linkedin&notSentiments=negative` is "X or LinkedIn, nothing negative".

Mentions take `platforms`, `sentiments`, `intents`, `keywordIds`, `tags` and `linkHost`, each with a `not` twin; people take `platforms`, `tags` and `intents` the same way, plus `keywordKinds` and `neverKeywordKinds`. A mention the classifier has not scored yet still passes a `notSentiments` filter: it is not that sentiment.

## Errors

One envelope, stable codes, see [Errors](/errors):

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

Every response carries an `X-Request-Id` header, and an error repeats it as `error.requestId`; quote it when you write to support. You may send your own `X-Request-Id` (8 to 64 characters of `A-Z`, `a-z`, `0-9`, `.`, `_`, `-`) and it is echoed back, so a request can be traced from your logs to ours; anything else is replaced by an id we mint.

## Keys and scopes

Requests carry `Authorization: Bearer <key>`. A key is `read` or `write`; a `read` key can only call `GET`. Keys belong to one workspace, and every request acts inside it; `GET /v1/whoami` says which one, and whether the credential may write.

## Webhooks

Every payload uses one envelope, whatever the event:

```json
{
  "id": "dlv_...",
  "event": "mention.matched",
  "createdAt": "2026-09-03T10:04:44.881Z",
  "alert": { "id": "feed_...", "name": "Buying signals" },
  "data": { ...the resource, shaped exactly as the API returns it... }
}
```

The body is signed: `X-Mentions-Signature` is the hex HMAC-SHA256 of the raw bytes with your channel secret. Events, payloads and retries are covered in [Webhooks](/webhooks).

## Versioning and deprecation

The version is in the path: `/v1`. Additive changes (a new endpoint, a new optional field or filter, a new value in an enum) ship on `/v1` without notice and never remove or rename anything, so a client that ignores what it does not know keeps working. A change that would break a client ships as a new path (`/v2`); `/v1` keeps working for at least six months after that, every `/v1` response in that window carries `Deprecation` and `Sunset` headers (RFC 9745, RFC 8594) with the date it stops, and this page says what changed. The SDKs, the CLI and the skill are generated from `/v1/openapi.json` and follow it release by release.

## What is not in the reference

The dashboard talks to the same service for sign-in, onboarding, billing, and the Slack and Telegram connection flows. Those endpoints are shaped for the dashboard, change with it, and are deliberately left out of this reference. Everything documented here works with an API key.
