Owlmetry
CLI

Stats

Query daily and hourly time-series rollups and trigger backfills from the terminal.

Time-Series Rollups are pre-aggregated daily and hourly count tables for the four event-shaped data sources — events / metric_events / funnel_events / questionnaire_responses. They power the subtle sparkline charts on dashboard cards and arbitrary date-range trend queries that survive raw-event retention pruning.

Kinds: events, users, sessions, metric_completions, funnel_completions, questionnaire_responses. Grains: daily, hourly. Buckets are UTC; the in-progress bucket is excluded by default so a partial day or hour can't render as a misleading dip. Reads always hit the project-rollup row (one row per (project, is_dev, bucket)) unless a single-app filter is supplied — never a SUM across apps at query time.

Two background jobs maintain the tables — stats_aggregate_hourly at :05 UTC and stats_aggregate_daily at 00:30 UTC, each re-aggregating the trailing 3 buckets to absorb late-arriving events from offline SDKs. Historical backfills (e.g. first-time rollout or repair after a gap) are an operator action — run pnpm backfill in the monorepo against the production DB. It re-aggregates the trailing 365 days using DELETE+INSERT … SELECT … GROUP BY in a single transaction per bucket range, so re-running over the same window is idempotent and stale rows never survive. unique_users and unique_sessions are per-bucket distincts and non-additive across buckets — sparkline value lines plot the additive count columns.

Query a series

owlmetry stats <kind> <grain> (--project-id <id> | --team-id <id>) \
  [--app-id <id>] [--days <n> | --hours <n>] [--from <iso> --to <iso>] \
  [--data-mode <mode>] [--slug <slug>] [--include-current]

<kind> is one of events, users, sessions, metric_completions, funnel_completions, questionnaire_responses. <grain> is daily or hourly. The default rendering is a bar-graph table showing each bucket's value alongside a trend bar; pass --format json for the structured StatsBucketedResponse.

FlagRequiredDescription
--project-id <id>One ofProject UUID. Mutually exclusive with --team-id
--team-id <id>One ofTeam UUID — aggregates across every project in the team
--app-id <id>NoNarrow to one app within the chosen scope
--days <n>NoTrailing UTC days for grain=daily (default 30, max 365). Ignored when --from/--to are set
--hours <n>NoTrailing UTC hours for grain=hourly (default 24, max 2160). Ignored when --from/--to are set
--from <iso>NoExplicit start (YYYY-MM-DD for daily, ISO 8601 for hourly). Pair with --to
--to <iso>NoExplicit end (inclusive at the bucket level)
--data-mode <mode>Noproduction / development / all. Defaults to production
--slug <slug>NoNarrow metric_completions / funnel_completions to one definition
--include-currentNoInclude the in-progress bucket (defaults to excluding it)

events, users, and sessions all read the same events_{daily,hourly} tables — the kind selects which column the value series follows. metric_completions filters to phase='complete'. funnel_completions filters to each funnel's terminal step (resolved from funnel_definitions at the route layer).

Backfill a date range

Backfilling the rollup tables is an operator action with no public CLI/API surface — it runs server-side as a one-shot script directly against the database. From the monorepo on the production VPS:

pnpm backfill

Re-aggregates the trailing 365 days for every project in a single pass. Idempotent: re-running over the same window replaces existing rollup rows with freshly computed values, so it's safe to invoke as often as needed. Aggregation tables are explicitly excluded from retention pruning, so historical buckets stay queryable indefinitely.

Ready to get started?

Connect your agent via MCP or CLI and start tracking.