Metrics
A metric is a number the workspace watches: triage time, weekly signups, cost per run. Each has a name, a slug (a stable handle like triage_time that machines and zv1 address it by), an optional unit, and a series of readings — one observation each, append-only. A correction is a new reading, not an edit.
Metrics come in two kinds. A measure is a number you observe — minutes, percent, dollars; its chart is the line of readings. An event is a thing that happens — a demo booked, a ticket closed; each reading counts occurrences, and the chart shows totals per day, week, or month. The platform does the counting.
A metric can also carry standing setup: which direction is good, a standing target the chart always draws, and an expected cadence (daily, weekly, monthly). A metric with a cadence that goes quiet shows as stale — a number that stops reporting is itself a signal.
Metrics exist so expectations can bind to data. When a decision entry's expectation is bound to a metric (triage time ≤ 15 min by Aug 1), every new reading of that metric lands on the entry as evidence automatically — and the open entry shows the latest reading against the target, with a ready to settle nudge when it's met. Settlement stays yours: the reading invites it, never performs it.
The Metrics tab lists every metric with its latest reading and a watching count — how many open expectations are bound to it.
How readings get in
By hand. The Record button on any metric: value, optional as-of date, optional note about where the number came from.
Through zv1. Tell zv1 the number — "support triage is down to 12 minutes this week" — and it offers to record the reading. If the metric doesn't exist yet, zv1 can create it in the same step. Works in any tool's docked assistant, chat.zerowidth.ai, or the mobile app. Every write asks for your approval first.
From a machine. The public API accepts readings with an API key, addressed by slug:
curl -X POST https://api.zerowidth.ai/1.0/ledger/metrics/triage_time/readings \
-H "Authorization: Bearer zw_..." \
-H "Content-Type: application/json" \
-d '{ "value": 12, "note": "nightly export", "key": "2026-08-13" }'
A write to a slug that doesn't exist yet creates the metric — no setup call first; it appears in the gallery marked auto-created until someone adopts it with a name and unit. Send "kind": "event" alongside the reading if the new metric should count occurrences rather than track a level; a metric that already exists keeps the kind it has, so a write can't change how its chart reads. The optional key makes the write idempotent: re-running the same job with the same keys returns the original readings instead of doubling the series. For bulk pushes, POST /1.0/ledger/readings/batch takes up to 500 readings across any number of metrics in one call, with the same per-item keys.
Keys are minted in accounts with per-scope permissions — the Push metric readings scope can write readings and read nothing else, which is exactly what you'd hand a cron job, a webhook adapter, or a warehouse export.
How readings get out
Everything that goes in can leave. On the metric page, Export downloads the full series as CSV or JSON. With the Read metrics scope, the API serves the same history:
# Raw readings, oldest first, paginated
curl https://api.zerowidth.ai/1.0/ledger/metrics/triage_time/readings?limit=500 \
-H "Authorization: Bearer zw_..."
# Rolled up per bucket — totals for events, averages for measures
curl "https://api.zerowidth.ai/1.0/ledger/metrics/demo_booked/series?bucket=week" \
-H "Authorization: Bearer zw_..."
Wherever a reading comes from, it's the same object: one number arrives, every open expectation watching that metric hears about it.