# 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.

Source: https://tokentriage.com/docs/build/company-billing

---
import Callout from '../../../components/Callout.astro';
import Details from '../../../components/Details.astro';
import NextSteps from '../../../components/NextSteps.astro';

{/* Provenance of every block below marked as program output, so a later editor can
    regenerate rather than guess:

    statement, ledger and the three verify transcripts —
      TT_METER_FIXTURE_OUT=<dir> go test -run 'TestMeterVerify(ShowsAndChecksThePurposeSplit|RefusesAStatementThatBillsATestAsTraffic|StillChecksAStatementSealedBeforeTheSplit)$' ./internal/cli/
      then, in <dir>: tokentriage meter verify --statement <file> --ledger ./cell-acme
    invoice line —
      go test -v -run TestTheBreakdownReachesTheWireAndOnlyWhenItExists ./internal/control/billing/
    The corpus is the seeded test cell, so the money in the invoice block is
    arithmetic over a fixture and is nobody's bill. */}

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](/docs/build/cost-reconciliation). You will get
more from this page if you have already
[created a company](/docs/get-started/first-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.

```json
{
  "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.

<Callout type="key">
The split moves no money. Purpose is a dimension, not a price: a playground request and
an application request are the same billable request at the same rate, counted once. What
changes is that you can now tell them apart.
</Callout>

## 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.

```json
{
  "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.

```console
$ 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.

```console
  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.

```console
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.

<Details summary="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.

</Details>

## 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:

```console
$ 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:

```console
$ 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:

```console
$ 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.

```ts title="TypeScript"
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);
```

```python title="Python"
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.

<NextSteps items={[
  { title: 'Turn on business dimensions', href: '/docs/build/cost-dimensions', description: 'Attribute the same requests to a team, a feature or an end customer.' },
  { title: 'Reconcile against a provider invoice', href: '/docs/build/cost-reconciliation', description: 'The other direction: a provider bills you, and your ledger disagrees.' },
  { title: 'How a dollar gets a name', href: '/docs/build/cost-attribution', description: 'Where a cost figure comes from and what each verdict on it means.' },
  { title: 'Export your data and delete a workspace', href: '/docs/build/export-and-delete', description: 'Why settled spend and statements are kept when a workspace is deleted.' },
]} />
