# SaaS Metrics & Cohort Retention Report (MRR, Churn, NRR) (`cleancellco/saas-metrics-cohort-report`) Actor

Turn a subscription export (CSV, JSON or dataset) into monthly MRR bridge, churn, NRR, ARPA and cohort retention, plus a formatted Excel report. Pure calculation: no scraping, no logins.

- **URL**: https://apify.com/cleancellco/saas-metrics-cohort-report.md
- **Developed by:** [Clean Cell Co.](https://apify.com/cleancellco) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SaaS Metrics & Cohort Retention Report (MRR, Churn, NRR)

Turn a subscription export into standard SaaS metrics: a monthly **MRR bridge** (new, expansion, reactivation, contraction, churn), **ARR**, **logo churn**, **gross MRR churn**, **net revenue retention (NRR)**, **ARPA** and **cohort retention** by signup month. You also get a formatted **Excel report** with charts and a heat-mapped cohort table.

Pure calculation. It doesn't scrape or log in to anything, and it doesn't call any third-party API, so a site change can't break a run.

### What you can feed it

- Use one data source per run. If you paste CSV text, link a CSV or pick a dataset, the SAMPLE demo rows in "Subscriptions (JSON)" are ignored automatically; you can also delete them.
- **JSON rows** pasted into the input, or **CSV text**, or a **CSV URL** for your own export (public http(s) link, max 50 MB; private, local and internal addresses are blocked), or an **Apify dataset ID**.
- One row per subscription period: `customer_id`, `start_date`, `end_date` (blank if still active), and either `mrr` or `amount` + `interval` (`month`, `quarter`, `year`, `week`). Annual plans are normalized to monthly.
- Column names differ? Use `fieldMapping`, e.g. `{"customer":"Customer ID","start":"Created","end":"Canceled At","amount":"Amount","interval":"Interval"}`. Map the columns from your billing export with `fieldMapping`; exports from different billing tools vary, so check the first run. Amounts must be in currency units (49.00), not cents (4900). The `currency` input is a label only; no conversion is done.
- Plan changes (upgrade or downgrade): end the old row and add a new row for the same customer, with the new row's `start_date` equal to the old row's `end_date`. The change is counted as expansion or contraction.

### What you get

| Where | Content |
|---|---|
| Dataset | One row per month with every monthly metric above (cohort retention is in `COHORTS` and the Excel report) |
| Key-value store `REPORT` | Excel workbook: Summary (KPIs + 2 charts), Monthly, Cohorts (logos), Cohorts (revenue) |
| Key-value store `COHORTS` | Cohort retention as JSON |
| Key-value store `OUTPUT` | Headline numbers: current MRR, ARR, active customers |

Example monthly row:

```json
{"month":"2026-03","startingMrr":250,"newMrr":0,"expansionMrr":50,"reactivationMrr":0,"contractionMrr":0,"churnedMrr":50,"endingMrr":250,"arr":3000,"customers":2,"logoChurnRate":0.3333,"netRevenueRetention":1.0}
```

### Definitions

- A customer counts as active in a month if subscribed at month-end.
- The MRR bridge is checked on every run: starting + new + expansion + reactivation − contraction − churn = ending. The run fails loudly if it doesn't balance.
- NRR = (starting MRR − churn − contraction + expansion) / starting MRR. New and reactivated MRR are excluded.
- Invalid input (bad dates, missing columns, negative MRR, end before start) stops the run with a clear message.

### Use it from code or AI agents

Call it through the Apify API, MCP or integrations (Make, Zapier, n8n). Schedule it monthly against a fresh export to keep an up-to-date metrics workbook.

### Pricing

Free during launch; pricing may be added later with notice.

### Privacy

Only customer IDs, dates and amounts are needed. Don't include names or emails. Use pseudonymous customer IDs. The data stays in your own Apify run storage and follows your plan's retention. The Actor makes no outbound calls except fetching your `csvUrl`, and it runs with **limited permissions**: it can only use its own run storages and a dataset you explicitly pick as input.

### Sample output

`OUTPUT` record:

```json
{
  "months": 9,
  "subscriptionRows": 5,
  "currentMrr": 280.0,
  "currentArr": 3360.0,
  "activeCustomers": 3,
  "excelReportKey": "REPORT"
}
```

A sample Excel report is in `samples/output/sample_report.xlsx`.

### Disclaimer

Calculated metrics for informational purposes only; not accounting, financial or investment advice. Verify the numbers before sharing them with investors or using them in financial statements.

Built with AI assistance.

# Actor input Schema

## `subscriptions` (type: `array`):

One object per subscription period: customer_id, start_date (YYYY-MM-DD), end_date (empty if active), and mrr OR amount + interval (week/month/quarter/year). The two SAMPLE rows are a demo: they are ignored automatically if you use CSV text, a CSV URL or a dataset, and you can also delete them.

## `csvText` (type: `string`):

Paste a CSV with the same columns (header row required).

## `csvUrl` (type: `string`):

Direct public http(s) link to your own CSV export (max 50 MB). Private, local and internal network addresses are blocked.

## `datasetId` (type: `string`):

Read subscription rows from one of your existing datasets (read-only access is granted to the selected dataset only).

## `fieldMapping` (type: `object`):

Map your column names, e.g. {"customer":"Customer ID","start":"Created","end":"Canceled At","amount":"Amount","interval":"Interval"}. Amounts must be in currency units (e.g. 49.00), not cents.

## `asOfDate` (type: `string`):

YYYY-MM-DD. Defaults to today.

## `currency` (type: `string`):

Label only; no conversion.

## `reportTitle` (type: `string`):

Title on the Summary sheet.

## `excelReport` (type: `boolean`):

Save a formatted .xlsx to the key-value store as REPORT.

## Actor input object example

```json
{
  "subscriptions": [
    {
      "customer_id": "SAMPLE-1",
      "start_date": "2026-01-05",
      "mrr": 49
    },
    {
      "customer_id": "SAMPLE-2",
      "start_date": "2026-02-10",
      "end_date": "2026-05-10",
      "amount": 588,
      "interval": "year"
    }
  ],
  "currency": "USD",
  "reportTitle": "SaaS metrics",
  "excelReport": true
}
```

# Actor output Schema

## `monthlyMetrics` (type: `string`):

One row per month: MRR bridge, ARR, customers, churn, NRR, ARPA.

## `excelReport` (type: `string`):

XLSX workbook with Summary (KPIs + charts), Monthly and Cohort sheets (omitted if excelReport=false).

## `cohorts` (type: `string`):

No description

## `summary` (type: `string`):

Current MRR, ARR, active customers, months, row count.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "subscriptions": [
        {
            "customer_id": "SAMPLE-1",
            "start_date": "2026-01-05",
            "mrr": 49
        },
        {
            "customer_id": "SAMPLE-2",
            "start_date": "2026-02-10",
            "end_date": "2026-05-10",
            "amount": 588,
            "interval": "year"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cleancellco/saas-metrics-cohort-report").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "subscriptions": [
        {
            "customer_id": "SAMPLE-1",
            "start_date": "2026-01-05",
            "mrr": 49,
        },
        {
            "customer_id": "SAMPLE-2",
            "start_date": "2026-02-10",
            "end_date": "2026-05-10",
            "amount": 588,
            "interval": "year",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cleancellco/saas-metrics-cohort-report").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "subscriptions": [
    {
      "customer_id": "SAMPLE-1",
      "start_date": "2026-01-05",
      "mrr": 49
    },
    {
      "customer_id": "SAMPLE-2",
      "start_date": "2026-02-10",
      "end_date": "2026-05-10",
      "amount": 588,
      "interval": "year"
    }
  ]
}' |
apify call cleancellco/saas-metrics-cohort-report --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cleancellco/saas-metrics-cohort-report"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/0ATrAkCbZQz3dNGk9/builds/KtRhOLemV9td7TjiE/openapi.json
