What your company is billed for
Read your invoice's usage evidence, understand when requests have a purpose breakdown, change your plan, and recover a failed payment.
On this page
Your August bill says 100,000 requests. Some of those were your product serving its users. Some were you clicking Run in the playground while you tried a routing change, and some were an LLM judge grading last week’s eval.
Finance asks how much of the line was the product and how much was engineering testing it. When the signed usage statement includes a purpose breakdown, the invoice carries that split, and you can check it against your own decision ledger.
Some statements carry only a quantity. Externally attested usage and central meters measured at day close can use this path, so a current invoice may have no purpose breakdown. Missing purpose lines do not mean zero tests or all application traffic. Read the statement’s actual evidence before drawing either conclusion.
Purpose changes reporting, not the request rate: a billable application request and a billable test request count once at the same platform rate. Provider inference spend is separate.
This page is about the invoice TokenTriage sends your company. Matching a provider’s bill against your ledger is a different task: see Reconcile against a provider invoice. You will get more from this page if you have already created a company and sent traffic through a gateway.
The artifact behind the line
An invoice line is a number. A usage statement is the evidence for it, and it is what you can check.
One statement covers one tenant, one meter and one period. It carries the quantity and
the provenance of the measurement. A statement folded from a decision ledger records
its ledger span and can include one line per activity purpose, as in the example below.
A quantity-only statement has no such lines. The hash at the top is taken over the
bytes below it, so a statement whose lines were edited is a different statement at a
different address, and the signature stops matching.
{
"hash": "b2dba4073ec0693dd315e38a26e8e0758fc53beac084cffd021d8ef850f954a7",
"sig": {
"alg": "ed25519",
"kid": "tts-dev-2026",
"sig": "JQAKO2s_PvYAIu84x5Xw5CWXn_cz-QtSD6thidzQ3_XABhqxRlyaynZWim1JfFBLM6bmWHTqompbzT1nxa87Bw"
},
"statement": {
"v": "TTUS1",
"tenant_id": "acme",
"meter_id": "logical_requests",
"period": { "start": "2026-07-15T00:00:00.000Z", "end": "2026-07-16T00:00:00.000Z" },
"quantity": 15,
"lines": [
{ "purpose": "application", "quantity": 12 },
{ "purpose": "playground", "quantity": 3 }
]
}
}
That is an excerpt of a statement a test cell issued, with its provenance and
ledger_ref blocks left out to keep the shape readable; the commands that produced it
are in a comment at the top of this page’s source. Fifteen billable requests, twelve of
them your application’s, three of them playground runs. The lines add up to the
quantity, and nothing in the system will seal a statement where they do not.
The purposes a request can be admitted under
A purpose is what a request claims to be for. It is checked against the grant on the provider connection the request uses, so a connection a workspace manager may only validate cannot quietly start serving production traffic.
The set is closed. These five are what you will see, plus one word that is not a purpose at all.
| On the statement | What produced it |
|---|---|
application |
your application’s own traffic through a gateway |
playground |
a request you ran from the playground |
comparison |
a request run to compare candidates side by side |
judge |
a request an LLM judge made while grading |
connection_validation |
the bounded check that a provider connection works |
unattributed |
not a purpose: the part of the quantity the builder could not place |
Two rules follow from that last row. A request whose ledger row names no purpose lands on
application, because that is what a saved-mode gateway request is. A quantity nobody
could attribute lands on unattributed instead, where you can see it and ask about it.
A paid test on the invoice
When the selected statements include a purpose breakdown, the metered line carries it
beside its quantity, under the key purposes. A quantity-only selection has no purpose
breakdown to carry. The invoice uses its own word for a count, qty, because an invoice
and a statement are read by different people.
{
"kind": "usage",
"description": "logical_requests — 100000 of 250000 included (pro@1, 2026-08-01 → 2026-09-01)",
"nano_usd": 0,
"money": { "usd": 0, "estimated": false, "unpriced": false },
"qty": 100000,
"meter_id": "logical_requests",
"statement_refs": [
"97dd341ec7e7db3db40b2361af9079c2f49f95cc8123e6d452186edfd8a9562c"
],
"purposes": [
{ "purpose": "application", "qty": 90000 },
{ "purpose": "playground", "qty": 10000 }
]
}
That block is one line of an invoice rated in a test run over a seeded cell, so the
figures are arithmetic over a fixture rather than anyone’s bill. Ten percent of the
month’s requests were playground runs. statement_refs names the statements the line
was rated from, and those are the addresses you verify.
The overage line beside it names the same statements and carries no purposes key.
Splitting an overage would mean deciding whether your plan’s included requests were spent
on your application or on your tests, and there is no rule that says which. The metered
split sits on the metered line.
Where you read it in the portal
Open Account → Invoices, then the month you want: /account/invoices/2026-08. Each
line on the sealed invoice shows its kind, its amount, and how it was priced. When a
usage line has a breakdown, What those requests were for lists each purpose beside
its count in the same words the statement uses.
The screen presents the server’s integers and computes nothing: no percentages, no remainder worked out in the browser. If a month’s statements carried no breakdown, the field says so in place of a table rather than showing an empty one.
Checking the split against your own rows
tokentriage meter verify folds the billable-request predicate over a ledger directory
you hold and compares the result with the statement. It needs no network, no database,
no index and no key, and it is free at every tier: checking our bill cannot be gated.
For a statement that includes a breakdown, the command re-derives the purpose lines from the same rows as the quantity and prints them as Check 1b.
$ tokentriage meter verify --statement statement.json --ledger ./cell-acme
statement: statement.json
ledger: ./cell-acme
address: b2dba4073ec0693dd315e38a26e8e0758fc53beac084cffd021d8ef850f954a7
covers: tenant acme, meter logical_requests, [2026-07-15T00:00:00.000Z, 2026-07-16T00:00:00.000Z)
signature: UNKNOWN_KID
this build holds no statement-signing keys, so WHO issued this statement is not checked here; pass --trust-key tts-dev-2026=<public key> to check it. The two ledger checks below do not need a key
VERDICT: MATCH
This bill reproduces from your ledger. Every member the statement names is
byte-identical, the row count, the first and last request and the span chain
all agree, and a fresh fold of your own rows yields the same billable count.
Check 1 — quantity
statement claims 15
fresh fold of your rows 15
Check 1b — what those requests were admitted for
purpose statement your ledger
application 12 12
playground 3 3
unattributed 0 (the statement's builder could not place these; no fold can reproduce them)
That capture is the first third of the report, from a build with no signing keys
embedded, which is why the signature line reads UNKNOWN_KID rather than naming a cell.
The rest of the report covers the ledger span, every row in the period in its class, and
the provenance the statement discloses. Exit status is 0 when the bill reproduces and
non-zero when it does not, so this runs in a script.
What can go wrong
Three outcomes are worth recognising before you meet them.
The total reproduces and the split does not. This is the case the check exists for, and it reads as a mismatch even though Check 1 agreed. Both halves of the mistake are named, so you know which line to argue about.
Check 1 — quantity
statement claims 15
fresh fold of your rows 15
Check 1b — what those requests were admitted for
purpose statement your ledger
application 15 12
playground (not billed) 3
unattributed 0 (the statement's builder could not place these; no fold can reproduce them)
DIFFERENCE: purpose "application": the statement bills 15 request(s) onto this line, a fresh fold of your rows attributes 12
DIFFERENCE: purpose "playground": a fresh fold of your rows attributes 3 billable request(s) to it, and this statement's breakdown does not name it at all — that quantity is billed under some other purpose
The statement carries no breakdown. This includes older statements, externally attested quantities and central meters measured at day close. They say nothing about purposes and keep verifying on their quantity: an invoice already issued stays reproducible. The report states the absence rather than printing zeros, because “we partitioned this and found no tests” and “nobody partitioned this” are different facts.
VERDICT: MATCH
...
note: purpose breakdown: this statement carries NONE, so the quantity above is all that was checked. It was sealed by a path that partitioned nothing: a builder older than the breakdown itself, an externally attested quantity, or a central meter measured at day close. It is not a claim that every request was application traffic.
That capture is elided at the ...; the omitted part is the same span and provenance
sections the passing report prints, and there is no Check 1b table at all.
A residual sits on unattributed. A non-zero figure there means the builder’s fold
placed fewer requests than the quantity it billed. You are not being charged twice, and
the total is still the total. If your own rows can place them, the verifier says so and
the verdict stays a pass; if the residual is large or persistent, it is worth a support
conversation, because it means the evidence behind one line of your bill is thinner than
the evidence behind the rest.
If the verifier says WRONG DIRECTORY
--ledger defaults to the local decision log, so the most common first-run mistake is
pointing it at a cell that holds no rows for the tenant on the statement. The command
says so in those words and names the tenants the directory does hold, rather than
reporting a failed bill. Point --ledger at that tenant’s cell and run it again.
Changing the plan your company is on
Open Account → Billing (/account/billing). The panel states the plan the workspace
holds, whether it is paid and current, when the term you are in ends, and whether a
payment you have already made is still being applied.
Which control you are offered follows from where you stand, and the screen decides it once rather than per button:
- On a trial, or expired: Upgrade, which opens checkout on this origin at
/account/checkout. - Paid and current, or holding a recorded term: Manage subscription, which opens the payment provider’s own hosted pages.
- Behind on a payment: Update payment method, which is the next section.
A completed checkout and a return from the payment page are records of what you did. Neither of them is this product’s record of what it applied. The plan moves when the provider confirms the payment and we record the effect, so until then the panel reports the payment as applying rather than showing you a plan you do not yet hold. If you have two tabs open and complete both, both are recorded and both apply; the plan you end on is the one whose confirmation landed last, and the plan history behind your invoice carries each change once.
The same facts read without a browser. At the company:
$ tokentriage cloud org get --org org_…
The organization document carries a billing block for a caller who holds billing.read
there: the plan, its version, the commercial standing, whether payments are in test or
production mode, and a link to the page above. It also carries the two facts you would
otherwise have opened the workspace billing panel for — term_end, the instant the
access you have already paid for runs to, and pending_payments, how many payments were
received and are still being applied. Both are absent rather than zero when there is
nothing to report: a company on a trial has bought no window, so nothing ends, and a
count that could not be taken stays absent rather than reading as “nothing is pending”. A developer or an analyst who holds
org.read and no finance action reads the same organization and gets no billing block,
because what the company pays is not part of seeing the company.
The bills themselves, from the CLI
cloud org get says what your company is on. Two further verbs say what it was
charged, and both read at the company rather than at a workspace:
$ tokentriage cloud org invoices --org org_… --json
$ tokentriage cloud org statement --org org_… --period 2026-08 --json
invoices lists every ended month of the company’s payer workspace, each exactly
once, with the status this plane can honestly give it: recorded (the invoice of
record is held here), finalizing (the month ended and its measurement is not
complete), sealed (no record is stored, so what is offered is a projection of the
base subscription) or refused with the reason named. A month is not left out of the
list, because a gap in a list of bills reads as a month that was not billed; and a
month with no amount carries no amount rather than a zero.
statement reads one of those months with its lines. --period is one whole UTC
calendar month, spelled YYYY-MM with a zero-padded month — the spelling the listing
publishes. Any other spelling is refused before the request leaves your machine:
$ tokentriage cloud org statement --org org_… --period 2026-8
Error: --period "2026-8" is not a billing period: a period is one whole UTC calendar month, spelled YYYY-MM with a zero-padded month, such as 2026-08
Both verbs need billing.read at the organization, the same grant that decides whether
cloud org get carries a billing block at all.
Read the same invoice with an SDK
Use a management API key whose current authority includes billing.read at the
company. These reads do not change the plan or payment method. A runtime credential
for gateway traffic does not grant finance access.
const index = (await client.invoices.list(org)).data;
const invoice = (await client.invoices.get(org, "2026-08")).data;
if (!index || !invoice) throw new Error("invoice data was absent");
console.log(index.months, invoice.month, invoice.lines, invoice.disclosures);
index = client.invoices.list(org)["data"]
invoice = client.invoices.get(org, "2026-08")["data"]
print(index["months"], invoice["month"], invoice["lines"], invoice["disclosures"])
The examples also read the organization’s billing.manage_url. This links to
the authenticated billing screen; opening it does not change the subscription or
apply a payment. Provider payment controls remain in that browser flow.
The complete read-only programs are sdk/ts/examples/read_invoices.ts and
sdk/python/examples/read_invoices.py. They take TOKENTRIAGE_BASE_URL,
TOKENTRIAGE_MANAGEMENT_KEY, TOKENTRIAGE_ORG and TOKENTRIAGE_PERIOD from the
environment. Use a period returned by the index; an unfinished month is not an invoice.
Keep the producer’s status and disclosures beside the amount. recorded means the
invoice exists, not that it is paid: its stored state can still be draft. sealed
without a record is a projection. A refused or finalizing month may have no amount;
keep that absence instead of replacing it with zero. Flat subscription lines have no
request quantity. These are TokenTriage charges, separate from provider inference spend.
These programs and the CLI are exercised against the actual production invoice factory, a PostgreSQL invoice created by the canonical producer, and management-key authentication. The local invoice is a fixture, not a customer charge or live renewal.
At the workspace, GET /portal/tenants/{tenant_id}/upgrade is the plan you hold and the
published ladder, and GET /portal/tenants/{tenant_id}/billing-state is the lifecycle and
commercial standing. Both are reads.
No TokenTriage surface asks you to type card details, and the CLI does not pretend to. The checkout and the billing portal are the payment provider’s own pages; what this product hands back is the address of one.
If a payment does not clear
Your workspace is marked past due and keeps serving traffic for a disclosed grace
period. Nothing is switched off during it. /account/billing prints the exact instant the
first suspension could happen, and that instant is the one the control plane refuses
against, not an estimate: it is the moment your workspace entered past due plus the grace
your company is on.
To recover, open Account → Billing and choose Update payment method. That posts to
POST /portal/tenants/{tenant_id}/billing-portal-session and returns a provider-hosted
URL where you replace the card. Two things about that door are worth knowing before you
meet them:
- It needs a workspace owner. A member who can read the page cannot open it.
- A workspace still on its trial that has never checked out has no billing identity at the
provider yet, so the request is refused with
precondition_failedand the screen offers you checkout instead.
When the replacement payment clears, the provider tells us, and both halves move together: the lifecycle state returns to active and the commercial standing returns to paid. The suspension date disappears from your billing page, because the state it was computed from is gone. A workspace that recovered inside its grace was never suspended, so it accrues none of the retention obligations a suspension carries, and the page says nothing about the incident afterwards.
In the window between your payment arriving and the effect landing, the page tells you both things at once: the money is acknowledged as applying, and the standing, the state and the suspension date are all still what they were. That is deliberate. A page that called you current the instant money arrived would be telling you that you were safe while the clock it had printed for you was still running.
What this does not cover
The breakdown answers what a request was admitted for. It does not say which team, feature or customer sent it: that is business dimensions, which you turn on separately and which carry their own cost attribution.
It also does not reach the storage and archive meters. Those bill bytes and rows rather
than requests, and a purpose column over them would describe a population the statement
is not about, so their statements carry no lines and meter verify says as much.