Quickstart
Point one base URL at TokenTriage, send a request, and read back exactly what that request cost — in about five minutes, in pure observation mode, with nothing about your responses changed.
Last updated
On this page
TokenTriage starts as a ledger: it observes and costs every request and changes nothing about the response. You climb to routing later, once the evidence says it’s safe. This page gets you from install to a costed request you can see the dollar amount for — without touching your application logic beyond one base-URL swap.
1. Install
TokenTriage is a single source-available (FSL) Go binary — no CGO, no sidecar runtime. Build it from source (a prebuilt release binary works too):
git clone https://github.com/inferops/tokentriage.git
cd tokentriage
# One static binary; the pricing table is compiled in via go:embed.
CGO_ENABLED=0 go build -o dist/tokentriage ./cmd/tokentriage
Confirm the build and the embedded pricing snapshot:
$ tokentriage version
tokentriage dev (commit none, built unknown)
pricing snapshot: litellm@2026-07-14#179affb
See Installation for the container image and cross-compile targets.
2. Run a ledger proxy
The fastest start is a zero-config ledger proxy for an Anthropic upstream (ideal for Claude Code). It costs every request and mutates nothing:
$ tokentriage run --anthropic
This listens on 127.0.0.1:8787 (the data plane — your traffic) and
127.0.0.1:9090 (the admin plane — /health, /metrics, /tt/api/status, and,
when enabled, the dashboard at /ui/).
3. Point your app at the proxy
Swap only the base URL. Nothing else in your code changes. The OpenAI dialect lives
under /v1 (so /v1/chat/completions); the Anthropic SDK takes the proxy root and
appends /v1/messages itself.
from openai import OpenAI
# The one change: base_url points at the proxy's /v1 dialect prefix.
client = OpenAI(base_url="http://127.0.0.1:8787/v1")
resp = client.chat.completions.create(
model="gpt-5",
messages=[{"role": "user", "content": "Summarize this ticket."}],
)import OpenAI from "openai";
// The one change: baseURL points at the proxy's /v1 dialect prefix.
const client = new OpenAI({ baseURL: "http://127.0.0.1:8787/v1" });
const resp = await client.chat.completions.create({
model: "gpt-5",
messages: [{ role: "user", content: "Summarize this ticket." }],
});curl
$ curl http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-5",
"messages": [{"role": "user", "content": "Hello"}]
}'
Using the Anthropic SDK instead? Give it the proxy root — it appends /v1/messages
on its own:
from anthropic import Anthropic
client = Anthropic(base_url="http://127.0.0.1:8787") # root, NOT /v1import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "http://127.0.0.1:8787" }); // root, NOT /v1For Claude Code specifically, wrap sets ANTHROPIC_BASE_URL for you and injects a fresh
x-tt-trace-id (plus x-tt-agent), so every request in that session groups into one
trace:
$ tokentriage wrap --agent claude -- claude
4. See what that one request cost
This is the payoff: a 200 OK is not the finish line — the dollar is. TokenTriage
stamps one response header on every proxied request, x-tt-request-id, which is the
join key into the costed ledger row. (In ledger mode that’s the only x-tt-*
response header; decision headers appear once you climb to shadow/route.)
Re-run the curl call with -i to see the header the proxy added:
$ curl -i http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{"model":"gpt-5","messages":[{"role":"user","content":"Hello"}]}'
HTTP/1.1 200 OK
content-type: application/json
x-tt-request-id: tt_d5k7ntwm6utqbk4eigg3am3udm # ← the join key (illustrative id)
...
Read the dollar back — three ways
A. From the terminal (top). Roll up the ledger — attributed by model, agent, MCP
tool, trace, or step-class. Values below are illustrative:
$ tokentriage top --by model
MODEL REQS INPUT OUTPUT CACHE-R CACHE-W REASON CACHE-SAV COST P50 P90 P99
gpt-5 1 88 40 32 0 0 0.0% $0.000514 $0.000514 $0.000514 $0.000514
TOTAL 1 88 40 32 0 0 0.0% $0.000514
That $0.000514 under COST is real accounting, not a placeholder: it is 88·$1.25e-6 + 40·$1e-5 + 32·$1.25e-7 from the embedded price table for openai/gpt-5 (INPUT·OUTPUT·CACHE-R
tokens), stamped with the pricing snapshot. P50/P90/P99 are the per-request cost
percentiles for the group; with one request they collapse to that single figure. (You can also roll up --by agent, --by tool, --by step-class, or
drill into one run with --trace <ID>; window with --since 24h.)
B. From the Python SDK. The zero-dependency instrumentation SDK reads back the receipt the proxy stamped, then pulls the honest dollar from your own admin plane by the join key (it forwards and reads; it never computes the price itself):
import tokentriage as tt
tt.init(base_url="http://127.0.0.1:8787")
# Ask the OpenAI SDK for the raw response so the x-tt-* headers are visible.
raw = client.chat.completions.with_raw_response.create(
model="gpt-5",
messages=[{"role": "user", "content": "Summarize this ticket."}],
)
rcpt = tt.receipt(raw) # typed, read-only view of the response headers
print(rcpt.request_id) # -> "tt_d5k7ntwm6utqbk4eigg3am3udm"
# Pull the costed row from the admin plane (default http://127.0.0.1:9090).
row = tt.ledger.fetch(rcpt) # or tt.ledger.fetch(rcpt.request_id)
print(row.cost.render()) # -> "$0.000514 (snapshot litellm@2026-07-14#179affb)"import * as tt from "tokentriage";
tt.init({ baseUrl: "http://127.0.0.1:8787" });
// Ask the OpenAI SDK for the raw Response so the x-tt-* headers are visible.
const { response } = await client.chat.completions
.create({
model: "gpt-5",
messages: [{ role: "user", content: "Summarize this ticket." }],
})
.withResponse();
const rcpt = tt.receipt(response); // typed, read-only view of the response headers
console.log(rcpt.requestId); // -> "tt_d5k7ntwm6utqbk4eigg3am3udm"
// Pull the costed row from the admin plane (default http://127.0.0.1:9090).
const row = await tt.ledger.fetch(rcpt); // async in TS (Python is sync)
if (row.cost) console.log(tt.renderCost(row.cost)); // -> "$0.000514 (snapshot litellm@2026-07-14#179affb)"The cost you get back is a closed three-state value — PRICED, UNPRICED, or absent
— so a request the server could not price renders as UNPRICED (...), never a fabricated
$0.00. (The TypeScript SDK exposes the same ledger.fetch under sdk/ts.)
C. From the dashboard. Prefer a web view? Enable the dashboard
(dashboard.enabled: true) and open http://127.0.0.1:9090/ui/, or boot the whole thing
against a watermarked synthetic ledger with no API key:
$ tokentriage demo
dashboard: http://127.0.0.1:<PORT>/ui/ # <PORT> is chosen dynamically — open the URL your terminal prints
You should now see your spend attributed on the Cost surface — the same dollar the top
CLI and the SDK read back, now on a chart:

5. Climb the trust ladder
You’ve done the first rung — a request costed and read back. When you’re ready to cut spend, you don’t flip routing on and hope. You climb one rung at a time and let the evidence promote you:
ledger— where you are now. Observe and cost, zero mutation.shadow— also compute the would-route decision and its counterfactual cost, soreportshows the savings you’d get without touching a response.route— enforce decisions once the honesty verdict says the cheaper path is genuinely as good.
The next tutorial walks all four rungs hands-on, with the real command output at each: Your first routing rollout.
