Skip to main content
The analytics export streams the router’s raw routing decisions — one row per upstream model call, nothing aggregated — so your data team can load them into its own warehouse and run its own analysis. It is a pull API: issue a read-only key, poll a cursor, append what comes back. Every number in the Router report is derived from these same rows, so you can reproduce or challenge any of them.
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 your rk_ 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.
Router keys are rejected on the analytics endpoints, and analytics keys are rejected on every routing endpoint. The separation is enforced on both sides.

Pull a page

The body is newline-delimited JSON: one object per line, no envelope, no wrapping array. Pipe it straight into a loader. 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 — when X-Weave-Has-More turns false, you are caught up.
Rows are written once and never updated, and 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. So COUNT(*) counts model calls. To count user-visible turns, filter on turn_type = 'main_loop'. To collapse retries and failovers, group by request_id.
This is the one thing teams get wrong on first load. A model-call count will not match a request count from any other source, and it is not supposed to.

Ordering and freshness

Rows come back ordered by recorded_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.
Partition your warehouse table on requested_at, but bookmark your job on recorded_at. They are different clocks.

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.
decision_reason is free-form diagnostic text written for humans, and its wording changes between router versions. Do not parse it and do not group by it — group on decision_model, sticky_hit, failover_used, or candidate_models instead.

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.
Last modified on October 17, 2026