# Agent-usage ledger

The ledger is the record you use to charge your own customers for agent use, or to talk to them about it. For every Site, customer account, user and UTC day it counts the completed-work units your backend reports ([Usage metering](/docs/usage-metering)) in four classes, and keeps the evidence behind every unit. Telltale reports units; it never charges anyone.

| Class (dashboard) | API and CSV name | What it means | Chargeable? |
| --- | --- | --- | --- |
| Chargeable agent | `billable` (`billableUnits`) | An AI agent did the work, with high confidence: verified identity (a verified operator signature), an automation trace (WebDriver, an automation framework or a headless browser), injected input, sustained behaviour, a behaviour change (a handover to software input), two or more independent behaviour families agreeing with no physical-device input, or Claude's cursor driving the clicks. The same list as [what counts as chargeable](/docs/usage-metering#what-counts-as-chargeable). | Yes, if you charge for it |
| Medium-confidence agent | `behaviour` (`behaviourUnits`) | An AI agent reading on one behaviour pattern alone (medium confidence). Shown as AI agent activity, held for review at protected actions by default, never charged. | No |
| Human | `human` (`humanUnits`) | The session showed varied trusted input from a person. | No |
| Unknown | `unknown` (`unknownUnits`) | Not enough evidence either way. Unknown is not human. | No |

The dashboard counts **units**: one report to `POST /api/v1/usage` carries `units` (1 or more), so the entries CSV can have fewer rows than the units it adds up to. The CSV downloads keep the API's names above; `routeCalls` and `routeBillableCalls` are the calls through your MCP server and API and the chargeable ones.

A unit is counted once, when it is reported. The ledger only counts units you report with `POST /api/v1/usage`; agent minutes and tool runs stay in [agent usage](/docs/usage-metering).

[Approved-route calls](/docs/approved-route) are counted too, as their own unit: `routeCalls` and `routeBillableCalls` on each day and account, and a `route_call` entry for each call. They are never added to the four classes, never sent to Stripe meters with the work units and never counted by account alerts, so a tool call that also reports completed work is not counted twice. A route call names an account only when your server named it (identify, an agent token, or the account your MCP server's or your API middleware's identify returned).

Route calls come from three channels, and each day and account also counts them per channel (`routeChannels` in the totals, for example `{"browser": 12, "mcp": 340}`): **browser** (reviewed tools in your pages), **mcp** (tool calls through your own MCP server, from [Telltale for MCP servers](/docs/mcp)) and **api** (calls to your API: verified agent tokens, and calls with agent evidence reported by [Telltale for APIs](/docs/api-channel)). Only channels with calls are listed. An MCP call is chargeable when your server named its account and the call ran (a call your rule refused is counted, never chargeable); An API call from the middleware is a route call only when it carries agent evidence (Unknown calls are route counts, never ledger entries), and it is chargeable when your server or an agent token named its account, the evidence is high confidence (Verified by token, Verified or Published IP range; never Declared), the call ran and it was not sampled. Each entry's `evidence.channel` says which channel it came from. A person counts once across channels toward Telltale's own plans ([Pricing](/docs/pricing)).

## Evidence for every unit

Each unit has a ledger entry with:

- `usageId`: the unit (the same ID `POST /api/v1/usage` returned), and `idempotencyKey` (`tt_<entry id>`) for your billing system;
- `evidence.sessionIds`: the session the work came from (sessions with AI agent activity are kept for your plan's history period, see [data location and retention](/docs/data-retention));
- `evidence.verdict` and `evidence.basis`: the verdict and detector version when the work was reported;
- `evidence.evidenceClass`: `verified-identity`, `automation-trace`, `behaviour-only`, `evidence-unavailable` (the session was gone before the unit could be checked: never charged), `human` or `unknown`;
- `evidence.howWeKnow`: how strongly the agent is named (`Verified`, `Unconfirmed`, `Self-reported` or `Not named`) and `evidence.controller`, the agent's name when it is named. A name identifies a tool or operator, not an AI model;
- `evidence.signals`: the strong automation signals seen (for example `webdriver`).

When a customer disputes a charge, the entry is your answer: which session, what we saw, and how we know.

## Corrections, adjustments and erasure

The ledger is append-only. Nothing is rewritten in place:

- When a unit's facts change after it was counted, or you move a unit to another class after a dispute (Charge › Ledger › an entry › Change class, or `POST /api/admin/ledger-correction`), the ledger adds an **adjustment** entry that moves the units from the old class to the new one. It is counted on the day it is made, so a period you already charged for never changes. Your own correction always wins over later evidence.
- Erasing a user or account (Settings › Data & privacy) removes their account ID, user ID and session IDs from every entry and day count. The counts stay, on an erased row of the same day, so totals do not change.

## Retention

Day counts are kept 400 days. Entries, with their evidence, are kept as long as the Site keeps usage (Settings › Data & privacy, 90 days by default, up to 400).

## Plans

The ledger and the evidence behind every unit are on every plan. On Telltale's hosted plans, the CSV downloads are part of Starter, Growth and Enterprise (CSV exports), and the Stripe meter-events downloads, sending to Stripe and account alerts are part of Growth and Enterprise. Self-hosted Telltale includes everything.

## Read the ledger

```sh
curl -sS "https://app.trytelltale.dev/api/v1/usage/ledger?from=2026-10-01&to=2026-11-01&view=accounts" \
  -H "Authorization: Bearer $TELLTALE_SERVER_KEY"
```

- `view=days` (default): one row per day, account and user with `billableUnits`, `behaviourUnits`, `humanUnits`, `unknownUnits` and `adjustments`.
- `view=accounts`: totals per account for the period, with the number of users and the first and last day.
- `view=entries`: the entries with their evidence, oldest first. Add `usageId` for one unit.
- `from` and `to` are Unix epoch milliseconds or ISO 8601 dates. The period is half-open, [from, to); without them it is the last 30 UTC days. `accountId` and `userId` filter. `limit` (1 to 1,000, default 200) and `page.nextCursor` page through the rows; a cursor belongs to its view, period and filters.
- `totals` holds the four classes for the period (days and accounts views). `coverage` says from when counts and entries are still kept.

For billing, read closed days (a `to` at a UTC midnight in the past) and use each entry's `idempotencyKey` so nothing is charged twice.

Charge › Ledger shows the same and downloads CSV (`GET /api/admin/ledger.csv?site=SITE_ID&view=days|accounts|entries`), with the route calls in their own `routeCalls` and `routeBillableCalls` columns (and `routeCallId` and `channel` on `route_call` entries).

## Stripe meters

Map your customer accounts to your own Stripe customer IDs (Charge › Ledger › Stripe meters). Then either:

- **Download the meter-events file** (CSV or JSON) for a period and send it with your own tooling. Each chargeable entry is one meter event: `identifier` (`tt_<entry id>`), `event_name` (your meter's event name, `agent_units` by default), `timestamp`, `stripe_customer_id` and `value`. Rows marked `credit` take chargeable units away (a correction): issue those as credits in Stripe.
- **Let Telltale send them.** Paste a [restricted key](https://docs.stripe.com/keys#limit-access) with write access to meter events, and turn on sending. Telltale then sends new chargeable units every five minutes (or now, with Send to Stripe now). The `identifier` doubles as Stripe's idempotency key, so a retry never charges twice. A unit reversed within 23 hours of being sent is cancelled with a meter event adjustment; after that it is listed as a credit to issue by hand. Stripe only accepts events from the last 35 days. Sending stays off until you turn it on, and the key is stored encrypted and shown only as a hint.

Units of accounts without a mapping are not sent; they are sent once you map the account (within the 35 days).

## Account alerts to sales and customer success

Turn on account alerts in Charge › Ledger › Account alerts to hear when an account **starts using agents**, or when its agent units in a calendar month reach a threshold you set (up to five). Choose what counts: chargeable agent units (default) or every agent unit, behaviour-only included.

- **Slack**: paste a Slack [incoming webhook](https://api.slack.com/messaging/webhooks) URL. It must be HTTPS and resolve to a public address, the same rules as webhook endpoints. Send test sends one message now. The URL is stored encrypted.
- **Webhooks**: `account.agent_usage_started` and `account.agent_usage_threshold` go to your webhook endpoints, signed like every event ([Webhooks](/docs/webhooks)).

### Recipe: HubSpot or Salesforce

There is no built-in CRM connection yet. Point a webhook endpoint (filtered to the two account events) at a small handler of your own, or at your automation tool, and update the company record:

```javascript
// After verifyWebhook() (see Webhooks), for account.agent_usage_started and account.agent_usage_threshold:
const { type, data } = event;
await crm.updateCompany(lookupCompany(data.accountId), {
  telltale_agent_use: type === 'account.agent_usage_started' ? 'started' : `over ${data.threshold} units in ${data.month}`,
  telltale_agent_name: data.controller ? `${data.controller} (${data.howWeKnow.label})` : 'Not named',
});
```

Show the agent name with how we know it, never on its own. See the [Agent seat playbook](/docs/agent-seat-playbook) for what to say to the customer.
