What an answer costs, and how the ceilings behave
How AI Support spend is measured across both turn tables, what the daily and monthly ceilings do when they are reached, what happens when a call cannot be priced at all, and which settings drive the bill.
Every figure on this page comes from one screen and two tables: the Overview
dashboard at /admin/ai/support, and the ai_support_turn and
ai_support_admin_turn rows behind it. The first records what the assistant
spent answering your customers. The second records what your own administrators
spent asking it questions. Both count against the same ceilings.
The page itself opens on access.ai.support, but every number on it comes from
that route. A role with the page key and not the analytics key gets an empty
dashboard and no explanation — see Permissions.
What Overview reports
| Block | What it says |
|---|---|
| Status line | One sentence: off, no provider, paused, drafting, or answering |
| Spent today | Today's cost, both surfaces, above a count of customer turns |
| Spent this month | The month's cost, both surfaces, above the average per customer answer |
| Handled without a person | Conversations the assistant closed with no human, of those it touched |
| Prompt cache | Share of input tokens served from cache — read this one, see below |
| Budget | Today and this month against their ceilings, or "no cap" |
| Cached tokens / Fresh tokens | Today's split. Cached cost a tenth of fresh |
| What it did (30 days) | Answered, handed to a person, stayed silent, and failed when there are any |
| Ready to answer unsupervised? | Drafts reviewed, sent as written, average rewrite, against the promotion gate. Only while you are in COPILOT |
The masthead rail above them carries the live state — which provider is answering, whether the budget still allows a call, and whether the cache has gone bad.
Every dollar figure on this screen — the two spend tiles and the two budget bars — includes what your own administrators spent: the console, the handbook, "ask about this customer", "teach from this ticket" and the provider connection test. They are all built from the same sum, so the tile and the bar can no longer disagree.
The counts underneath them are customer conversations only. That split is deliberate: folding an operator's own questions into "turns today" would inflate the one number the whole feature is judged on. It does mean spend ÷ turns is not a per-answer cost on a desk that uses the operator assistant — which is why the average per answer on the month tile divides customer cost by customer turns rather than doing that division for you.
How the two windows are measured
Both periods are UTC, with explicit UTC boundaries: the day runs from 00:00 UTC, and the month from the 1st at 00:00 UTC. That is deliberate — a window derived from the server's local timezone resets at a different instant on every install, and the admin screen then disagrees with the ledger for a few hours every month.
Spend for a window is one SQL sum over both surfaces:
SELECT COALESCE(SUM(costUsd), 0)
FROM (
SELECT costUsd FROM ai_support_turn
WHERE createdAt >= :windowStart AND costUsd > 0
UNION ALL
SELECT costUsd FROM ai_support_admin_turn
WHERE createdAt >= :windowStart AND costUsd > 0
) AS spendThere is no status filter. A row carrying a cost is a row somebody was charged for, whatever happened to it afterwards — and that is the whole rule, so a status added later needs no maintenance here.
The model was called and the tokens were billed before anything downstream got to decide the answer was not worth sending. That covers three cases people expect to be free and are not:
- Refused. The groundedness gate withheld a generated answer.
- Failed. The provider errored after it had already produced tokens.
- Cancelled. One of your agents replied while the assistant was generating. Whatever had been produced by the time it stopped was billed — and if the generation had already finished with drafting on barge-in switched off, the finished answer is not even offered to the desk.
An install whose confidence floors are set very high can therefore burn a daily
budget without a single customer receiving an answer. If spend is rising and
Answered is not, that is what is happening — check the escalation reasons on
Overview, then Sources.
Skipped turns contribute nothing, but not because of their status: nothing was
generated, so costUsd is zero and the costUsd > 0 clause never sees them.
The two ceilings
Both are Super-Admin only and both live on the Guardrails tab of
/admin/ai/support/settings. 0 is unlimited on either — a real escape hatch
for an operator running a local model whose per-token cost is genuinely zero,
not a way of saying "unset". Setting both to 0 is also the only way to
switch off the refusal in the next section; one ceiling left standing keeps it
armed.
The shipped $5 a day and $100 a month are deliberately low enough to be safe on an install nobody is watching. They are not production numbers, and leaving them is the single most common cause of the next two sections.
There is no fallback model, no queue, and no partial service. From the moment a ceiling is reached, every conversation escalates to a person until the window rolls over — the daily one at 00:00 UTC, the monthly one on the 1st.
Your team experiences this as the addon spontaneously stopping in the middle of a shift, usually with no idea a limit exists. Set the ceilings where you actually want them, and put the Overview remaining-budget figure in front of whoever runs the desk.
Three things happen the first time a customer's message hits a closed gate, and only the first of them is on a screen:
- The Overview status line reads "Paused: the spend cap has been reached." It says that for every pause, including the unpriced case below, which is the one sentence on the dashboard that can send you looking for the wrong problem.
- A notification goes to the whole desk — everyone holding
view.support.ticket— linking to/admin/ai/support. It is the only signal that names the reason, and it says one of three things: the daily cap was reached, the monthly cap was reached, or spend cannot be measured. Only the first two end with "Raise the cap in Settings to resume"; the third counts the calls that could not be priced and points at the two per-million-token variables in the install guide rather than spelling them out on screen. It is sent once per cap period — the first conversation the cap stops that day, or that month — so the hour after a ceiling trips is not sixty identical alerts to every administrator while the desk is answering everything by hand. - That conversation's session moves to
AI_SUSPENDED, which is per-conversation and does not lift on its own. It is lifted on the next message the customer sends once the budget allows again — and only for the two cap reasons, so a conversation suspended because spend could not be measured stays suspended even after you have fixed the pricing.
Admin-side model calls hit the same gate and return a 429 rather than escalating. Four of the seven admin-side spenders listed at the end of this page say The AI budget for this period has been reached; the provider connection test and gap drafting word it themselves, and gap drafting is the only one that tells you when the reason was the unpriced case rather than a ceiling. The seventh — the retrieval preview — consults no budget at all and so cannot report one.
When spend cannot be measured, the gate refuses
A cap is only a cap if the spend under it is a real number. Two providers can record a turn that moved thousands of tokens and cost, as far as the database is concerned, nothing:
- The compatible endpoint adapter reports no cost by design. It cannot know: the thing behind it may be a local model, a metered API or a flat subscription.
- Any model id not in the price table — typically one typed in by hand — prices to nothing rather than being guessed at.
So the budget gate counts those turns. A turn is unpriceable when it moved
tokens (inputTokens or outputTokens above zero) and recorded no cost;
genuinely free work — a skipped turn, a refusal before the call, a direct answer
from one of your own articles — moves no tokens and is not caught.
If either cap is above 0 and the current month contains even one unpriceable
turn, the gate refuses everything, with the reason unpriced_model. The
assistant escalates every conversation and the admin routes return 429, exactly
as if a ceiling had been reached. That is deliberate: the alternative is
reporting $0.00 every day while the real bill grows at the provider, under a
ceiling that was never going to fire.
There are two ways out, and both are decisions only you can make.
Price the endpoint. Set both of these in .env to what your endpoint
actually charges, in USD per million tokens, and restart the backend:
AI_SUPPORT_OPENAI_INPUT_USD_PER_MTOK=0.60
AI_SUPPORT_OPENAI_OUTPUT_USD_PER_MTOK=2.40Both are required and both must be above zero — half a rate card is worse than
none, and a rate of 0 would be indistinguishable from "unknown" all over
again. Cached input is billed at the full input rate here, because these
endpoints do not agree on whether a cached prefix is discounted and
over-counting is the safe direction for a ceiling.
Or set both caps to 0. That has always meant "no ceiling", and it is the honest setting for an install running a local model that really is free.
The count is over the current UTC month and old rows are not re-priced. Turns already written without a cost keep counting, so an install that has been running unpriced for a fortnight stays refused until the 1st, even after the rate card is in place.
If you need the assistant back today, set both caps to 0, let the month roll
over, and put your real ceilings back then — with the rate card already
configured, they will be enforced against a real number from the first turn.
What drives the bill, in order
1. The answering model. aiSupportModel, on the Model tab. It ships as
MashDiv AI Core, and on an install running MashDiv AI those three tiers are
the whole vocabulary — the tier is a promise about the job, not a pinned model,
so what serves it can improve without you editing a setting.
| Tier | Input | Output |
|---|---|---|
| MashDiv AI Lite | $1.80 | $9 |
| MashDiv AI Core | $3.60 | $18 |
| MashDiv AI Max | $9 | $45 |
Those are the gateway's sell side, so the local budget gate and the figure on your wallet statement agree. Core is currently on an introductory rate and returns to $5.40 / $27 on 1 September 2026; the schedule is in the price table, so the gate switches over on the day with nothing for you to change.
An install running its own provider key picks from a different list, priced at the vendor's own rates:
| Model | Input | Output |
|---|---|---|
claude-haiku-4-5 |
$1 | $5 |
claude-sonnet-5 |
$2 | $10 |
claude-opus-5 |
$5 | $25 |
claude-sonnet-5 carries a dated change of its own — $3 / $15 from 1 September
2026 — read from the same schedule, on the same UTC day boundary as the caps.
Which model serves a MashDiv tier is a routing decision made at the gateway, and
nothing on your install asserts one.
A model outside both lists prices to nothing. The agent editor refuses one with
a 400, but aiSupportModel written through the settings endpoint is not checked
against the list, so a typo there is accepted, answers normally, and records
$0.00 a turn — which is what the refusal in the previous section exists to
catch. If the assistant stops the day after somebody edited the model, look
there first.
2. The two internal models. aiSupportRerankModel and
aiSupportClassifierModel both ship as MashDiv AI Lite, the cheapest tier.
Reranking runs on every question that retrieves anything and classification runs
on every message: they are high-volume and structurally simple, and pointing
either at a larger model is the easiest way to multiply your bill for no
measurable gain. Both controls appear on the Model tab only on a
self-managed install — on MashDiv AI the gateway routes those calls down to
the cheap tier itself, so there is nothing to choose.
3. Reasoning effort and answer length. aiSupportEffort defaults to low
and aiSupportMaxTokens to 4000. Thinking tokens are billed and count against
the ceiling on length, so effort is a real cost lever rather than a quality
dial — a two-sentence answer measured on a live install at high effort took 42
seconds, 1,026 output tokens and $0.25. On MashDiv AI each tier has its own
effort ceiling and a setting above it is served at the highest the tier allows;
on a self-managed install the adapter strips the parameter for models that
reject it — claude-haiku-4-5 among them — so raising it there changes nothing
but your expectations.
4. Prompt cache reads. A cache read costs a tenth of a fresh input token; a cache write costs 1.25× for the five-minute lifetime, or 2× for the one-hour one. When the cache is working, most of your input is being billed at a tenth.
Prompt-cache health
The Prompt cache tile is not a setting — it is a measurement. The ratio is
cacheRead ÷ (cacheRead + input) over the last 24 hours of successful customer
turns; unlike the money, it does not read the admin table.
Below 0.5 it is called unhealthy. Fewer than ten turns in the window is not
enough evidence either way, so a quiet install does not alarm.
When something changes the top of the prompt on every request — one unsorted map, one interpolated timestamp above a cache breakpoint — the prefix stops being reused and every answer silently pays full price. The bill multiplies several times over and nothing else on the platform will tell you. Watching this ratio fall is the only detection mechanism that exists.
Two things watch it for you:
- The Prompt cache tile and its alert banner on Overview.
- An hourly job,
aiSupportCacheHealthCheck, described in Scheduled jobs. When the ratio is unhealthy over at least ten turns it writes a warning to the backend log and raises an admin notification — "AI support is costing more than it should" — to everyone holdingview.support.ticket, linking to/admin/ai/support.
It runs hourly rather than daily precisely because a day of an invalidated cache is a day of multiplied bills.
The two levers that cost nothing
Neither calls a model, so neither can spend a cent. Deflection is a search over articles you wrote and published, offered while the customer is still typing their subject line, with the file-anyway button never hidden. A direct answer arrives in about half a second, and only on the opening question of a conversation written in English — a verbatim English article is a worse answer, not a faster one, for a customer who wrote in Spanish. In COPILOT it is filed as a draft like everything else, because an install where a person reads every reply does not stop being one just because this reply is your own prose.
Direct answers ship off because they change the voice — the customer reads your prose rather than the assistant's persona — not because they are expensive. On an install with a real knowledge base they are the largest reduction in cost per ticket available, and they are the reason chasing coverage on Gaps pays for itself.
aiSupportDeflectEnabled is on the Retrieval tab of
/admin/ai/support/settings, where you would expect it.
aiSupportDirectAnswerEnabled is live in the backend and accepted by the
settings endpoint, but no screen in the product renders it — there is
nothing to click.
Until there is, the only way to switch it on is to write the key directly. The
settings endpoint takes only the keys you are changing, so a body carrying
aiSupportDirectAnswerEnabled set to true is a complete request:
The admin-side spenders
Seven things in the admin area call a model. Five of them now record what they spent, so the ceiling is computed from the whole bill rather than from your customers' half of it.
| Where | Route | Permission | Checks the cap | Recorded |
|---|---|---|---|---|
| The operator assistant, in the console and on Handbook | POST /api/admin/ai/support/console |
access.ai.support |
Yes | Yes |
| Handbook questions, when the assistant is off | POST /api/admin/ai/support/handbook |
view.ai.support.handbook |
Yes | Yes |
Ask about this user, on /admin/crm/user/{id} |
POST /api/admin/ai/support/ask |
view.user |
Yes | Yes |
| Teach from this ticket, in the Live Inbox | POST /api/admin/ai/support/article/from-ticket |
create.ai.support.knowledge |
Yes | Yes |
| Test connection, on the Provider screen | POST /api/admin/ai/support/provider/test |
access.ai.support.provider |
Yes | Yes |
| Draft articles from open gaps | POST /api/admin/ai/support/gap/draft |
create.ai.support.knowledge |
Yes | No |
| Test what it would find, the retrieval preview on Sources | GET /api/admin/ai/support/knowledge/search |
view.ai.support.knowledge |
No | No |
The Handbook screen is two routes wearing one face: it fires the console route when the operator assistant is switched on, and the documentation-only handbook route when it is not. Both are billed and both are recorded, but only the first keeps a conversation you can reopen.
All but the last check the budget before spending and refuse with a 429 when it is reached. The operator assistant books its cost onto the conversation you were having, so you can read it turn by turn. The other four have no conversation to book against, so each files its turn under a per-administrator, per-surface ledger row — "Handbook questions — 41 turns, $3.12" — which is what makes their spend readable rather than merely counted. Those ledger rows are listed apart from real conversations in the console's history, and deleting a conversation clears its text without giving its cost back. See Your own assistant.
Generating a draft from the Live Inbox spends too, but it runs the same engine a customer's message runs and writes an ordinary customer turn — so it is already in both the spend tiles and the turn counts, and it is not in the table above.
gap/draft calls a model twice for every gap — the re-ranker inside
retrieval, then the drafting call itself — and writes no turn row, so its spend
is invisible to both ceilings and cannot be accounted for retroactively. It
carries its own control instead: it reads the remaining daily allowance once,
keeps a running total of both calls as it works, and stops the batch when that
total is exhausted — because re-reading a budget its own calls do not move would
return the same number twenty-five times.
Its response is therefore the only place its cost is ever stated. Read the
Cost $… in the message it returns; nothing on Overview will show it to you.
Test what it would find on the Sources screen says under its own box that it
"costs nothing". That is true only of the answering model. It still calls the
re-ranker, which is a model — retrieval reranks whenever the first pass finds
more candidates than aiSupportTopK, which on any real corpus is every query. So
the default path bills a small generation per search.
Two things sharpen that. Its rerank parameter overrides
aiSupportRerankEnabled rather than being narrowed by it, so it reranks even on
an install that has switched reranking off everywhere else. And it is the one
model-calling admin route with no ceiling in front of it: no budget check, no
turn row, no line on Overview.
Add ?rerank=false to get the BM25 ordering instead. The response is the same
shape, reranked comes back false, and it genuinely costs nothing. See
the API reference.
Two more worth knowing before you hand out the permissions:
- Teach from this ticket is the most expensive single call an admin can make
in this addon: the answering model over a whole transcript at 1,500 output
tokens, reachable by anyone with
create.ai.support.knowledge, on as many tickets as they care to click. - Test connection is not a reachability ping. It runs the whole path a customer's question takes, ending in a real generation, and bills a fraction of a cent for it — on the one screen an operator opens repeatedly because the assistant has gone quiet.
Where to look when the bill moves
- Prompt cache on Overview. If it has fallen, nothing else matters until it is fixed.
- The average per answer under the month tile. A jump here with flat volume means a model, an effort level or a max-tokens change.
- Spent this month against that average × the turn count. The gap is what your own administrators spent. If it is large, the console is your bill.
- What it did (30 days). Spend rising while
Answeredis flat means you are paying for refusals. - The Model tab, for the two internal models. They are the change nobody remembers making.
More failure shapes are in Troubleshooting.