Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Mentio Dev Usage API
version: 0.0.1
description: 'Operations tagged Usage 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: Usage
paths:
/v1/usage:
get:
operationId: getUsage
summary: Get usage and balance
description: The prepaid balance (ledger, pending mention charges, and the effective balance the stop rule reads), the daily burn and the days it buys, the keywords the wallet runs and pauses, the matches recorded today and over 30 days, and whether tracking is stopped or the balance is low. Every matched mention bills ($0.008), relevant or not; every active keyword bills $5 a month, charged daily.
tags:
- Usage
security:
- bearerAuth: []
responses:
'200':
description: The usage summary
content:
application/json:
schema:
$ref: '#/components/schemas/UsageSummary'
'401':
description: Missing or invalid API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
servers:
- url: https://api.mentio.dev
/v1/usage/breakdown:
get:
operationId: getUsageBreakdown
summary: Get the usage breakdown
description: 'What the workspace consumed and was charged over a window, grouped by one dimension per call (`by`: day, platform or keyword), in USD cents at list price, with the window''s totals on every call. `range` reads a trailing window of UTC days ending today (default 30d); `month` reads one calendar month (YYYY-MM), the shape a bill or a per-customer margin is reconciled against. Keyword-days come from the daily tick and mention charges from the matches that billed, so a deleted keyword keeps its charges in the keyword rows (`keyword.removed`) while its mention counts read 0; the same numbers ride on each keyword as `stats.cost` for the running month. `totals.ledgerDebitCents` is what the wallet has debited so far for the window''s days: mentions settle the morning after their day, so a window ending today lags `totals.totalCents` by the unsettled ones, and a closed month differs from it only by cumulative rounding. Rows are paged (`limit`, `offset`, `total`); a workspace may read this at most 30 times a minute through its keys and tokens together.'
tags:
- Usage
security:
- bearerAuth: []
parameters:
- schema:
type: string
enum:
- day
- platform
- keyword
- group
default: keyword
description: 'The dimension to group by: day (one row per UTC day of the window), platform, keyword (default: the row a margin is computed from), or group (what a customer or a campaign cost).'
required: false
description: 'The dimension to group by: day (one row per UTC day of the window), platform, keyword (default: the row a margin is computed from), or group (what a customer or a campaign cost).'
name: by
in: query
- schema:
type: string
enum:
- 7d
- 30d
- 90d
description: 'Trailing window of UTC days ending today: 7d, 30d, 90d (default 30d). Ignored when `month` is given.'
required: false
description: 'Trailing window of UTC days ending today: 7d, 30d, 90d (default 30d). Ignored when `month` is given.'
name: range
in: query
- schema:
type: string
pattern: ^\d{4}-(0[1-9]|1[0-2])$
description: 'A calendar month (YYYY-MM, UTC) instead of a trailing window: from its first day to its last, or to today for the running month. A future month is a 400.'
required: false
description: 'A calendar month (YYYY-MM, UTC) instead of a trailing window: from its first day to its last, or to today for the running month. A future month is a 400.'
name: month
in: query
- schema:
type: string
pattern: ^\d{4}-\d{2}-\d{2}$
description: First UTC day of a custom window, YYYY-MM-DD, inclusive; with `to` (default today). At most 366 days. Wins over `range` and `month`.
required: false
description: First UTC day of a custom window, YYYY-MM-DD, inclusive; with `to` (default today). At most 366 days. Wins over `range` and `month`.
name: from
in: query
- schema:
type: string
pattern: ^\d{4}-\d{2}-\d{2}$
description: Last UTC day of a custom window, YYYY-MM-DD, inclusive; never after today. Needs `from`.
required: false
description: Last UTC day of a custom window, YYYY-MM-DD, inclusive; never after today. Needs `from`.
name: to
in: query
- schema:
type:
- array
- 'null'
items:
type: string
minLength: 1
maxLength: 200
maxItems: 30
description: Only these keywords (kw_...), deleted ones included. Repeatable, or comma-separated.
required: false
description: Only these keywords (kw_...), deleted ones included. Repeatable, or comma-separated.
name: keywordIds
in: query
- schema:
type:
- array
- 'null'
items:
type: string
minLength: 1
maxLength: 200
maxItems: 30
description: 'Only these groups (grp_...): a keyword-day by the group it was metered under, a mention or a comment by its keyword''s group as it is now. Repeatable, or comma-separated.'
required: false
description: 'Only these groups (grp_...): a keyword-day by the group it was metered under, a mention or a comment by its keyword''s group as it is now. Repeatable, or comma-separated.'
name: groupIds
in: query
- schema:
type: array
items:
type: string
enum:
- bluesky
- hackernews
- github
- stackoverflow
- devto
- reddit
- x
- youtube
- news
- linkedin
- tiktok
- instagram
- appstore
- googleplay
- trustpilot
- googlemaps
- rss
maxItems: 20
description: Only these platforms. A keyword-day belongs to no platform, so with this filter the keyword line reads 0 and only mentions and comments count. Repeatable, or comma-separated.
required: false
description: Only these platforms. A keyword-day belongs to no platform, so with this filter the keyword line reads 0 and only mentions and comments count. Repeatable, or comma-separated.
name: platforms
in: query
- schema:
type: integer
minimum: 1
maximum: 500
default: 100
description: Rows per page, 1 to 500 (default 100). Only by=keyword can outgrow a page; a window has at most 90 days and a dozen platforms.
required: false
description: Rows per page, 1 to 500 (default 100). Only by=keyword can outgrow a page; a window has at most 90 days and a dozen platforms.
name: limit
in: query
- schema:
type:
- integer
- 'null'
minimum: 0
default: 0
description: Skip this many rows.
required: false
description: Skip this many rows.
name: offset
in: query
responses:
'200':
description: The window's totals and one page of rows for the dimension
content:
application/json:
schema:
$ref: '#/components/schemas/UsageBreakdown'
'400':
description: Unknown dimension or range, month malformed or in the future, limit out of range
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Missing or invalid API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: More than 30 breakdown reads this minute for the workspace
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
servers:
- url: https://api.mentio.dev
components:
schemas:
UsageBreakdown:
type: object
properties:
window:
type: object
properties:
from:
type: string
description: First day, YYYY-MM-DD, inclusive, UTC.
to:
type: string
description: 'Last day, inclusive: today for a trailing window or the running month.'
days:
type: integer
minimum: 0
description: Length of the window in days.
keywordDaysFrom:
type:
- string
- 'null'
description: 'The first day of the window with a recorded keyword count, or null when there is none. Earlier days carry keywordDays: null.'
required:
- from
- to
- days
- keywordDaysFrom
description: The window the report covers, in UTC days.
by:
type: string
enum:
- day
- platform
- keyword
- group
description: The dimension the rows are grouped by.
filtered:
type: boolean
description: 'true when keywordIds, groupIds or platforms narrowed the report: totals and rows count only what passes them, except totals.ledgerDebitCents, which is the whole workspace''s.'
currency:
type: string
enum:
- USD
description: Every amount is in USD cents.
totals:
type: object
properties:
keywordDays:
type: integer
minimum: 0
description: Keyword-days metered in the window.
keywordCents:
type: integer
description: The keyword-days at the keyword rate ($5 a month, 500/30 cents a day), rounded once on the total.
matchedMentions:
type: integer
minimum: 0
description: Matches recorded in the window, relevant or not.
billableMentions:
type: integer
minimum: 0
description: Of the matches billed in the window (every scored match, relevant or not), the ones in this group.
unclassifiedMentions:
type: integer
minimum: 0
description: 'Matches recorded in the window that are never charged: no score yet (still being scored, or classification failed), or a free review from the look-back of a newly connected app. Every other match is charged, on the mentions line or, for a comment, on the comments line.'
mentionCents:
type: integer
description: The billed mentions at $0.008 each, rounded once on the total.
billableComments:
type: integer
minimum: 0
description: 'Comments billed in the window (the comments line): comments delivered under your mentions and comments that matched a keyword, each once per workspace.'
commentCents:
type: integer
description: The billed comments at $0.008 each, rounded once on the total.
totalCents:
type: integer
description: keywordCents plus mentionCents plus commentCents.
ledgerDebitCents:
type: integer
description: What the ledger has debited so far for the days of the window, each debit by the day it settled, for the whole workspace whatever the filters. Mentions settle the morning after their day, so a window ending today lags totalCents by today's mentions (and yesterday's before the tick at 00:05 UTC); a closed month differs from totalCents only by cumulative rounding.
unattributedBillable:
type: integer
minimum: 0
description: Billed mentions whose match row is gone (deleted keyword), so no platform or keyword row can claim them. Charged all the same. Comments carry their keyword and platform on the billed row, so they are never unattributed (by=group puts a deleted keyword's comments on the no-group row).
required:
- keywordDays
- keywordCents
- matchedMentions
- billableMentions
- unclassifiedMentions
- mentionCents
- billableComments
- commentCents
- totalCents
- ledgerDebitCents
- unattributedBillable
description: The whole window as one line, the same for every dimension.
data:
type: array
items:
type: object
properties:
key:
type: string
description: 'The row''s key: the UTC day (YYYY-MM-DD) for by=day, the platform for by=platform, the keyword id for by=keyword, the group id for by=group.'
label:
type: string
description: 'Readable name: the keyword term or the group name, otherwise the key.'
keyword:
type:
- object
- 'null'
properties:
id:
type: string
description: Keyword id (kw_...); a deleted keyword keeps its id here.
term:
type: string
description: The term as it was last metered, or as the keyword reads now.
removed:
type: boolean
description: The keyword has since been deleted. Its charges stay on the record; its mentions went with it, so its mention counts read 0.
required:
- id
- term
- removed
description: by=keyword only; null otherwise.
group:
type:
- object
- 'null'
properties:
id:
type: string
description: Group id, or "none" for keyword-days metered before groups existed whose keyword is gone.
name:
type: string
description: The group's name as it reads now, or "Deleted group" / "No group".
removed:
type: boolean
description: The group has since been deleted.
required:
- id
- name
- removed
description: by=group only; null otherwise.
keywordDays:
type:
- integer
- 'null'
minimum: 0
description: 'Keyword-days metered in this group: the days the daily tick charged for. Null for by=platform (a keyword-day belongs to no platform) and for a day before the first recorded tick (unknown, not zero); a keyword row counts only the days on record, so before window.keywordDaysFrom it is a floor, not a zero.'
keywordCents:
type: integer
description: The keyword-days at the keyword rate ($5 a month, 500/30 cents a day), rounded once on the total.
matchedMentions:
type: integer
minimum: 0
description: Matches recorded in the window, relevant or not.
billableMentions:
type: integer
minimum: 0
description: Of the matches billed in the window (every scored match, relevant or not), the ones in this group.
unclassifiedMentions:
type: integer
minimum: 0
description: 'Matches recorded in the window that are never charged: no score yet (still being scored, or classification failed), or a free review from the look-back of a newly connected app. Every other match is charged, on the mentions line or, for a comment, on the comments line.'
mentionCents:
type: integer
description: The billed mentions at $0.008 each, rounded once on the total.
billableComments:
type: integer
minimum: 0
description: 'Comments billed in the window (the comments line): comments delivered under your mentions and comments that matched a keyword, each once per workspace.'
commentCents:
type: integer
description: The billed comments at $0.008 each, rounded once on the total.
totalCents:
type: integer
description: keywordCents plus mentionCents plus commentCents.
required:
- key
- label
- keyword
- group
- keywordDays
- keywordCents
- matchedMentions
- billableMentions
- unclassifiedMentions
- mentionCents
- billableComments
- commentCents
- totalCents
description: 'by=day: chronological. by=platform and by=keyword: most expensive first, then most matched, deleted keywords included.'
total:
type: integer
minimum: 0
description: Rows in the dimension before `limit` and `offset`.
required:
- window
- by
- filtered
- currency
- totals
- data
- total
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
UsageSummary:
type: object
properties:
balance:
type: object
properties:
cents:
type: integer
description: 'Ledger balance: every credit minus every settled debit.'
pendingCents:
type: integer
description: Mentions and comments billed since the last daily settlement, priced but not yet debited.
effectiveCents:
type: integer
description: 'cents minus pendingCents: what the stop rule and the keyword gate look at.'
currency:
type: string
enum:
- USD
required:
- cents
- pendingCents
- effectiveCents
- currency
description: The prepaid wallet.
burn:
type: object
properties:
perDayCents:
type: integer
description: Average daily debit over the last 7 days (or since the workspace was created).
daysLeft:
type:
- integer
- 'null'
minimum: 0
description: effectiveCents divided by perDayCents; null when nothing is burning.
required:
- perDayCents
- daysLeft
description: How fast the balance goes.
keywords:
type: object
properties:
active:
type: integer
minimum: 0
description: Unmuted keywords, the ones charged daily.
paused:
type: integer
minimum: 0
description: Keywords the wallet paused for lack of balance; a top-up resumes them.
capped:
type: integer
minimum: 0
description: 'Unmuted keywords at their monthly mention cap: still charged daily, not matched until the month turns or the cap is raised.'
limit:
type: integer
minimum: 0
description: 'How many keywords the workspace may run right now: the self-serve ceiling when the balance covers one more keyword-day, else 0.'
dayCents:
type: integer
description: What one more day of the active keywords costs.
required:
- active
- paused
- capped
- limit
- dayCents
description: Keywords against the wallet.
mentions:
type: object
properties:
today:
type: integer
minimum: 0
description: 'Matches recorded today (UTC), relevant or not: every one bills.'
last30d:
type: integer
minimum: 0
description: Matches recorded in the last 30 days.
required:
- today
- last30d
description: Matched mentions, the other thing that bills.
stopped:
type: boolean
description: The wallet paused tracking; a top-up that covers a day of every keyword resumes it.
lowBalance:
type: boolean
description: Running, and the effective balance is at or under 20 percent of the last credit.
lastTopUpAt:
type:
- string
- 'null'
description: Newest paid top-up; null before the first.
required:
- balance
- burn
- keywords
- mentions
- stopped
- lowBalance
- lastTopUpAt
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.