Net revenue retention: keep and grow, one number
Net revenue retention reads what your Customers of 90 days ago pay today, beside logo churn and the gross line.
Net revenue retention is the ARR your Customers of 90 days ago pay today, upgrades and downgrades included. It reads 85 % on leaseline.app.
The number
How much of last quarter’s ARR did our Customers keep and grow?
curl -X POST https://api.sb/leaseline.app/metrics/query \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metric":"net-revenue-retention","shape":"retention","window":"90d"}'
curl -X POST https://api.sb/leaseline.app/metrics/query \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metric":"logo-churn","shape":"trend","window":"90d"}'
# Not live yet (gaps #4)
curl -X POST https://api.sb/leaseline.app/metrics/query \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metric":"gross-revenue-retention","shape":"retention","window":"90d"}'import { $ } from 'startup-builder'
const nrr = await $.Metric.query({ metric: 'net-revenue-retention', shape: 'retention', window: '90d' }) // the Customers earning on Jul 21, 90 days on
const churn = await $.Metric.query({ metric: 'logo-churn', shape: 'trend', window: '90d' }) // how many of them left
return await $.Metric.query({ metric: 'gross-revenue-retention', shape: 'retention', window: '90d' }) // the same, before upgrades and win-backs · Not live yet, gaps #4
npx startup-builder do Metric.query --json '{"metric":"net-revenue-retention","shape":"retention","window":"90d"}'
npx startup-builder do Metric.query --json '{"metric":"logo-churn","shape":"trend","window":"90d"}'
npx startup-builder do Metric.query --json '{"metric":"gross-revenue-retention","shape":"retention","window":"90d"}' # Not live yet (gaps #4){
"name": "do",
"arguments": {
"code": "const nrr = await $.Metric.query({ metric: 'net-revenue-retention', shape: 'retention', window: '90d' }) // the Customers earning on Jul 21, 90 days on\nconst churn = await $.Metric.query({ metric: 'logo-churn', shape: 'trend', window: '90d' }) // how many of them left\nreturn await $.Metric.query({ metric: 'gross-revenue-retention', shape: 'retention', window: '90d' }) // the same, before upgrades and win-backs · Not live yet, gaps #4"
}
}application/json{
"cohort": "2026-07-21",
"customers": 13,
"startArr": 4511,
"moves": {
"reactivatedArr": 0,
"expansionArr": 694,
"contractionArr": 0,
"churnedArr": -1388,
"oneTimeRolloffArr": 0
},
"endArr": 3817,
"rows": [
{
…
sample · 1 Not live yet85 % net revenue retention · ■■□□ Early sign · n 13 Customers · 90 days
$.Metric.query({ metric: · sample
| The Customers earning on Jul 21 | ARR then | ARR 90 days on | Kept and grown |
|---|---|---|---|
13 | $4, | $3, | 85 % |
The hub defines it: net-revenue-retention. The CSM’s number is this reading on its own book; Head of customers reads it for the Startup.
Beside it
| Reading | 90 days | What it counts |
|---|---|---|
logo-churn | 31 % | 4 of 13 Customers left |
The gross line, gross-revenue-retention (Not live yet, gaps #4) | 69 % | the same ARR before upgrades and win-backs: only Downgrades, Lost Customers and One-time sales ending |
All three are readings with no parent: none of them redefines a number in the tree.
Its leaves
| Move | 90 days | Its page |
|---|---|---|
| Won back | $0 ARR | Won back |
| Upgrades | +$694 ARR | Upgrades |
| Downgrades | $0 ARR | Downgrades |
| Lost Customers | −$1, | Lost Customers |
| One-time sales ending | $0 ARR | One-time sales ending |
Compiled, not written: edit its page, then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.