Calculate MRR from the Stripe API
Updated 2026-08-04
Stripe has no MRR endpoint. You build it from active subscriptions.
The approach
- List subscriptions with
status=active - Expand price data so you get amount and interval in one call
- Normalise each line to a monthly figure
- Sum
The code
type Interval = "day" | "week" | "month" | "year";
const MONTHS_PER: Record<Interval, number> = {
day: 12 / 365,
week: 12 / 52,
month: 1,
year: 12,
};
/** Monthly contribution of a recurring charge. */
function toMonthly(amount: number, interval: Interval, count = 1): number {
const cycles = MONTHS_PER[interval] * Math.max(1, count);
return cycles > 0 ? amount / cycles : 0;
}
/** Stripe reports minor units. Not every currency has two decimals. */
function fromMinor(amount: number, currency = "usd"): number {
const zeroDecimal = new Set(["jpy", "krw", "vnd", "clp", "isk"]);
return zeroDecimal.has(currency.toLowerCase()) ? amount : amount / 100;
}
async function trueMrr(key: string): Promise<number> {
let mrr = 0;
let startingAfter: string | undefined;
for (;;) {
const qs = new URLSearchParams({
status: "active",
limit: "100",
"expand[]": "data.items.data.price",
});
if (startingAfter) qs.set("starting_after", startingAfter);
const res = await fetch(`https://api.stripe.com/v1/subscriptions?${qs}`, {
headers: { Authorization: `Bearer ${key}` },
});
if (!res.ok) throw new Error(`stripe ${res.status}`);
const page = await res.json();
for (const sub of page.data) {
for (const item of sub.items.data) {
const price = item.price;
if (!price?.recurring || price.unit_amount == null) continue;
const major = fromMinor(price.unit_amount, price.currency);
mrr += toMonthly(major, price.recurring.interval,
price.recurring.interval_count || 1)
* (item.quantity ?? 1);
}
}
if (!page.has_more) break;
startingAfter = page.data[page.data.length - 1].id;
}
return mrr;
}
The four traps
Pagination. Stripe returns 10 by default, 100 maximum. Miss the
has_more loop and everything past your first page silently vanishes. This is
the most common bug in homegrown MRR scripts, and it fails quietly — your number
is just wrong.
Minor units. Stripe reports cents, so you divide by 100 — except for zero-decimal currencies like JPY and KRW, where you must not. Get this wrong and your yen revenue is 100× too small.
Quantity. A subscription item can have quantity > 1 for seat-based
pricing. Ignoring it undercounts every multi-seat customer.
Multiple items. One subscription can carry several items — a base plan plus
add-ons. Reading only items.data[0] misses the rest.
Status filtering
status=active excludes trialling, past-due and cancelled subscriptions, which
is what you want. See
what counts as an active subscription
for the reasoning, particularly around past-due.
Permissions
A restricted key with read on Subscriptions, Prices and Products is enough for this. Nothing needs write access. Full list: Stripe key permissions for reporting.
If you'd rather not maintain this
The code above is roughly what FRGMNT's Stripe adapter does — and then the same
toMonthly runs against Lemon Squeezy, Paddle and the rest, because the
normalisation is platform-independent. See
normalising subscription intervals in code,
or query it from the terminal.
Frequently asked
Which Stripe API endpoint gives you MRR?
None directly. You list active subscriptions with their price data expanded, normalise each item to a monthly amount, and sum. Stripe has no single MRR endpoint.
Do I need to paginate the subscriptions endpoint?
Yes. Stripe returns 10 items by default and 100 at most. Above 100 active subscriptions you must follow the has_more and starting_after cursor or your MRR will be silently truncated.
Read next
- Normalising subscription intervals in codeOne small pure function converts any billing interval to a monthly figure. Here it is, with the edge cases tha…
- Query your MRR from the terminalGet True MRR, today's cash and recent transactions as text or JSON from your shell, across Stripe, Lemon Squee…
- How to calculate MRR properlyThe correct method for monthly recurring revenue, the three mistakes that make most MRR figures wrong, and wha…