Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Mentio Dev Attention API
version: 0.0.1
description: 'Operations tagged Attention across 4 of this provider''s published API definitions: api_mentio.json, mentio-dev-api-openapi.json, mentio-dev-app-openapi.json, mentio-dev-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://api.mentio.dev
tags:
- name: Attention
paths:
/v1/attention:
get:
operationId: listAttention
summary: List attention items
description: 'What needs a person, newest first: a keyword whose mentions spiked in the last hour (mention.spike), whose negative share of the last 24 hours jumped (sentiment.negative_spike), that turned noisy (keyword.noisy), or a channel whose last sends all failed (channel.failing). Detected once an hour; an item opens when its condition starts and resolves on its own when the condition is gone. Open items by default; `status=all` reads the history. Each opening is also an account event of the same name, which webhook, Slack, email and Telegram channels can subscribe to.'
tags:
- Attention
security:
- bearerAuth: []
parameters:
- schema:
type: string
enum:
- open
- resolved
- dismissed
- all
default: open
description: open (default), resolved, dismissed, or all.
required: false
description: open (default), resolved, dismissed, or all.
name: status
in: query
- schema:
type: string
description: 'Only these kinds, comma separated: mention.spike, sentiment.negative_spike, keyword.noisy, channel.failing, feed.failing (feed.recovered never opens an item).'
required: false
description: 'Only these kinds, comma separated: mention.spike, sentiment.negative_spike, keyword.noisy, channel.failing, feed.failing (feed.recovered never opens an item).'
name: kind
in: query
- schema:
type: integer
minimum: 1
maximum: 100
default: 50
description: Items per page, newest first; 50 by default, at most 100.
required: false
description: Items per page, newest first; 50 by default, at most 100.
name: limit
in: query
- schema:
type: string
description: nextCursor from the previous page.
required: false
description: nextCursor from the previous page.
name: cursor
in: query
responses:
'200':
description: Attention items
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/AttentionItem'
nextCursor:
type:
- string
- 'null'
description: Pass as cursor for the next page; null on the last one.
required:
- data
- nextCursor
'400':
description: Unknown kind, or a cursor this list did not hand out
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Missing or invalid API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
servers:
- url: https://api.mentio.dev
/v1/attention/{id}/dismiss:
post:
operationId: dismissAttention
summary: Dismiss an attention item
description: Put an item away. It leaves the open list and does not come back while its condition lasts; once the condition clears, a new episode may open a new item. Idempotent.
tags:
- Attention
security:
- bearerAuth: []
parameters:
- schema:
type: string
minLength: 1
description: Attention item id (att_...).
example: att_abc123
required: true
description: Attention item id (att_...).
name: id
in: path
responses:
'200':
description: The dismissed item
content:
application/json:
schema:
$ref: '#/components/schemas/AttentionItem'
'401':
description: Missing or invalid API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: No such attention item in this workspace
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
servers:
- url: https://api.mentio.dev
components:
schemas:
FeedFailingEventData:
type: object
properties:
attentionId:
type: string
description: 'The attention item (att_...): GET /v1/attention lists it, POST /v1/attention/{id}/dismiss puts it away.'
url:
type: string
description: Where to look in the dashboard.
feed:
type: object
properties:
id:
type: string
description: Feed id (fd_...).
url:
type: string
description: The feed address.
title:
type:
- string
- 'null'
description: The feed's own title, when it has one.
required:
- id
- url
- title
description: The workspace feed (GET /v1/feeds/{id}).
reason:
type: string
enum:
- login_required
- login_expired
- blocked
- not_found
- not_a_feed
- unreachable
description: 'login_required: the feed answers a sign-in or access-denied page (connect it with the forum''s sign-in, auth). login_expired: the same on a feed that has a sign-in: copy a fresh cookie. blocked: a 403, a 429 or a bot check. not_found: the feed is gone. not_a_feed: the address answers a page, not RSS or Atom. unreachable: no answer or a server error.'
detail:
type:
- string
- 'null'
description: What the last failed read saw, in a few words.
failingSince:
type: string
description: 'When it started failing: the third failed read in a row.'
keywordsAffected:
type: integer
minimum: 0
description: 'Live keywords of the workspace that read workspace feeds: what misses its posts while it fails.'
required:
- attentionId
- url
- feed
- reason
- detail
- failingSince
- keywordsAffected
ChannelFailingEventData:
type: object
properties:
attentionId:
type: string
description: 'The attention item (att_...): GET /v1/attention lists it, POST /v1/attention/{id}/dismiss puts it away.'
url:
type: string
description: Where to look in the dashboard.
channel:
type: object
properties:
id:
type: string
description: Channel id (dest_...).
kind:
type: string
enum:
- slack
- email
- webhook
- telegram
description: slack, email, webhook or telegram.
label:
type: string
description: The channel's label.
required:
- id
- kind
- label
description: The channel whose sends fail.
failures:
type: integer
minimum: 0
description: Failed sends in a row within the last 24 hours.
lastError:
type:
- string
- 'null'
description: The newest failure, as the transport reported it.
since:
type: string
description: The oldest of those failures.
required:
- attentionId
- url
- channel
- failures
- lastError
- since
MentionSpikeEventData:
type: object
properties:
attentionId:
type: string
description: 'The attention item (att_...): GET /v1/attention lists it, POST /v1/attention/{id}/dismiss puts it away.'
url:
type: string
description: Where to look in the dashboard.
keyword:
type: object
properties:
id:
type: string
description: Keyword id (kw_...).
term:
type: string
description: The tracked term.
kind:
type: string
enum:
- brand
- competitor
- topic
description: brand, competitor or topic.
name:
type: string
description: 'The keyword as Mentio names it: the term, or "term (Group)" outside the default group.'
groupId:
type: string
description: Its group (grp_...).
required:
- id
- term
- kind
- name
- groupId
description: The keyword the item is about.
window:
type: object
properties:
from:
type: string
description: Start, inclusive.
to:
type: string
description: End, exclusive.
required:
- from
- to
description: The stretch of time the counts cover, by match time.
matches:
type: integer
minimum: 0
description: 'Fresh matches in the hour: posts published at most 6 hours before they matched. Look-backs and free reviews never count.'
relevant:
type: integer
minimum: 0
description: Of those, scored relevant so far.
baseline:
type: object
properties:
meanPerHour:
type: number
minimum: 0
description: Fresh matches per hour over the baseline.
stddevPerHour:
type: number
minimum: 0
description: Their standard deviation.
hours:
type: integer
minimum: 0
description: 'Hours in the baseline: the week before the window, or since the keyword started listening.'
required:
- meanPerHour
- stddevPerHour
- hours
description: What the keyword usually gets.
required:
- attentionId
- url
- keyword
- window
- matches
- relevant
- baseline
ErrorResponse:
type: object
properties:
error:
type: object
properties:
code:
type: string
enum:
- unauthorized
- forbidden
- read_only_key
- validation_error
- not_found
- invalid_cursor
- payload_too_large
- rate_limited
- duplicate_keyword
- insufficient_balance
- keyword_limit_reached
- billing_not_configured
- order_not_credited
- schedule_required
- unknown_channel
- not_a_digest
- hourly_email_unsupported
- slack_not_connected
- slack_not_configured
- telegram_not_configured
- feed_unreadable
- feed_login_required
- feed_login_expired
- feed_limit_reached
- feed_auth_not_configured
- email_not_configured
- invalid_assignee
- classification_pending
- invalid_member
- already_member
- last_owner
- duplicate_segment
- filter_too_complex
- duplicate_view
- duplicate_group
- default_group
- default_group_context
- group_changed
- invalid_signature
- webhook_not_configured
- invalid_token
- protected_user
- upstream_unavailable
- internal_error
description: 'Stable machine-readable code; branch on this, never on the message. Codes are additive: a client should treat one it does not know as a generic failure of the same status.'
message:
type: string
description: Human-readable detail; may change between releases.
requestId:
type: string
description: The id of this request (also the X-Request-Id response header); quote it to support.
retryAfterSeconds:
type: integer
description: 'For rate_limited and other retryable errors: how long to wait before retrying (mirrors the Retry-After header).'
required:
- code
- message
description: The error.
required:
- error
KeywordNoisyEventData:
type: object
properties:
attentionId:
type: string
description: 'The attention item (att_...): GET /v1/attention lists it, POST /v1/attention/{id}/dismiss puts it away.'
url:
type: string
description: Where to look in the dashboard.
keyword:
type: object
properties:
id:
type: string
description: Keyword id (kw_...).
term:
type: string
description: The tracked term.
kind:
type: string
enum:
- brand
- competitor
- topic
description: brand, competitor or topic.
name:
type: string
description: 'The keyword as Mentio names it: the term, or "term (Group)" outside the default group.'
groupId:
type: string
description: Its group (grp_...).
required:
- id
- term
- kind
- name
- groupId
description: The keyword the item is about.
windowDays:
type: integer
minimum: 0
description: The window the noise is measured over, in days (since the keyword's last change when that is inside it).
scored:
type: integer
minimum: 0
description: Matches the classifier scored in that window.
relevant:
type: integer
minimum: 0
description: Of those, relevant.
noiseShare:
type: number
minimum: 0
maximum: 1
description: The share scored under the relevance line. Every match bills, relevant or not.
required:
- attentionId
- url
- keyword
- windowDays
- scored
- relevant
- noiseShare
AttentionItem:
type: object
properties:
id:
type: string
description: Attention item id (att_...).
kind:
type: string
enum:
- mention.spike
- sentiment.negative_spike
- keyword.noisy
- channel.failing
- feed.failing
- feed.recovered
description: 'mention.spike: far more mentions in the last hour than the keyword usually gets. sentiment.negative_spike: the negative share of the last 24 hours jumped. keyword.noisy: most scored matches are noise. channel.failing: the channel''s last sends all failed. feed.failing: a workspace feed failed its last 3 hourly reads. feed.recovered: it read again after failing.'
status:
type: string
enum:
- open
- resolved
- dismissed
description: 'open: the condition holds. resolved: it no longer does. dismissed: a person put it away; it does not come back for the same episode.'
subject:
type: object
properties:
type:
type: string
enum:
- keyword
- channel
- feed
description: keyword, channel or feed.
id:
type: string
description: The keyword (kw_...), the channel (dest_...) or the feed (fd_...).
required:
- type
- id
description: What the item is about.
title:
type: string
description: 'One line for a person: what happened.'
openedAt:
type: string
description: When the condition was first seen.
resolvedAt:
type:
- string
- 'null'
description: When it no longer held; null while it does.
dismissedAt:
type:
- string
- 'null'
description: When a person dismissed it; null if nobody did.
data:
anyOf:
- $ref: '#/components/schemas/MentionSpikeEventData'
- $ref: '#/components/schemas/NegativeSpikeEventData'
- $ref: '#/components/schemas/KeywordNoisyEventData'
- $ref: '#/components/schemas/ChannelFailingEventData'
- $ref: '#/components/schemas/FeedFailingEventData'
description: 'The facts as they stood when it opened: the same object the account event of the same name carries as `data`.'
required:
- id
- kind
- status
- subject
- title
- openedAt
- resolvedAt
- dismissedAt
- data
NegativeSpikeEventData:
type: object
properties:
attentionId:
type: string
description: 'The attention item (att_...): GET /v1/attention lists it, POST /v1/attention/{id}/dismiss puts it away.'
url:
type: string
description: Where to look in the dashboard.
keyword:
type: object
properties:
id:
type: string
description: Keyword id (kw_...).
term:
type: string
description: The tracked term.
kind:
type: string
enum:
- brand
- competitor
- topic
description: brand, competitor or topic.
name:
type: string
description: 'The keyword as Mentio names it: the term, or "term (Group)" outside the default group.'
groupId:
type: string
description: Its group (grp_...).
required:
- id
- term
- kind
- name
- groupId
description: The keyword the item is about.
window:
type: object
properties:
from:
type: string
description: Start, inclusive.
to:
type: string
description: End, exclusive.
required:
- from
- to
description: The stretch of time the counts cover, by match time.
negative:
type: integer
minimum: 0
description: Relevant matches scored negative in the window.
relevant:
type: integer
minimum: 0
description: Relevant matches in the window.
share:
type: number
minimum: 0
maximum: 1
description: negative / relevant.
baseline:
type: object
properties:
negative:
type: integer
minimum: 0
description: Negative relevant matches in the 7 days before the window.
relevant:
type: integer
minimum: 0
description: Relevant matches in those 7 days.
share:
type: number
minimum: 0
maximum: 1
description: The usual negative share, smoothed so a quiet week still has one.
required:
- negative
- relevant
- share
description: What the keyword usually reads.
required:
- attentionId
- url
- keyword
- window
- negative
- relevant
- share
- baseline
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: API key minted via POST /v1/api-keys (format mk_live_...)
x-refined-from:
- api_mentio.json
- mentio-dev-api-openapi.json
- mentio-dev-app-openapi.json
- mentio-dev-openapi.json
x-mentio-errors:
- code: unauthorized
status: 401
source: user
retryable: false
description: No credential, or one that does not resolve to a workspace.
fix: 'Send Authorization: Bearer mk_live_… with a key from /api-keys.'
- code: forbidden
status: 403
source: user
retryable: false
description: The credential is valid but may not do this.
- code: read_only_key
status: 403
source: user
retryable: false
description: A read key was used on a write endpoint.
fix: Create a key with write scope.
- code: validation_error
status: 400
source: user
retryable: false
description: The body or query failed validation; the message names the field.
- code: not_found
status: 404
source: user
retryable: false
description: No such resource in your workspace. An id from another workspace is a 404, never a 403.
- code: invalid_cursor
status: 400
source: user
retryable: false
description: The cursor was not one this endpoint issued, or the filters changed between pages.
- code: payload_too_large
status: 413
source: user
retryable: false
description: The body exceeds the endpoint limit.
- code: rate_limited
status: 429
source: user
retryable: true
description: The workspace made more than 600 requests in a minute (every key and token together), or hit an endpoint's own cap; Retry-After says when.
fix: Wait Retry-After seconds and send the same request again. X-RateLimit-Remaining on every response says how much room is left.
- code: duplicate_keyword
status: 409
source: user
retryable: false
description: The group already tracks this term. A term may be tracked once per group; another group may track it too.
- code: insufficient_balance
status: 402
source: user
retryable: false
description: The wallet cannot cover one more keyword-day.
fix: Top up from Billing; tracking resumes on its own.
- code: keyword_limit_reached
status: 402
source: user
retryable: false
description: The self-serve keyword cap; beyond it is an enterprise conversation.
- code: billing_not_configured
status: 503
source: system
retryable: false
description: This deployment has no payment provider set up.
- code: schedule_required
status: 400
source: user
retryable: false
description: A daily alert rule was created without a schedule.
- code: unknown_channel
status: 400
source: user
retryable: false
description: A rule referenced a channel id the workspace does not own.
- code: not_a_digest
status: 400
source: user
retryable: false
description: A digest-only action was called on an instant rule.
- code: hourly_email_unsupported
status: 400
source: user
retryable: false
description: An hourly rule was given an email channel (up to 24 emails a day per rule). Use Slack, Telegram or a webhook, or a daily digest by email.
- code: slack_not_connected
status: 409
source: user
retryable: false
description: Connect Slack from Settings first. 404 on the Slack connection endpoints, 409 elsewhere.
- code: slack_not_configured
status: 503
source: system
retryable: false
description: This deployment has no Slack app credentials.
- code: telegram_not_configured
status: 503
source: system
retryable: false
description: This deployment has no Telegram bot.
- code: feed_unreadable
status: 400
source: user
retryable: false
description: No feed was found at the address, or the site blocks automated readers, or it answers an error; the message says which.
- code: feed_login_required
status: 400
source: user
retryable: false
description: The address answers a sign-in or access-denied page. Send it again with `auth` (the cookie your browser sends to the forum, or one header) to read it signed in.
- code: feed_login_expired
status: 400
source: user
retryable: false
description: 'The feed still answers a sign-in page with the `auth` sent: copy a fresh cookie from a signed-in browser.'
- code: feed_limit_reached
status: 409
source: user
retryable: false
description: A workspace reads at most 500 feeds; disconnect one before connecting another.
- code: feed_auth_not_configured
status: 503
source: system
retryable: false
description: This deployment has no FEED_AUTH_SECRET, so a feed sign-in (auth) cannot be stored encrypted and is refused instead.
- code: email_not_configured
status: 503
source: system
retryable: false
description: This deployment has no Resend key, so nothing that needs Resend (the audience sync) can run.
- code: invalid_assignee
status: 400
source: user
retryable: false
description: The assignee id is not a workspace member.
- code: classification_pending
status: 409
source: user
retryable: true
description: A relevance or sentiment verdict was sent for a mention the classifier has not scored yet.
- code: invalid_member
status: 400
source: user
retryable: false
description: A person's owner, and the member an outreach activity is credited to, must be workspace members.
- code: already_member
status: 409
source: user
retryable: false
description: An invitation was sent to an address that already belongs to a member.
- code: last_owner
status: 409
source: user
retryable: false
description: Removing this member would leave the workspace without an owner.
- code: duplicate_segment
status: 409
source: user
retryable: false
description: Segment names are unique per workspace.
- code: filter_too_complex
status: 400
source: user
retryable: false
description: The filters together (lists, anyOf groups, a view or alert rule applied on top) name more values than one query can carry. Shorten the lists or use fewer groups.
- code: duplicate_view
status: 409
source: user
retryable: false
description: View names are unique per workspace.
- code: duplicate_group
status: 409
source: user
retryable: false
description: Group names and externalIds are unique per workspace.
- code: default_group
status: 409
source: user
retryable: false
description: Every workspace keeps one default group, where a keyword lands when no group is named.
- code: default_group_context
status: 400
source: user
retryable: false
description: 'The default group is the workspace itself: its company is the profile (PATCH /v1/company). A company description goes on any other group.'
- code: group_changed
status: 409
source: user
retryable: false
description: A keyword was created in or moved into the group while its keywords were being deleted. Its other keywords are gone; retry the delete.
- code: invalid_signature
status: 401
source: user
retryable: false
description: An inbound webhook did not carry a valid signature.
- code: webhook_not_configured
status: 503
source: system
retryable: false
description: The inbound webhook has no secret on this deployment.
- code: invalid_token
status: 400
source: user
retryable: false
description: A signed link (unsubscribe, confirmation) was tampered with or is malformed.
- code: protected_user
status: 409
source: user
retryable: false
description: Staff accounts cannot be deleted from the admin page.
- code: upstream_unavailable
status: 502
source: platform
retryable: true
description: A provider (Slack, Telegram, Polar, …) failed while serving your request. Retry with backoff.
- code: internal_error
status: 500
source: system
retryable: false
description: Something failed on our side. The response carries a requestId; quote it to support.