Use the Router report if you want charts. Use this export if you want the underlying rows in your own warehouse, or anything else that can load newline-delimited JSON.
Issue an analytics key
Analytics keys are a distinct credential from yourrk_ router key. They carry the ra_ prefix, they authenticate only the /v1/analytics/* endpoints and the read-only GET /v1/sessions/{session_id}/cost endpoint, and they cannot route inference, reach your provider credentials, or spend credits. A key sitting in an ETL config can read telemetry and nothing else.
Issue one from Settings → Weave Router → Your API keys → New analytics key. The raw key is shown once, at issuance — store it before closing the dialog, because it cannot be recovered afterwards. Self-hosted deployments issue them from the router’s own dashboard at Settings → API keys → Issue key → Analytics (read-only).
Analytics keys appear in the same key list as routing keys, marked with an Analytics badge and a read-only spend column. To replace one, issue a new key and revoke the old one.
Pull a page
Send
Accept-Encoding: gzip (curl’s --compressed). These rows are repetitive JSON and compress by roughly an order of magnitude.
Each key is limited to 60 requests per minute, answering 429 with Retry-After beyond that. At the maximum page size that is 600,000 rows a minute.
Keep a warehouse in sync
Store the cursor between runs. The same loop does the initial backfill and the steady-state tail — whenX-Weave-Has-More turns false, you are caught up.
id is unique, so deduplicating on id makes any replayed page a harmless no-op:
What a row is
One row per upstream model call, not per user message. That is deliberately the finest grain the router addresses, and one thing a person would call a single request commonly produces several rows: a retry after an upstream error, a failover to another provider, a context-compaction call, a title-generation call, a classifier call, or a sub-agent turn. SoCOUNT(*) counts model calls. To count user-visible turns, filter on turn_type = 'main_loop'. To collapse retries and failovers, group by request_id.
Ordering and freshness
Rows come back ordered byrecorded_at (when Weave recorded the row), not by requested_at (when the turn happened). Telemetry is written after a turn completes, so event time is not monotonic — within a single page requested_at can move backwards. Ordering on ingest time is what makes “resume at this cursor and miss nothing” true.
For the same reason the export withholds the most recent 60 seconds: a row committing right now could otherwise land just behind a cursor you already passed. The readable tail is always about a minute behind live.
Fields
GET /v1/analytics/schema returns the machine-readable field dictionary — name, type, nullability, description — plus the grain, ordering, and holdback semantics. Generate your DDL from it rather than hand-transcribing this page.
The table covers the core groups.
/schema also lists routing provenance (routing_strategy, roster_version, policy_artifact_id, and related release identifiers), experiment assignment, plan and settlement, and trial-mode git context fields.
Field additions are made within a schema version, so write your loader to tolerate new columns; the version in the /schema response changes only if a field is removed or changes meaning. Version 2 removed requested_input_cost_usd, requested_output_cost_usd, and savings_usd. Version 3 added subscription_served and reports 0 in both actual_* cost columns for those rows.
Computing savings yourself
The export reports what each turn actually cost (actual_*) and nothing else. There is deliberately no counterfactual cost column and no precomputed savings_usd: the counterfactual rests on assumptions you should own — which baseline model you would otherwise have run, whether its prompt cache would have been as warm, and which price you hold it to.
Build the baseline from the token columns and a price table of your own:
GET /v1/analytics/models publishes the price book that populates model_prices (per model, per provider, input and output USD per 1M tokens, plus the cache-read multiplier). It reports current prices, not the prices in force when an older row was recorded, so pin your own price table if you need historical ones.
Cost fields are null — not zero — for rows the router could not price, so they drop out of an average instead of dragging it down. Filter them explicitly, as above, when you want the priced subset. Subscription-served rows (subscription_served = true) report 0 instead, with real token counts, so add AND NOT d.subscription_served to the query above if plan-covered turns shouldn’t count as savings.
See how savings are calculated for how Weave’s own dashboard makes the comparison, and what it does and does not claim.
What the export does not include
- Prompt or completion content. The router’s telemetry stores no message text, so none is available to export.
- Weave’s internal scoring artifacts: cluster assignments, per-candidate scores, and exploration probabilities. Policy, roster, and release identifiers are exported (see
/schema). - Shadow and counterfactual evaluations of models that did not serve the turn.
- Credentials, key identifiers, and key fragments.
