# Analytics (/docs/concepts/analytics)



Analytics answer "how is the program going?" with numbers Domino counts from its own records. For example:

* **How many people signed up this week**, and how that compares with last week.
* **How many members were active each day** of the last month.
* **How many friends qualified** from your referral race.

Each number is a **metric**, counted for one project in one environment. Domino counts sign-ups, active members, quest attempts, completions, rejections, attempts sent to review, points awarded, spent and net, members earning points, offer redemptions, accepted and qualified referrals, group joins and leaves, tracker outcomes, and the members in leaderboard settlements.

See them in Console under **Analytics**, for the selected project and environment: headline numbers, a chart per metric, and a table by quest. Your developer or coding agent can read the same numbers through the CLI or the API. Quote these numbers rather than computing your own from other reads.

> [!DEV]
> Read a project's metrics over a range with `domino analytics get` or `management.analytics.read()` (`read` permission). [Project analytics](/docs/reference/automation#project-analytics) defines every metric.

## How metrics match Console [#how-metrics-match-console]

Where Console counts the same thing, a metric uses the same definition, so the numbers agree:

* **Sign-ups** are the memberships the Members page counts.
* **Completions** are the completions Console counts, never revoked ones.

Some metrics count history Console doesn't show:

* **Attempts sent to review** count every attempt that went to staff review, decided since or not. Console counts only those still waiting.
* **Active members** count members with an attempt in each period. Any member with an attempt counts, test members included, and so does a member whose attempt a tracker started. This is not the [active members](/docs/concepts/participants#active-members) count your plan bills, which counts live members who sent a signed-in request on a day after they joined, and leaves out test members, imports and tracker records. **Settings → Plan & usage** shows the billed count.

## The metric catalog [#the-metric-catalog]

Domino counts a fixed set of metrics, each with a one-line definition. There are no formulas or custom queries: a new number becomes a new metric in Domino.

> [!DEV]
> Each metric has an ID, such as `quests.completions`, a definition, and the breakdowns and filters it accepts. List the catalog with `domino analytics metrics`, and read one metric with `domino analytics query --metric ID --range 7d`. The [metric catalog reference](/docs/reference/automation#metric-catalog) lists every metric. Read with no breakdown or filter, a metric that `domino analytics get` also shows has the same number there: both run the same count.

Two metrics are rates, from 0 to 1: the share of decided attempts that were rejected, and the share of accepted referrals whose friend has qualified since. A rate stays empty while fewer than 10 attempts or referrals stand behind it, for the whole range, each period, and each row of a breakdown.

> [!DEV]
> The rates are `quests.rejectionRate` and `referrals.qualificationRate`, which counts the referrals accepted in a bucket. An empty rate is `null`.

## Breakdowns and filters [#breakdowns-and-filters]

A **breakdown** splits a metric into rows: by quest, offer, referral program, group, tracker or leaderboard, by where and why attempts were rejected, or by whether a tracker's records were admitted or skipped. A quest you publish tomorrow gets its own row without anyone changing the query. An archived quest or program shows up only when it had activity in the range or the range before, marked as archived. Rows come largest first, and the rest can be summed into an "Other" row, so a chart can show its top 8.

Rejections split by who made them: your evaluator, staff, an exhausted budget, an expired deferral, or Domino itself when another member already completed the quest with the same key. That last case is a shared key, such as a one-time code two members both tried, not a fault in your evaluator; an evaluator that rejects with its own `key-used` code still counts as the evaluator. Top rejection reasons keep the two apart as well.

> [!DEV]
> The sources are `evaluator`, `staff`, `budget`, `expired` and `keyUsed`, in `rejectionsBySource` and as `source` rows. Each top reason's `codeSource` says who wrote its code: `relay` or `evaluator`.

A **filter** narrows what a metric counts:

| Filter        | Keeps                                                                                                                                                     |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quest`       | Activity on those quests                                                                                                                                  |
| `tracker`     | Activity on the quests the tracker triggers; on `trackers.records`, the tracker's own outcomes                                                            |
| `offer`       | Redemptions of those offers                                                                                                                               |
| `program`     | Qualifications in those referral programs                                                                                                                 |
| `leaderboard` | Settlements of those leaderboards                                                                                                                         |
| `group`       | Activity by members who are in the group now, whatever their membership was at the time; on `groups.joins` and `groups.leaves`, the changes in that group |

> [!DEV]
> Each filter takes a key or a list of keys. A query caps breakdown rows with `limit` (1 to 100, default 100) and sums the rest into `other`; for a rate, `other` divides the summed numerators by the summed denominators of the rows it holds. A group is checked when the query runs: a key with no group answers `group-not-found`, naming it.

## Dashboards [#dashboards]

A **dashboard** is a page of panels that project code defines with `defineDashboard`. Each panel reads one catalog metric, with at most one breakdown, its filters, a ledger for points metrics, and a chart: `number`, `line`, `bar` or `table`. A panel's numbers are what `domino analytics query` returns for the same metric, breakdown and filters.

A dashboard is a definition, like a quest:

* **Deployed in test.** A deployment checks every panel against the catalog and every key against the environment, and refuses a dashboard that breaks a rule. A changed definition publishes a new version; an identical one publishes nothing.
* **Promoted to live.** A [promotion](/docs/concepts/promotion) carries it to live by key.
* **Archived, not deleted.** Removing it from relay.json leaves it in place. Archiving takes it out of the list and keeps it readable.

A dashboard shows totals, never an individual member, so a person with the experimental **Viewer** role can read the project's dashboards without reading anything else in it: a way to [share them with a sponsor](/docs/build/dashboards#share-dashboards-with-a-sponsor).

Dashboards read every panel at once, over one range: the dashboard's default, or the one you choose. A panel that fails, such as one filtered on a group the environment lacks, carries its own error while the others still read. [Dashboards](/docs/build/dashboards) shows how to define, deploy and check one, and the [analytics reference](/docs/reference/analytics) how an agent picks metrics for a project's goals and explains a dashboard's numbers.

## Ranges end now [#ranges-end-now]

You choose a range: the last 7, 30 or 90 [project-calendar](/docs/concepts/calendar) days, today included, or all time. All time starts on the day of the project's earliest dated record: usually its first membership, or an older record such as a completion imported with its original date. Every range ends at the moment you read it.

| Range       | Buckets                                                                  | Compared with                       |
| ----------- | ------------------------------------------------------------------------ | ----------------------------------- |
| `7d`, `30d` | One per project-calendar day                                             | The range of equal length before it |
| `90d`       | One per week, from Monday; the first and last weeks are cut to the range | The range of equal length before it |
| `all`       | One per week, from Monday; the first and last weeks are cut to the range | Nothing                             |

Days and weeks follow your project calendar, as budgets and leaderboards do. A day starts at local midnight.

> [!DEV]
> Each metric has a `total` for the range, a value per bucket (`series`), and, except for `all`, a `previousTotal` for the range before. A day lasts 23 or 25 hours around daylight saving, and when the timezone changes, the day or week already open finishes in the old timezone. Each bucket's key names its timezone, such as `2026-10-08@Europe/Budapest`.

## Records count as they stand now [#records-count-as-they-stand-now]

A metric reads the records as they are when you read it, so the past can change:

* **A completion revoked later leaves the bucket it was in.** So does a redemption canceled later, and a leaderboard settlement rejected later.
* **Tracker outcomes show their current state.** A retry overwrites a skipped outcome, so `trackers.records` shows each record's outcome today, in the bucket of when the record was observed, not the outcome it had then.
* **Group changes count only real changes.** `groups.joins` and `groups.leaves` skip repeats that left membership as it was, and memberships Domino recorded without a date.
* **Points carry their entry's effective date.** A reversal takes the date of the entry it reverses, so a revoked award cancels out in its original bucket.
* **Member counts are distinct.** Active members and members earning points count each member once over the range, so a total can be less than the sum of its buckets.

## Qualified referrals, two ways [#qualified-referrals-two-ways]

Referrals have two qualification numbers, dated differently:

| Number              | Counts                                                                       | Answers                                                           |
| ------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `qualified`         | Active qualifications, by when each first qualified                          | How many friends qualified this week?                             |
| `acceptedQualified` | Referrals accepted in the range whose friend has an active qualification now | Of the friends accepted this week, how many have qualified since? |

A friend accepted before the range who qualifies inside it counts in `qualified` only. Divide `acceptedQualified` by `accepted` for the share of a range's friends who have qualified: each friend counts once, so it never exceeds 100%. A recent range's share can still grow as its friends qualify. Both count paused (suspended) referrals.

## The test environment [#the-test-environment]

Test uses the same definitions, and counts test members like everyone else. Two things make its numbers move:

* **Test member clocks.** A test member's clock can be [advanced](/docs/use/quests#try-quests-with-test-members) up to 90 days. Their attempts, completions and quest points are dated on that clock, and activity dated after now is left out until its date passes.
* **Resets delete history.** Resetting a test member deletes their attempts, completions and points, so past buckets shrink.

> [!DEV]
> The read's `testEnvironment` reports when a test member was last reset and how many have an advanced clock. Live has neither.
