Skip to content
Usage

What your agents spent, per session and per project — in tokens.

A team knows what its cloud costs. What its coding agents cost, per project, is usually a number nobody has: the counts sit on each developer's machine, in whatever tool reads that machine's transcripts, and never roll up. Here a session's consumption is a row against the build session that spent it — five token classes kept apart, the turns, the models, the window — reported by the client at the end of the session and keyed on the transcript, so a retry overwrites instead of double-counting. Then it rolls up by project, day, week or month. The numbers are self-reported, and they are token counts, not dollars.

record_build_session_usage

build session · transcript · tokens · turns
A build session A bracket from start to end with a usage bar beneath, filled to about two thirds. start end

input · cache write 5m · cache write 1h · cache read · output · illustrative

How it works

Report at the end. Roll up whenever.

The two verbs. Each is an MCP tool and a REST route; each tool links to its row in the catalogue.

  1. Report

    At the end of a session the client sends what that transcript consumed: five token classes counted separately, the assistant turns and the sub-agent turns among them, the model ids it saw and the window it covers. The call is keyed on the build session and the transcript id, so sending it twice overwrites one row rather than adding a second. Nothing is measured on the server — every number in the row is the one the client reported.

    record_build_session_usage
  2. Roll up

    Ask for the workspace total, or a row per project, day, week or month, inside an optional project filter and an optional date window. Each bucket sums the five token classes, the turns and the sub-agent turns, and says how many reports it is made of. The per-project rollup follows each session’s current project, so re-homing a session moves its history with it.

    get_usage
Usage per build session, in token counts Three session brackets stacked, each with a start and end label and a usage bar beneath sized to that session's token count; the counts are shown as numbers at the right. start end session · feat/lease-sweep 84.2k tokens start end session · fix/typed-params 31.7k tokens start end session · docs/site-plan 118.9k tokens record_build_session_usage · token counts, not dollars · read in the dashboard's Usage view
Three build sessions, each with the tokens it consumed beneath it. The bar is the session's share of the largest — the figure is illustrative; the numbers on your dashboard are the ones your client reported.

The problem is that the number does not add up to anything. Each developer's machine can total the tokens its own agent used — and that total stops at the machine. Nobody can say what a project cost this month, which repository is expensive, or whether the agent that reviewed everything used more than the one that wrote it, because the counts were never attributed to a shared object in the first place. The other shape is a row: a session's consumption recorded against the build session that spent it, in a workspace both the agent and the team can read, keyed so a retry cannot inflate it, and summed by the project the session belongs to.

In the session protocol this is the last step: after the agent has recorded its decisions, filed what it did not finish into the backlog and closed the session, one record_build_session_usage says what the session consumed. A closed session still accepts it — the report arrives after the close by design, and often from a process that only runs at exit.

Self-reported, in tokens

Two things this page will not soften

Both are limits of the product, not of the copy. Read them before the feature list, because they decide whether the feature is the one you want.

Self-reported

Every token, turn, model and timestamp in a usage row is copied from the request. PostMQ does not connect to your model provider, does not sit in the request path and does not observe your agent, so it can neither confirm a report nor notice a missing one. The numbers are as good as the client that sends them — no better, and no worse.

source:the client that calls the tool

Token counts, not dollars

Money needs a per-model price table with rates that move, and the product does not own one. So it stores counts and prints no currency: no price, no spend figure, no estimate. Read the rollup and price it against your provider's rates yourself — and label what you compute an estimate.

stored:five token classes + turns

Awareness, not enforcement

No sweep, limiter, gate or plan check reads the usage table. There is no budget to set, no threshold, no alert and nothing that stops work when a number gets large. You can see what a project cost after the fact; you cannot cap it while it runs.

readers:the rollup, and nothing else

What is left after those is worth having, and it is precise. A report carries the build session and a transcript id of 1–128 characters — together the idempotency key — then five token classes as separate non-negative counts, the assistant turns with the sub-agent turns among them (never more than the total), up to 32 model ids, each 128 characters or fewer, an agent name of 1–64 characters defaulting to claude-code, a reporter version of 64 characters or fewer, and the window the report covers. Every one of those bounds is enforced twice: once by the service, as a plain malformed-request error, and once by a database CHECK constraint, so a value the service ever stopped checking still cannot land.

The window's end doubles as the report's place in line. A client that reports once per turn can have a report that read turn N stall on the network and arrive after one that read turn N+1, which would leave the row holding the earlier, smaller snapshot — and no amount of client-side care fixes that, because a send whose reply is lost is a write the client never learns about. So the overwrite is conditional: a report whose window_end is older than the stored row's is discarded, and the response says so with applied false and returns the row as it stands. Sending an equal one is how a corrected re-read of the same window lands; omitting it altogether writes unconditionally.

The reporter

A session's tokens, next to its decisions

How the report gets sent — mostly without anyone remembering to send it, and on one client because the end-session skill does — the reporter we run ourselves, described closely enough to reproduce.

postmq-usage-reporter is a SessionEnd hook: one Python file, no dependencies. It reads either transcript format the kit's clients write and reports which of them produced the numbers. When a session ends it reads that session's transcript and the sub-agent transcripts beneath it, sums the five token classes, counts the turns and the sub-agent turns among them, collects the model ids and the first and last timestamps, and sends the lot with record_build_session_usage — or POST /v1/usage; one function builds one payload for both. It needs a credential with the write_session_state scope, the same bearer your agent's MCP configuration already carries.

It finds the session through a pointer file that the session's own start step wrote — a small file named for the transcript, holding the id of the build session that transcript belongs to. With no pointer it says the session is not attributed and stops: it never guesses which session to charge. And it is fail-open by construction — a missing transcript, an unset credential, a network error or an unexpected exception each print one line and exit successfully, because a hook that blocks the end of a session is worse than a missing number. The setup is on the Claude Code page; the session boundary it hangs off, step by step, is in how we run start-session and end-session.

The hook is a convenience, not the interface. record_build_session_usage is an ordinary MCP tool that any MCP-capable client can call, and POST /v1/usage is its versioned REST twin under /v1 with the same field names, so any agent or script that can make an HTTP request can report a session's usage. The protocol around it is how we run it — the server does not enforce it, and the recipe is the public part.

What people see

A filter, a totals card, a breakdown table

app.postmq.com/usage is four filters over two reads. The filters are a project picker (every project, archived ones included, so usage recorded before an archive is still reachable), a break-down-by picker offering project, day, week and month, and a from and a to date — the to date inclusive of the whole day you pick. Above the breakdown sits a Totals card of eight figures for the current selection: Input, Cache write (5m), Cache write (1h), Cache read, Output, Turns, Sidechain turns and the number of Reports the total is made of. The breakdown itself is a table of nine columns — the group key and those eight — thousands-separated, with a project key shown as the project's name.

Two honest limits, and they are the reason this section is short. There is no chart on this view today and no export button: it is figures and a table. And it is read-only — nothing on the page records, edits or deletes a usage row, because recording is the agent's write. The cumulative-flow, throughput, aging-WIP and cycle-time charts live on a separate flow view in the dashboard, which is dashboard-only and honest about having no transition history.

One thing you will not find: a usage report is deliberately not audited — it writes no audit row. There is no usage family in the audit event types at all, and the record path takes only the workspace from the credential because the row records no actor. That is the same deliberate exception as a session ping — a light, high-frequency touch at process exit. Every session-state lifecycle event is still appended to the workspace's tamper-evident SHA-256 chain in the same transaction as the row it records; this one is not a lifecycle event, and the code says so where it is written. The chain itself is on the security page.

API surface

2 tools, 2 routes, one dashboard page

The MCP tool, the versioned REST route under /v1 and the dashboard page all run the same service layer; the tools are thin adapters over the services the routes call, and the reads are tested byte-identical between REST and MCP.

record_build_session_usage
MCP tool
{
  "method": "tools/call",
  "params": {
    "name": "record_build_session_usage",
    "arguments": {
      "build_session_id": "01K…",
      "client_session_id": "aaaaaaaa-0000-4000-8000-000000000001",
      "input_tokens": 9600,
      "cache_write_5m_tokens": 0,
      "cache_write_1h_tokens": 19600,
      "cache_read_tokens": 247200,
      "output_tokens": 4100,
      "turns": 51,
      "sidechain_turns": 4,
      "models": ["a-model-id"],
      "agent": "claude-code",
      "window_start": "2026-08-19T09:00:00Z",
      "window_end": "2026-08-19T17:20:00Z"
    }
  }
}
REST
POST /v1/usage
Authorization: Bearer pmq_…redacted…
Content-Type: application/json

{
  "build_session_id": "01K…",
  "client_session_id": "aaaaaaaa-0000-4000-8000-000000000001",
  "input_tokens": 9600,
  "cache_write_5m_tokens": 0,
  "cache_write_1h_tokens": 19600,
  "cache_read_tokens": 247200,
  "output_tokens": 4100,
  "turns": 51,
  "sidechain_turns": 4,
  "models": ["a-model-id"],
  "agent": "claude-code"
}
Dashboard
app.postmq.com/usage                   the report lands in Totals and in the breakdown row for its project
                                       Break down by: Project · From / To bound it to a window
get_usage
MCP tool
{
  "method": "tools/call",
  "params": {
    "name": "get_usage",
    "arguments": {
      "group_by": "project",
      "from": "2026-08-01"
    }
  }
}
REST
GET /v1/usage?group_by=project&from=2026-08-01
Authorization: Bearer pmq_…redacted…
Dashboard
app.postmq.com/usage                   Break down by: Project · a row per project, biggest first
                                       Totals: Input · Cache write (5m) · Cache write (1h) · Cache read · Output · Turns · Sidechain turns · Reports

The 2 MCP tools · from the server's own manifest

Token counts and turns per build session, rolled up per project, day, week or month. PostMQ speaks standard MCP, so any MCP-capable client can call them; each name links to its row in the catalogue.

The usage MCP tools, from docs/mcp/tool-surface.json
toolwhat it doesneeds
record_build_session_usage Record (idempotent upsert) the token usage one AI-coder transcript contributed to a build session, keyed on (build_session_id, client_session_id) — a re-fired report overwrites the counts, unless it is older than the stored one by window_end, in which case it is ignored and the response carries applied:false. Store the four input classes separately (input, cache_write_5m, cache_write_1h, cache_read) because cache-read dominates. Allowed on an active OR closed session. Requires the write_session_state scope. required: build_session_id write_session_state
get_usage Roll up token + turn usage across your workspace's build sessions, grouped by group_by (total, project, day, week, or month), within an optional project filter and an optional inclusive-from / exclusive-to created_at window (ISO-8601). The per-project rollup follows each session's CURRENT project. Open to any authenticated caller. authenticated

The 2 REST routes

Under the versioned /v1 API. The write carries the write_session_state scope; the read accepts a bearer of either tier or a human session.

The usage REST routes
routewhat it doesneeds
POST /v1/usage record one transcript’s report against a build session — an upsert on (build session, transcript id): a first report is 201 Created, a re-fired report overwrites the counts and is 200 OK, and a report older than the stored one by window_end is discarded (200, applied false); allowed against an active or a closed session write_session_state
GET /v1/usage the rollup — group_by total, project, day, week or month, with an optional project_id and an optional inclusive from / exclusive to window over created_at authenticated
Guarantees and limits

What holds, how, and where the caveat is

A mechanism per row; the badge says whether the row is shipped as stated or shipped with a caveat the note carries. Nothing on this page is default-off or roadmap.

Usage guarantees and limits: what holds, the mechanism, and its scope
what holdshowscope
A re-report overwrites; it never double-countsC104 the record is an upsert on (workspace, build session, transcript id) behind the unique index UQ_build_session_usages_report; the row is read inside the transaction under UPDLOCK, HOLDLOCK — a key-range lock that covers the key before the row exists, so two concurrent first reports for one transcript serialise instead of racing to a second insert shipped
A stale report is discarded, not appliedC104 window_end is the report-ordering token: when the incoming report and the stored row both carry one and the incoming one is older, nothing is written and the response carries applied false with the row as it stands. Serialising two reports decides which lands last, not which is newer — so this is the half a lock could never buy shipped
A first report is 201 Created, an overwrite is 200 OKC104 the service returns inserted or overwritten and the endpoint maps that to the status; the body carries the same created flag on both transports, so a client can tell which happened without guessing shipped
The row is mutable on purpose, and its first-seen time is notC104 this is the one session-state table with no trigger: an overwrite replaces the counts, the window and the models, preserves created_at and bumps updated_at — clamped so it can never fall before created_at when a reporting clock is skewed shipped
The counts are the client’s, and PostMQ measures none of themC110self-reported: the numbers are as good as the client that sends them; a client that reports nothing produces no row and no gap you can see every token, turn, model and window value in the row is copied from the request; the server validates bounds and stores it. PostMQ does not connect to your model provider, does not observe your agent, and holds no price table shipped with caveat
The five token classes stay apartC105 input, cache write (5m), cache write (1h), cache read and output are separate BIGINT columns with a non-negative CHECK, never one total — they are billed at different rates and cache read dominates agentic coding, so a single number would hide the shape shipped
Turns are bounded by their own arithmeticC105 turns and sidechain_turns are non-negative and sidechain_turns may not exceed turns; window_end may not precede window_start; each rule is both a service check (a 400) and a database CHECK constraint shipped
The five rollup axes, one queryC106the period is computed from created_at — when the report landed — not from the window the report describes total, project, day, week and month; total and project aggregate in SQL, day, week and month project the rows and bucket in memory; week is Monday-anchored and month starts on the first, both keyed yyyy-MM-dd shipped with caveat
Per-project cost follows the session, not a copy of itC106 the rollup joins build_sessions for each session’s current project rather than denormalising a project onto the usage row, so re-homing a session moves its historical usage with it; the project rows come back biggest first, by cache read plus input plus output shipped
Writes need write_session_state; reads need only a signed-in callerC107 POST /v1/usage carries RequireScope("write_session_state") and the record tool passes the same scope constant to its gate; GET /v1/usage carries no scope and accepts a bearer of either tier or a dashboard session, and the read tool only authenticates — the same rule over REST and MCP shipped
The workspace comes from the credentialC107 on every path the workspace is read from the resolved bearer or session and never from the arguments or the body; an unknown build session in that workspace is a not-found, not a foreign-key error shipped
A usage report writes no audit rowC108not audited, on purpose — every session-state lifecycle event is still on the workspace’s tamper-evident chain; this high-frequency touch is not one there is no usage event family in the audit event types at all, and the service names no audit writer; the write path takes only the workspace from the credential because the row records no actor. It is the same deliberate exception as a session ping shipped with caveat
REST and MCP return the same bytesC05 both surfaces build the record result and the rollup through one shared session-state serializer, so the two transports are byte-identical by construction and tested that way shipped
People see totals and a breakdownC109there is no chart on this view today, no export and no dollar figure; the flow charts are a separate view app.postmq.com/usage — a project filter, a break-down-by picker, a from and a to date; a Totals card of eight figures and a breakdown table of nine columns, thousands-separated shipped with caveat
Nothing acts on the numberC110awareness, not enforcement — if you need a ceiling, this is not it today no sweep, gate, limiter or plan check reads the usage table — only the rollup does. There is no budget, no threshold, no alert and no enforcement: usage is never consulted to stop a send, a session or a credential shipped with caveat
Honest limits

What this is not

It is a record of what was consumed, not a meter and not a control. The numbers are self-reported by the client that sends them, so PostMQ cannot verify one or notice a session that never reported; there is no automatic capture. There are no dollars — no price table, no spend figure, no estimate, and no currency printed anywhere. There is no per-model breakdown: the model ids seen are stored as a set, and the counts are per transcript, not per model. There are no budgets, thresholds, alerts or enforcement — nothing reads the usage table except the rollup, so nothing can stop on a number. The period buckets are computed from when a report landed, not from the window it describes, and the day, week and month rollups bucket in memory rather than in SQL. The dashboard view has no chart and no export, and the flow charts are dashboard-only with no transition history behind them. There is no CLI command for any of this, and a usage report is deliberately not audited. What the number is good for is the governance question rather than the finance one — cost sitting next to the decisions it paid for, which is the case the governance page makes.

Questions

Seven things people ask

No. PostMQ never bills for what your agents consume — it is not between you and your model provider, it holds no price table and it issues no invoice for tokens. What it records is a count, next to the session that spent it. Your PostMQ subscription is priced on the people and agents you govern, on the pricing page; the token counts here are a record, not a meter.

From the client that reports them. A call to record_build_session_usage carries the counts; the server validates their bounds and stores them. PostMQ does not connect to your model provider, does not sit in the request path and does not observe your agent, so it can neither confirm nor correct a report. That is the honest boundary: the numbers are self-reported, and they are as good as the client sending them. Our own reporter reads what the client itself recorded — a finished transcript, or on one client a snapshot of its conversation store — but that is a property of the reporter, not of the server.

Not in the product. Turning five token classes into money needs a per-model price table with rates that change, and PostMQ does not own one — so it stores token counts, not dollars, and prints no currency anywhere. If you want a figure, read the rollup with get_usage, price it against your provider’s current rates yourself, and label the result an estimate. A built-in estimate is roadmap, not shipped.

No, and nothing in the code pretends otherwise: no sweep, limiter, gate or plan check reads the usage table — the rollup is its only reader. There is no threshold to set, no alert to receive and no enforcement to switch on. Usage here is for awareness, not enforcement: you can see what a project cost after the fact, not cap it while it runs.

Ours does — postmq-usage-reporter, one Python file with no dependencies, installed by the kit into every client it supports. How it is triggered depends on what that client offers: an end-of-session event where one exists, a per-turn event where it does not, and on one of them no event at all — there the end-session skill runs it, so a session nobody ends records nothing. It recognises which client wrote the transcript and reports that as the agent. But any client can: record_build_session_usage is an ordinary MCP tool and POST /v1/usage is its ordinary REST twin, with the same field names, so any agent or script that can make an HTTP request with a write_session_state credential can record a session’s usage. Nothing about the record is specific to one product — the agent field names whichever one sent it.

Nothing changes. The record is an upsert keyed on the build session and the transcript id, so a re-fired hook, a manual backfill of the same transcript and a later genuine session end all land on the same row rather than adding a second. The first report answers created true; every later one answers created false. One later report does not overwrite: one that arrives out of order, carrying a window_end older than the stored row’s, is discarded and answers applied false. That is why the hook can be re-run freely and why a project’s rollup never double-counts a session.

It shows as nothing. A build session with no usage row simply contributes zero to every rollup, and the page cannot distinguish that from a session that genuinely spent nothing — there is no per-session gap marker. Our reporter refuses to guess: with no build-session pointer it says the session is not attributed and exits without sending, rather than attaching the tokens to the wrong session.

Last verified 2026-08-19 against main at 6552288: the usage service and query service, the migration and its unique index, the 2 tools, the 2 routes, the dashboard page this page names and the reporter hook were read on that day.

Related: session state — the protocol this is the last step of · the backlog the same session files into · the usage tools in the catalogue · how we run the session boundary · what PostMQ itself is priced on.

Know what the last sprint of agent work consumed.

Create a workspace, connect your agent, install the hook — or make the one call yourself — and the next session's tokens land next to its decisions.