Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

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.

ExplanationLast updated

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_failed and 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.