What leaves your machine when you sync
tokenstat works completely offline. You never have to create an account, and with no account nothing is ever uploaded.
Syncing exists for one reason: a local CLI cannot add up a work laptop, a personal machine, and a desktop into one picture. If that is not a problem you have, you can stop reading and keep using the tool exactly as it is.
If it is, this page is the full account of what crosses the wire.
Read it yourself first
Before any of the explanation below, there is a command that makes it unnecessary:
tokenstat sync --dry-run
That prints the exact canonical JSON that would be uploaded, then exits without uploading anything. Nothing on this page can be more convincing than reading your own payload, and if a future version ever starts sending something new, this is where you would see it.
The payload, field by field
{
"v": 1,
"machine": "m_7f3a9c21b4e85d06",
"salt_id": "s_1a2b3c4d",
"tz": "Europe/Budapest",
"generated_at": "2026-07-29T09:12:44Z",
"window": { "from": "2026-07-01", "to": "2026-07-29" },
"rows": [
{
"d": "2026-07-14",
"src": "claude_code",
"model": "claude-opus-4-6",
"proj": "p_9f2ad41c7b03",
"in": 12043, "out": 3901, "cr": 88210, "cw": 4102,
"ev": 37, "plan": true, "conf": "high"
}
]
}
| Field | What it is | Why it is safe |
|---|---|---|
machine |
A random id made on first sync | Not a hardware id, MAC hash, or hostname hash. It carries no fingerprint and cannot be correlated across users. Your readable machine label is set in the web UI and never sent by the client |
proj |
HMAC-SHA256(local salt, project path), truncated |
The salt never leaves your machine, so projects can be grouped without anyone, me included, being able to name one |
salt_id |
Which local salt made those hashes | Rotating your salt starts a new namespace instead of silently merging rows under wrong keys |
model |
The vendor's public model id | Needed for pricing. Not sensitive |
src |
Which tool it came from | A closed list of supported tools, not free text |
d, in, out, cr, cw, ev |
A date and six counters | Numbers |
plan |
Whether a subscription covered it | Keeps plan-covered usage out of the money column |
conf |
How confident that row's count is | The same confidence the CLI already shows you locally |
tz |
An IANA timezone, e.g. Europe/Budapest |
The day boundary is your local one and the server must not re-bucket it. A timezone, never a city and never coordinates |
Read the list for what is missing, which is the point: there is no field for a file path, a prompt, a repository name, a hostname, an email, or an IP. Not "we promise not to fill them in". They do not exist.
Why a new field cannot appear quietly
Two things enforce that, neither of which is a policy.
The payload is built from types where a free-text field does not compile. Adding one is not a small careless change, it is a visible change to a type.
The server rejects unknown fields rather than ignoring them. A client that started sending something extra would fail, loudly, instead of having the extra quietly stored. The accepted schema is published at /api/v1/schema, and a change to it means a version bump you can see.
What the server stores
One row per day, per tool, per model, per opaque project key. That is the same shape as the payload, because the server has nothing else to work with.
Your profile is private until you claim a handle and turn publishing on. Private means private: an account that never publishes still contributes to nothing but its own dashboard, and the platform stats page carries grand totals only, with no way to derive an individual from it.
Turning it off
- Do not sync at all. The CLI never contacts the network unless you ask it to. There is no telemetry to disable, because there is none.
- Unlink a machine from your settings, and its rows go with it.
- Export everything as JSON, or delete the account outright, both from
/settings/data. Deletion is a real cascade, not a flag on a row.
Deleting the hosted account does not touch your local archive. That is yours, it was always yours, and it stays on your disk.
Related
- Is tokenstat safe to run? covers the local half: what it reads, and the boundary that keeps conversation text out of the database.
- The privacy notice is the formal version, covering sessions, cookies, and OAuth.
More in Sync and profiles
