---
title: "Billing"
description: "The prepaid balance, what a keyword-day and a mention cost, and the wallet and ledger endpoints."
canonical: https://docs.mentio.dev/billing
markdown: https://docs.mentio.dev/billing.mdx
---

# Billing

The prepaid balance, what a keyword-day and a mention cost, and the wallet and ledger endpoints.

Mentio bills by usage from a prepaid balance: what you track, and what the pipeline actually finds for you. There are no seats, no tiers, no subscription and no packs. You add any amount, and the balance goes down a little every day while keywords are tracked and mentions are found.

Money in the API is always integer USD cents (`500` is $5.00). The dashboard does the formatting.

## How it works

| Line     | Price                         | How it accrues                                                                                                                                                                                                                                                     |
| -------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Keywords | 5 USD per keyword per month   | Deducted per day: 5/30 USD per keyword-day. A keyword tracked for 12 days costs 12/30 of the monthly price                                                                                                                                                         |
| Mentions | 0.008 USD per matched mention | Every post the pipeline matches to one of your keywords, relevant or not. Keywords bundle no mentions                                                                                                                                                              |
| Comments | 0.008 USD per comment         | The [comments](/guides/comments) you receive under your mentions, for keywords with comments on, and every matched reply or comment (X and Bluesky replies, Hacker News comments, Stack Overflow answers), with comments on or not. Once per workspace and comment |

A mention counts when the pipeline finds a post matching one of your keywords, whether the classifier scores it relevant or filters it as noise. Both numbers are shown in the dashboard, so the balance never disagrees with what you can see in the feed. Mentions the classifier could not score (a `failed` classification) are never charged: an outage on our side costs you nothing.

Deductions settle once per UTC day. Between settlements, mentions and comments billed since the last one are priced and shown as `pendingCents`, and the balance the stop rule and the keyword gate look at is `balanceCents - pendingCents`.

For a script or an agent, [`GET /v1/usage`](/api/usage/get-usage) answers the money question in one read: the balance (ledger, pending, effective), the daily burn and the days it buys, the keywords running and paused, the matches recorded today and over 30 days, and whether tracking is stopped or the balance is low. The dashboard's own wallet routes stay out of the reference.

## Cost per keyword

Where the money went is [`GET /v1/usage/breakdown`](/api/usage/get-usage-breakdown): what the workspace consumed and was charged over a window, in USD cents at list price, grouped by one dimension per call, the way the [analytics reports](/api/analytics/get-analytics-breakdown) are:

* `by=keyword` (the default): one row per keyword with the days it was charged for (`keywordDays`), those days priced (`keywordCents`), the matches that billed on the mentions line (`billableMentions`), those priced (`mentionCents`), the comments line (`billableComments`, `commentCents`) and the sum (`totalCents`). A keyword deleted during the window keeps its row with `keyword.removed: true`: its charges stay on the record, its mention counts read 0 because its matches went with it.
* `by=group`: one row per [keyword group](/guides/groups), the bill per customer or campaign. Keyword-days count under the group the keyword was in on the day they were charged, so moving or deleting a keyword never moves money already spent; a group deleted during the window keeps its row with `group.removed: true`.
* `by=platform`: matched and billed mentions per platform. Keyword-days belong to no platform, so `keywordDays` is null there.
* `by=day`: one row per UTC day of the window, chronological, the shape of the dashboard's chart.

Two ways to pick the window: `range=7d|30d|90d` (default 30d) is a trailing window ending today; `month=2026-09` is one calendar month, from its first day to its last or to today for the running month, which is the shape a bill, or a margin per customer, is reconciled against. A month that has not started is a `400`.

Every call also returns `totals` for the whole window (keyword-days, matched, billed and never-charged mentions, billed comments, the list-price total) and `totals.ledgerDebitCents`, 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 the list price by today's mentions (and yesterday's before the tick at 00:05 UTC); a closed month differs from it only by cumulative rounding. Rows are paged with `limit` (up to 500) and `offset`, and `total` says how many there are; only `by=keyword` can outgrow a page. A workspace may read the breakdown 30 times a minute through its keys and tokens together (the dashboard is not counted), on top of the [general limit](/conventions#rate-limits); past it the answer is a `429` with `Retry-After`.

The same numbers ride on every keyword as `stats.cost`, for the running month, in the same words: `keywordDays`, `keywordCents`, `billableMentions`, `mentionCents`, `billableComments`, `commentCents` and `totalCents`. So `GET /v1/keywords` is one call for this month's cost of every keyword, and `stats.cost.totalCents` on a keyword equals its row in `GET /v1/usage/breakdown?by=keyword&month=<this month>`. `billableMentions` counts matches by the moment they were scored, the clock the ledger settles by, so it can trail `stats.thisMonth` by the matches still being scored and never counts one that failed to score.

```bash
# What did each keyword cost in September?
curl -sS "https://api.mentio.dev/v1/usage/breakdown?by=keyword&month=2026-09&limit=500" \
  -H "Authorization: Bearer mk_live_..." | jq '.data[] | {term: .label, totalCents, removed: .keyword.removed}'
```

## The welcome credit

Every new workspace starts with $5.80 of free credit: one keyword-month plus 100 mentions. It is granted once per user (a second workspace of the same user gets none), and no card is needed to use it. `GET /v1/billing/wallet` reports it under `signupCredit`.

Until a workspace adds funds for the first time, two guards keep the welcome credit for mentions that are about you:

* **A limit of 200 mentions a keyword a month.** Every keyword carries it as its cap, marked `cap.welcome: true` so you can tell it from a cap of your own. At the limit the keyword reads `pausedForCap: true` and waits for the first of the month, and it is not charged its daily keyword fee while it waits. A cap of your own that can never reach 200 in a month stays yours (under 200 a month, 6 or fewer a day, 39 or fewer a week); any other is kept in `cap.own` and `cap.ownPer` and applies from your first top-up. That top-up (or credit our team adds to the workspace) removes the limit and resumes the keyword at once. Sending `cap: { "mentions": 200 }` back while `cap.welcome` is true changes nothing.
* **The noise brake.** A keyword with at least 20 scored matches in the last 14 days of which fewer than 30% are relevant (a common word, like a brand named "profound") is paused: `muted: true`, `pausedForNoise: true`, `status=noisy`, and every owner gets one email. Change its required or excluded terms, its platforms or its context and it starts again (as long as the balance covers another day), judged only on what it matches from then on; or unmute it as it is. A top-up does not resume it: fixing it is your call.

Once a workspace has added funds, neither applies. A noisy keyword is still flagged in `stats.noise.noisy` and in the dashboard.

## Adding funds

A card can also be added on its own, without a top-up: **Add a card** under Payment method opens Polar's hosted page, which keeps the card for later top-ups and for auto recharge.

The dashboard's **Add funds** button opens a hosted Polar checkout with the amount prefilled ($20 by default); the customer can type any other amount from $20 to $5,000 per top-up. Polar is the merchant of record, so tax is added at checkout, and the balance is credited as soon as the order is paid. The endpoint behind the button is dashboard plumbing and is left out of the reference. Top-ups are non-refundable except where the law requires it; a refund Polar does issue is taken back off the balance.

## Your card

There is no separate step to add a card. The first top-up checkout takes the card and the billing address, and Polar keeps them on the workspace's customer record for the next top-ups. The billing page shows the cards on file (brand, last four digits, expiry) under **Payment method**, and the **Manage cards** button opens Polar's hosted portal, where cards can be added or removed, billing details changed and receipts downloaded. Every top-up checkout also accepts a different card, so paying with a new one is another way to change it.

## Auto recharge

With a card on file, a workspace can top itself up: set a threshold and an amount under **Auto recharge** on the billing page. Whenever the effective balance drops under the threshold, or under what one day of the running keywords costs, the saved card is charged the amount and the balance credited, the same way a manual top-up is; a paused workspace with auto recharge on is refilled the same way. Some rules keep it predictable:

* At most one automatic charge per workspace per UTC day, whatever happens to the balance afterwards. A noisy keyword can cost at most one top-up a day.
* An optional **weekly limit** caps what the card is charged automatically over any 7 days (before tax). A charge that would pass it is held back, nothing is charged, and the billing page says when the next automatic top-up can run; the limit must be at least one top-up. The wallet reports it as `autoRecharge.weeklyLimitCents`, with `autoRecharge.spentThisWeekCents` for what counts against it over the last 7 days (charges made plus any still settling).
* The amount follows the same bounds as a manual top-up ($20 to $5,000); the threshold can be anything from zero up.
* A failed charge (declined card, no card on file) is emailed to the workspace owners with the reason and retried the next day. The billing page shows the last automatic top-up and the last failure.
* A workspace that was paused resumes as soon as the automatic top-up lands, like after a manual one.

The setting appears on the billing page once the deployment can charge saved cards. `GET /v1/billing/wallet` reports it under `autoRecharge` (`available`, `enabled`, `thresholdCents`, `amountCents`, `lastRunAt`, `lastError`), and `PATCH /v1/billing/auto-recharge` with any of `enabled`, `thresholdCents`, `amountCents` changes it.

## When the balance runs out

Tracking pauses in two situations. At the daily settlement, if the effective balance cannot cover the day about to be charged for the running keywords (`nextDayCents`), the workspace is paused before the charge, so a day is never charged and lost. During the day, only if matched mentions overdraw what was already paid (the effective balance drops under zero). Nothing is deleted: keywords, mentions, alerts and channels stay exactly as they were, and matched mentions already in the feed remain there. The paused keywords show up as `autoMutedKeywords`.

Tracking resumes on its own, within a minute of a top-up, once the effective balance covers one day of every keyword, running and paused (`resumeCostCents`).

Two emails go out along the way: one when the effective balance drops to 20 percent of the last top-up (or of the welcome credit before the first top-up), and one when tracking stops. Each is sent once per top-up.

## Keyword capacity

Self-serve workspaces track up to 500 keywords; past that it is an [enterprise](https://mentio.dev/enterprise) conversation with the same rate card and volume discounts.

Creating or unmuting a keyword fails with `402 insufficient_balance` when the effective balance cannot pay for one more keyword-day (add funds and retry), or with `402 keyword_limit_reached` at the 500-keyword ceiling. The check rides in the write itself, so concurrent requests cannot race past either limit. See [Errors](/errors).

## Capping a keyword's mentions

The keyword charge is fixed; the mentions are not. When you resell tracking at a fixed price, or simply want spend bounded, give a keyword a cap per day, week or month: `cap: { "mentions": 50, "per": "day" }` on [`POST /v1/keywords`](/api/keywords/create-keyword) or [`PATCH /v1/keywords/{id}`](/api/keywords/update-keyword), `null` to remove it. `per` is `day` (from 00:00 UTC), `week` (from Monday 00:00 UTC) or `month` (the calendar month, the default).

A **daily cap** is the one for a fixed price per customer: mentions keep coming every day, and a keyword never costs more than 30 × the cap × $0.008 a month, plus its $5. A monthly cap on a busy term can be used up in a day and then wait for the 1st.

* The count is `cap.used`: matches recorded in the current period, relevant or not, the look-back a new keyword gets included, because every match bills, plus the [comments](/guides/comments) delivered under the keyword's mentions. A thread that would cross the cap is cut at it. The first matches of the period take the room: the cap cannot pick the most relevant, since a mention is scored after it is stored, so keep a capped keyword tight with required and excluded words.
* At the cap the keyword stops matching and receiving comments. It reads `pausedForCap: true`, lists under `status=capped`, and `cap.resumesAt` says when its next period starts. Every owner gets one email for a monthly cap; a daily or weekly cap is meant to be reached and sends none (the `keyword.capped` webhook still fires). The check rides in the write of each match, so concurrent batches cannot race past it; a cap set in the last two minutes may let a few through while the pipeline's snapshot catches up.
* It resumes on its own when its next period starts (the next day, the next Monday or the first of the month, UTC), or the moment a `PATCH` raises the cap above the count or removes it. Coming back after a day or more, it reads at most a day back, never the time it was capped, exactly like a keyword the wallet resumed.
* It is not a mute: the keyword keeps its place, its cursor and its daily keyword charge. Set `muted: true` to stop that too. A keyword can be muted and capped at once; it then reads as muted.

Mute and cap together give a reseller both controls: the cap bounds each customer's mentions on its own, and a `PATCH` mutes a keyword whose customer did not renew.

## The wallet through the API

Everything the billing page does with money, a program can do too (an integrator reselling tracking runs its own billing on top of it):

| Call                                                                                                                          | What it does                                                                                                                                                           |
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`GET /v1/billing/wallet`](/api/billing/get-wallet)                                                                           | The balance in full, the top-up bounds and the auto-recharge settings (the fields below)                                                                               |
| [`GET /v1/billing/ledger`](/api/billing/list-ledger)                                                                          | Every movement of the balance, newest first, cursor paged                                                                                                              |
| [`POST /v1/billing/top-ups`](/api/billing/create-top-up)                                                                      | A hosted checkout URL with `amountCents` prefilled. Nothing is charged by the call; the balance is credited when the payment lands and paused tracking resumes at once |
| [`GET /v1/billing/invoices`](/api/billing/list-invoices), [`GET /v1/billing/invoices/{id}/url`](/api/billing/get-invoice-url) | The receipts and a short-lived link to each PDF                                                                                                                        |

A `read` key can read all of it; a top-up checkout needs a `write` key. Auto recharge is the one money setting that stays behind a login: it charges a saved card with nobody present, so only a signed-in owner can change it, from the billing page or an OAuth sign-in, never with an API key (the wallet reports its settings under `autoRecharge`). Cards and the customer portal stay on the dashboard too. This is where the providers that expose a balance at all draw the line: a readable balance and receipts, a hosted checkout at most, and nothing a key can do that charges a card.

## Checking your balance

```bash
curl -H "Authorization: Bearer $MENTIONS_API_KEY" \
  "$MENTIONS_API_URL/v1/billing/wallet"
```

```json
{
  "balanceCents": 1842,
  "pendingCents": 96,
  "effectiveBalanceCents": 1746,
  "burnPerDayCents": 131,
  "daysLeft": 13,
  "stopped": false,
  "activeKeywords": 5,
  "autoMutedKeywords": 0,
  "nextDayCents": 84,
  "resumeCostCents": 84,
  "signupCredit": { "amountCents": 580, "grantedAt": "2026-08-14T09:12:03.000Z" },
  "lastTopUpAt": "2026-08-29T16:40:11.000Z",
  "billingConfigured": true,
  "minTopUpCents": 2000,
  "maxTopUpCents": 500000,
  "defaultTopUpCents": 2000,
  "currency": "USD"
}
```

| Field                                                 | Meaning                                                                                                                    |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `balanceCents`                                        | Ledger balance: every credit minus every settled debit                                                                     |
| `pendingCents`                                        | Mentions matched since the last daily settlement, priced but not yet debited                                               |
| `effectiveBalanceCents`                               | `balanceCents` minus `pendingCents`: what the stop rule and the keyword gate look at                                       |
| `burnPerDayCents`                                     | Average daily deduction over the last 7 days (or since the workspace was created)                                          |
| `daysLeft`                                            | `effectiveBalanceCents` divided by `burnPerDayCents`; `null` when nothing is burning                                       |
| `stopped`                                             | `true` while tracking is paused for lack of balance                                                                        |
| `activeKeywords`                                      | Unmuted keywords right now                                                                                                 |
| `autoMutedKeywords`                                   | Keywords the wallet paused; a top-up resumes them                                                                          |
| `nextDayCents`                                        | What one more day of the running keywords costs; tracking stops when the effective balance drops under it                  |
| `resumeCostCents`                                     | What one day of every keyword (running and paused) costs; a stopped workspace resumes once the effective balance covers it |
| `signupCredit`                                        | The welcome credit this workspace received (`amountCents`, `grantedAt`), or `null`                                         |
| `lastTopUpAt`                                         | Newest paid top-up; `null` before the first                                                                                |
| `billingConfigured`                                   | `false` on a deployment without Polar credentials; the dashboard hides the top-up button                                   |
| `minTopUpCents`, `maxTopUpCents`, `defaultTopUpCents` | The top-up range (2000 to 500000) and the checkout prefill (2000)                                                          |
| `currency`                                            | Always `USD`                                                                                                               |

## The ledger

Every movement of the balance is a ledger entry, newest first:

```bash
curl -H "Authorization: Bearer $MENTIONS_API_KEY" \
  "$MENTIONS_API_URL/v1/billing/ledger?limit=50"
```

```json
{
  "data": [
    {
      "id": "led_4f1c9a2e7b3d4c8e9a0b1c2d3e4f5a6b",
      "kind": "debit_mentions",
      "amountCents": -96,
      "day": "2026-09-06",
      "units": 1730,
      "note": null,
      "polarOrderId": null,
      "createdAt": "2026-09-07T00:05:12.000Z"
    },
    {
      "id": "led_9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a",
      "kind": "debit_keyword_days",
      "amountCents": -83,
      "day": "2026-09-06",
      "units": 118,
      "note": null,
      "polarOrderId": null,
      "createdAt": "2026-09-07T00:05:12.000Z"
    },
    {
      "id": "led_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
      "kind": "topup",
      "amountCents": 2000,
      "day": null,
      "units": null,
      "note": null,
      "polarOrderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "createdAt": "2026-08-29T16:40:11.000Z"
    },
    {
      "id": "led_0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c",
      "kind": "signup_credit",
      "amountCents": 580,
      "day": null,
      "units": null,
      "note": "Welcome credit",
      "polarOrderId": null,
      "createdAt": "2026-08-14T09:12:03.000Z"
    }
  ],
  "nextCursor": null
}
```

Pass `nextCursor` back as `cursor` to page. Credits are positive, debits negative.

| Kind                 | Meaning                                                                                |
| -------------------- | -------------------------------------------------------------------------------------- |
| `signup_credit`      | The welcome credit, once per user                                                      |
| `topup`              | A paid Polar order; `polarOrderId` is the order                                        |
| `refund`             | A refund Polar issued on a top-up, taken back off the balance                          |
| `debit_keyword_days` | The daily keyword deduction; `units` is the cumulative keyword-days settled so far     |
| `debit_mentions`     | The daily mention deduction; `units` is the cumulative matched mentions settled so far |
| `debit_comments`     | The daily comments deduction; `units` is the cumulative comments settled so far        |
| `adjustment`         | A manual correction by Mentio staff, with a `note`                                     |

Debit rows carry `day` (the last UTC day the row settled) and `units`. Each debit settles the difference between the cumulative charge since the workspace was created and what was already debited, so rounding never drifts: 30 keyword-days are exactly $5.00 (`500` cents) and 100 mentions are exactly $0.80, whatever the day-by-day split.

<Callout title="Billing errors favor you">
  Deductions are computed so that an error can only ever under-count, never over-charge. If a
  deployment has no billing credentials configured, the top-up endpoints return
  `503 billing_not_configured`, `billingConfigured` is `false`, and everything else keeps working
  on the balance the workspace already has.
</Callout>

The billing endpoints are dashboard endpoints: they accept an API key, but they change with the dashboard and are left out of the generated reference. The wallet and ledger fields above are the stable part.
