> ## Documentation Index
> Fetch the complete documentation index at: https://docs.userkit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Revenue metrics

> MRR, ARR, the four movement buckets, LTV, NRR and revenue by channel — with the normalization rules that decide what each number means.

Every one of these numbers is a normalization decision wearing a name. MRR is
"revenue, monthly" only after somebody has said what an annual plan contributes,
whether a trial counts and where a discount lands — and two products that answer
those differently produce two numbers that cannot be compared. This page is the
answers.

The panel draws them under **Analytics → Revenue** and **Analytics → Overview**,
behind `analytics:read`.

## Where they come from

They are **not** derived from [tracked events](/en/guides/analytics). They are
recomputed nightly from your subscriptions and invoices — the mirror of what your
payment gateway holds — into one row per environment, **UTC day** and
**currency**.

```bash theme={null}
curl "https://api.userkit.dev/v1/organization/analytics/metrics?environment=live&from=2026-05-01T00:00:00Z&to=2026-08-01T00:00:00Z" \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: …"
```

```json theme={null}
{
  "metrics": [
    {
      "day": "2026-07-31",
      "currency": "BRL",
      "mrr_minor": 4185000,
      "arr_minor": 50220000,
      "new_mrr_minor": 29900,
      "expansion_mrr_minor": 14950,
      "contraction_mrr_minor": 0,
      "churned_mrr_minor": 9900,
      "logo_churn": 1,
      "active_subscriptions": 141,
      "ltv_minor": 892000,
      "nrr_pct": 112.4,
      "fx_rate": null,
      "fx_rate_source": ""
    }
  ]
}
```

Amounts are in the currency's **minor unit** — cents, for most of them.

## Rows are per currency, and never summed across

Two prices in two currencies are two amounts somebody chose, not conversions of
one another, so nothing here adds them together. `fx_rate` and `fx_rate_source`
exist so that the day a converted total *is* drawn, the rate used and where it
came from are on the row rather than in whoever drew the chart. Until a rate
source exists they stay `null` and `""` — which sends a reader to check, instead
of asserting a conversion nobody performed.

## What each number means

| Field                   | Meaning                                                                                                              |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `mrr_minor`             | Recurring revenue **at the end of that UTC day**, normalized to a month                                              |
| `arr_minor`             | `mrr_minor × 12`. Stored beside it so a row is legible without code, and so the pair can never disagree in an export |
| `new_mrr_minor`         | MRR from customers who contributed nothing yesterday                                                                 |
| `expansion_mrr_minor`   | The increase, for customers who were already paying                                                                  |
| `contraction_mrr_minor` | The decrease, for customers still paying something                                                                   |
| `churned_mrr_minor`     | What customers who now contribute zero were contributing yesterday                                                   |
| `logo_churn`            | How many customers are behind that churned bucket                                                                    |
| `active_subscriptions`  | Distinct subscriptions contributing more than zero                                                                   |
| `ltv_minor`             | ARPA ÷ the monthly logo-churn rate. `null` when the window saw no churn                                              |
| `nrr_pct`               | Net revenue retention over 12 months, in percent. `null` when nobody was paying a year ago                           |

The four movement buckets partition the **per-customer** difference between D-1
and D, per currency: new when yesterday was zero, churned when today is zero,
expansion and contraction for a move between two nonzero amounts. A customer who
upgrades and another who downgrades on the same day show up in both middle
buckets rather than netting to nothing.

## Normalization

**Annual is amortized.** A yearly price divides by 12, a weekly one multiplies by
52⁄12, a daily one by 365⁄12, and a price billed every *n* intervals spreads over
its own span. MRR is a monthly rate, so every interval is expressed in it.

**One-time prices never enter MRR.** A lifetime deal is revenue, not recurring
revenue; it appears in invoices and in revenue by channel.

**Trials are out.** A trial is a promise to maybe pay, and counting it books
revenue that churns silently when the card is never entered. A subscription
enters MRR when its trial ends — read from the trial's end timestamp rather than
from the status column, so a webhook that has not yet flipped `trialing` to
`active` cannot hide a conversion.

**Discounts, proration and tax enter through the invoice, never the price.** What
a customer agreed to pay is the immutable catalogue price this rollup reads, so
MRR here is **contracted MRR**. Cash — what actually arrived, coupons and
proration included — is the invoice's own money, and it is what revenue by
channel counts.

**LTV** is ARPA divided by the monthly logo-churn rate, computed over a trailing
**30-day** window: customers paying 30 days ago who pay nothing today, over
customers paying 30 days ago. With no churn in that window the answer is `null`,
not infinity — an undefined LTV is not an enormous one, and a `null` sends the
reader to the churn column instead of printing a number nobody should plan
around.

**NRR** is a **12-month** window: what the customers who were paying 365 days ago
pay today, over what they paid then. `null` when there was no such cohort.

Statuses with no timestamp to place them in time — paused, incomplete, unknown —
are excluded outright rather than guessed at. A paused subscription asserting
revenue would be exactly the reassurance that stops somebody checking.

## What a mirror can reconstruct, and what it cannot

These numbers are derived from a copy of what your gateway holds, and the gateway
rewrites its rows in place. A subscription's start, its cancellation and its trial
end are timestamped, so a past day can be valued honestly. **A quantity change is
not.** A seat count that went from 3 to 5 reads as having always been 5.

So the nightly job recomputes about **8 UTC days** and freezes everything older
as it was written. That freeze is the only history a mirror can keep — and it is
why reprocessing further back would repaint last quarter with this quarter's seat
counts. A correction inside the window lands by itself on the next run; outside
it, what is stored is the history.

## Revenue by acquisition channel

```bash theme={null}
curl "https://api.userkit.dev/v1/organization/analytics/revenue-by-channel?environment=live" \
  -H "Authorization: Bearer uk_st_…" \
  -H "X-Organization-Id: …"
```

```json theme={null}
{
  "channels": [
    { "utm_source": "google", "utm_medium": "cpc", "utm_campaign": "launch", "currency": "BRL", "revenue_minor": 1288000, "invoices": 47, "customers": 21 }
  ]
}
```

This is the join no point tool can make, because no point tool sees both the
landing page and the invoice: **paid invoices grouped by the first-touch UTM of
the customer's founding contact**.

* **A customer is a team, and a team has no landing page — a person does.** The
  channel of a customer is therefore the first-touch attribution of its founding
  contact: the earliest membership, ties broken deterministically.
* **Colleagues do not get their own channel.** Somebody invited onto a team
  arrived through your product, not through a campaign, and crediting their UTM
  would credit the wrong door.
* **Imported customers keep the channel that won them**, because an imported line
  carries its own attribution and it lands on the contact at birth.
* **The money is the invoice's**: what was actually paid, discounts and proration
  included, in the invoice's own currency, grouped per currency and never summed
  across.
* Empty `utm_*` strings are the honest form of "no attribution recorded" —
  direct, or a contact created before the attribution existed.

A `?ref=` code rides in that same first touch, and it is what
[referrals](/en/guides/referrals) are owed on — the same capture answering a
different question: this asks which channel won the customer, that asks who gets
paid for it.

## Whose midnight

Days are UTC at storage and your organization's timezone at display, the same
rule the [event counts](/en/guides/analytics#whose-midnight) follow. Deciding it
once is what lets a revenue chart and an event chart be laid over each other.
