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

# Analytics (Revenue, Cost, MRR)

> Seven methods for reading revenue, cost, margin, billed, collected, and MRR numbers out of MarginFront

# Analytics

The analytics resource on the SDK is how you read finished numbers back out of MarginFront. Revenue, cost, margin, billed, collected, MRR. Same math the dashboard runs, same shapes.

All seven methods return plain TypeScript objects with typed fields. Money fields are `number` (not strings). If a margin can't be calculated (because revenue is zero), `marginPercent` is `null`, never `0` or `NaN`.

This page covers:

1. `analytics.revenue`: revenue, cost, margin, and the breakdown of where revenue came from
2. `analytics.costBreakdown`: cost sliced by agent, customer, signal, day, plan, and model
3. `analytics.agentEarned`: activity-only revenue, before any fees are added on
4. `analytics.invoiceTotals`: billed, collected, outstanding, overdue
5. `analytics.mrr`: last complete calendar month's billed total
6. `analytics.runRateMrr`: trajectory if the last 30 days keep going
7. `analytics.committedMrr`: contractual floor, regardless of usage

For the shape of every response type, see [Types Reference](/sdk/types-reference).

***

## Setup

```typescript theme={null}
import { MarginFrontClient } from "@marginfront/sdk";

const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY);
```

All seven methods are on `mf.analytics`. No separate import, no special setup.

***

## 1. `analytics.revenue`

**What it does in plain English:** gives you the whole revenue picture for a time window. How much you earned, how much it cost you, your margin, and where the revenue came from (per subscription, per pricing strategy).

This is the number you'd show on an executive dashboard. It includes usage-based revenue plus recurring fees, seat fees, and one-time fees (prorated if the subscription only partly overlapped the window).

### Signature

```typescript theme={null}
await mf.analytics.revenue(params: RevenueMetricsParams): Promise<RevenueMetrics>
```

### Parameters

| Name             | Type     | Required | Description                                         |
| ---------------- | -------- | -------- | --------------------------------------------------- |
| `startDate`      | `string` | yes      | ISO date, inclusive (e.g., `"2026-04-01"`).         |
| `endDate`        | `string` | yes      | ISO date, inclusive.                                |
| `customerId`     | `string` | no       | Filter to one customer. UUID, not your external ID. |
| `agentId`        | `string` | no       | Filter to one agent. UUID.                          |
| `signalId`       | `string` | no       | Filter to one signal. UUID.                         |
| `subscriptionId` | `string` | no       | Filter to one subscription. UUID.                   |

Any combination of filters can be mixed. No filter means org-wide.

### Response shape

```typescript theme={null}
interface RevenueMetrics {
  revenue: number;
  cost: number;
  margin: number;
  marginPercent: number | null;

  usageRevenue: number;
  recurringRevenue: number;
  seatRevenue: number;
  onetimeRevenue: number;

  eventCount: number;
  eventCountWithNullCost: number;

  bySubscription: SubscriptionRevenue[];
  byStrategy: StrategyRevenue[];
}
```

### Example

```typescript theme={null}
// Org-wide revenue for April
const metrics = await mf.analytics.revenue({
  startDate: "2026-04-01",
  endDate: "2026-04-30",
});

console.log(`Revenue: $${metrics.revenue.toFixed(2)}`);
console.log(`Cost: $${metrics.cost.toFixed(2)}`);
console.log(`Margin: $${metrics.margin.toFixed(2)}`);

if (metrics.marginPercent !== null) {
  console.log(`Margin %: ${metrics.marginPercent.toFixed(1)}%`);
} else {
  console.log(`Margin %: not available (no revenue yet)`);
}

// Revenue for a single customer
const customerMetrics = await mf.analytics.revenue({
  startDate: "2026-04-01",
  endDate: "2026-04-30",
  customerId: "c4e8d1a2-1234-4567-8901-abcdef123456",
});
```

### What this returns, in plain English

* **`revenue`** total dollars earned in the window. Usage plus recurring plus seat plus one-time fees.
* **`cost`** total service cost (the bill from OpenAI, Anthropic, Twilio, etc.) for the events in the window.
* **`margin`** revenue minus cost.
* **`marginPercent`** the margin as a percentage. Comes back as `null` (shown as a dash in dashboards) when revenue is zero, so you never see a `0%` or `NaN%` that would be misleading.
* **`usageRevenue` / `recurringRevenue` / `seatRevenue` / `onetimeRevenue`** the four ways revenue can show up, split out. They add up to `revenue`.
* **`eventCount`** every event counted in the window.
* **`eventCountWithNullCost`** events that are in the count but whose cost couldn't be calculated yet (unknown model, no pricing map). A "needs attention" number. See [Tracking Events: NEEDS\_COST\_BACKFILL](/sdk/tracking-events#needs_cost_backfill--what-it-means).
* **`bySubscription`** one row per active subscription in the window, each with its own revenue/cost/margin breakdown.
* **`byStrategy`** one row per pricing strategy that contributed revenue, with the strategy's charge type, pricing model, and signal id.

***

## 2. `analytics.costBreakdown`

**What it does in plain English:** shows you where your money is going. Total cost in the window, sliced six different ways: by agent, by customer, by signal, by day, by pricing plan, and by LLM model. Optional period-over-period comparison.

This is the number behind a "cost explorer" dashboard. Find the expensive customer. Find the expensive model. Find the day your bill spiked.

### Signature

```typescript theme={null}
await mf.analytics.costBreakdown(params: CostMetricsParams): Promise<CostMetrics>
```

### Parameters

| Name                 | Type      | Required | Description                                                                |
| -------------------- | --------- | -------- | -------------------------------------------------------------------------- |
| `startDate`          | `string`  | yes      | ISO date.                                                                  |
| `endDate`            | `string`  | yes      | ISO date.                                                                  |
| `customerId`         | `string`  | no       | Filter to one customer.                                                    |
| `agentId`            | `string`  | no       | Filter to one agent.                                                       |
| `signalId`           | `string`  | no       | Filter to one signal.                                                      |
| `subscriptionId`     | `string`  | no       | Filter to one subscription.                                                |
| `includePriorWindow` | `boolean` | no       | When `true`, adds a `prior` field with same-length-previous-window totals. |

### Response shape

```typescript theme={null}
interface CostMetrics {
  cost: number;
  eventCount: number;
  eventCountWithNullCost: number;
  byAgent: CostByAgentRow[];
  byCustomer: CostByCustomerRow[];
  bySignal: CostBySignalRow[];
  byDay: CostByDayRow[];
  byPlan: CostByPlanRow[];
  byModel: CostByModelRow[];
  prior?: Omit<CostMetrics, "prior">;
}
```

Each breakdown row has the shape `{ <idField>: string | null, cost: number, eventCount: number }`. See [Types Reference](/sdk/types-reference) for the exact row types.

### Example

```typescript theme={null}
// Cost for April, with March comparison included
const cost = await mf.analytics.costBreakdown({
  startDate: "2026-04-01",
  endDate: "2026-04-30",
  includePriorWindow: true,
});

console.log(`Total cost: $${cost.cost.toFixed(2)}`);
console.log(`Events: ${cost.eventCount}`);

if (cost.eventCountWithNullCost > 0) {
  console.log(
    `Attention: ${cost.eventCountWithNullCost} events have no cost yet. ` +
      `Map their models in the dashboard.`,
  );
}

// Top 3 most expensive models
const topModels = cost.byModel.sort((a, b) => b.cost - a.cost).slice(0, 3);

for (const row of topModels) {
  console.log(`  ${row.model ?? "(unmapped)"}: $${row.cost.toFixed(2)}`);
}

// Trend vs prior window
if (cost.prior) {
  const delta = cost.cost - cost.prior.cost;
  const sign = delta >= 0 ? "+" : "";
  console.log(`Change vs prior window: ${sign}$${delta.toFixed(2)}`);
}
```

### What this returns, in plain English

* **`cost`** total service cost in the window.
* **`eventCount`** every event in the window.
* **`eventCountWithNullCost`** events whose cost couldn't be calculated. Counted, not dropped. Shows up as a "needs attention" indicator.
* **`byAgent` / `byCustomer` / `bySignal` / `byPlan` / `byModel`** cost broken out along each dimension. The id fields can be `null` when an event isn't yet attached to that dimension (e.g., an orphan event with no pricing plan).
* **`byDay`** cost per UTC calendar day, one row per day with activity.
* **`prior`** only present when you passed `includePriorWindow: true`. Same shape as the outer object, but for the same-length window immediately before `startDate`. Lets you show trend arrows without a second call.

***

## 3. `analytics.agentEarned`

**What it does in plain English:** tells you how much revenue your agents produced from actual activity, before any fixed fees are added on top. Usage events multiplied by their pricing strategy rates, summed up.

This is the earliest signal that something is working. It moves the moment an event fires, without waiting for an invoice to finalize. If you want to answer "are my agents doing billable work this week," this is the number.

### Signature

```typescript theme={null}
await mf.analytics.agentEarned(params: AgentEarnedParams): Promise<AgentEarned>
```

### Parameters

| Name             | Type     | Required | Description                 |
| ---------------- | -------- | -------- | --------------------------- |
| `startDate`      | `string` | yes      | ISO date.                   |
| `endDate`        | `string` | yes      | ISO date.                   |
| `customerId`     | `string` | no       | Filter to one customer.     |
| `agentId`        | `string` | no       | Filter to one agent.        |
| `signalId`       | `string` | no       | Filter to one signal.       |
| `subscriptionId` | `string` | no       | Filter to one subscription. |

### Response shape

```typescript theme={null}
interface AgentEarned {
  revenue: number;
  eventCount: number;
  bySubscription: AgentEarnedSubscriptionRow[];
  byStrategy: AgentEarnedStrategyRow[];
}
```

### Example

```typescript theme={null}
// How much did my agents earn from activity this week?
const earned = await mf.analytics.agentEarned({
  startDate: "2026-04-20",
  endDate: "2026-04-26",
});

console.log(`Activity revenue: $${earned.revenue.toFixed(2)}`);
console.log(`From ${earned.eventCount} events`);

// Per-agent earnings
for (const row of earned.bySubscription) {
  console.log(
    `  Sub ${row.subscriptionId}: $${row.revenue.toFixed(2)} (${row.eventCount} events)`,
  );
}
```

### What this returns, in plain English

* **`revenue`** activity-only revenue in the window. Usage events × their pricing strategy rate. No recurring fees, no seat fees, no one-time fees.
* **`eventCount`** events that produced revenue (events tied to a usage pricing strategy).
* **`bySubscription`** per-subscription breakdown: how much revenue each sub produced and how many events did it.
* **`byStrategy`** per-pricing-strategy breakdown: which specific rate earned what, and the total quantity that ran through it.

### When to use this vs `analytics.revenue`

Use `agentEarned` when you care about activity in near-real-time. Use `revenue` when you want the full picture including fixed fees and prorations. They'll give you different numbers for the same window, on purpose.

***

## 4. `analytics.invoiceTotals`

**What it does in plain English:** tells you what you actually invoiced and what your customers actually paid. Billed, collected, draft inventory, outstanding A/R, and a live overdue count.

This is the finance-team view. It reads realized amounts off of invoices, not a formula. Non-draft invoices reconcile to the revenue formula, so you can trust these numbers directly.

### Signature

```typescript theme={null}
await mf.analytics.invoiceTotals(params: InvoiceTotalsParams): Promise<InvoiceTotals>
```

### Parameters

| Name         | Type     | Required | Description                        |
| ------------ | -------- | -------- | ---------------------------------- |
| `startDate`  | `string` | yes      | ISO date.                          |
| `endDate`    | `string` | yes      | ISO date.                          |
| `customerId` | `string` | no       | Filter to one customer's invoices. |

### Response shape

```typescript theme={null}
interface InvoiceTotals {
  billed: number;
  collected: number;
  draft: number;
  outstanding: number;
  overdueAmount: number;
  overdueCount: number;
  invoicesSentCount: number;
}
```

### Example

```typescript theme={null}
const totals = await mf.analytics.invoiceTotals({
  startDate: "2026-04-01",
  endDate: "2026-04-30",
});

console.log(`Billed: $${totals.billed.toFixed(2)}`);
console.log(`Collected: $${totals.collected.toFixed(2)}`);
console.log(`Outstanding: $${totals.outstanding.toFixed(2)}`);

if (totals.overdueCount > 0) {
  console.log(
    `Overdue: ${totals.overdueCount} invoices, $${totals.overdueAmount.toFixed(2)} total`,
  );
}
```

### What this returns, in plain English

* **`billed`** total on invoices you issued in the window (status `issued` or `overdue`), counted by invoice date. Waiting on payment.
* **`collected`** cash that actually landed in the window: payments that went through with a payment date in the window, net of refunds. Counted by the day the money arrived, not the invoice date.
* **`draft`** total on invoices you generated but haven't sent yet. Inventory, not revenue.
* **`outstanding`** what's still open right now: invoices that are `issued` or `overdue` and not yet paid. A current-state number with no date window, always zero or more.
* **`overdueAmount`** total on invoices currently past due. Age-based, not window-based. Current state right now.
* **`overdueCount`** how many invoices are currently past due.
* **`invoicesSentCount`** how many non-draft invoices were dated in the window.

***

## 5. `analytics.mrr`

**What it does in plain English:** tells you how much you actually billed last complete calendar month. Monthly recurring revenue, based on invoices that went out.

This is the "book MRR" number. Stable across the month (doesn't change until the month rolls over), based on realized billing. If you want forward-looking MRR, use `runRateMrr` or `committedMrr` below.

### Signature

```typescript theme={null}
await mf.analytics.mrr(params?: MrrParams): Promise<Mrr>
```

### Parameters

All optional.

| Name             | Type     | Required | Description                 |
| ---------------- | -------- | -------- | --------------------------- |
| `customerId`     | `string` | no       | Filter to one customer.     |
| `subscriptionId` | `string` | no       | Filter to one subscription. |

No date parameters. The window is fixed: first of last calendar month to first of this month.

### Response shape

```typescript theme={null}
interface Mrr {
  mrr: number;
  arr: number;
}
```

### Example

```typescript theme={null}
const { mrr, arr } = await mf.analytics.mrr();

console.log(`MRR: $${mrr.toFixed(2)}`);
console.log(`ARR: $${arr.toFixed(2)}`);

// Per-customer MRR
const customerMrr = await mf.analytics.mrr({
  customerId: "c4e8d1a2-1234-4567-8901-abcdef123456",
});
```

### What this returns, in plain English

* **`mrr`** total billed across all invoices dated in the previous calendar month.
* **`arr`** annualized: `mrr × 12`. A convenience so you don't multiply in the UI.

***

## 6. `analytics.runRateMrr`

**What it does in plain English:** tells you what your MRR would be if the last 30 days of activity kept going at the same pace. Recurring fees, seat fees, and actual usage rolled forward.

This is the "current run rate" number. Useful when usage moves fast and last month's MRR is already stale.

### Signature

```typescript theme={null}
await mf.analytics.runRateMrr(params?: MrrParams): Promise<RunRateMrrResult>
```

### Parameters

Same as `analytics.mrr`: optional `customerId` and `subscriptionId`. No date window.

### Response shape

```typescript theme={null}
interface RunRateMrrResult {
  mrr: number;
  breakdown: RunRateMrrBreakdownRow[];
}

interface RunRateMrrBreakdownRow {
  subscriptionId: string;
  customerId: string;
  agentId: string;
  planId: string;
  recurring: number;
  seatBased: number;
  usage: number;
  total: number;
}
```

### Example

```typescript theme={null}
const { mrr, breakdown } = await mf.analytics.runRateMrr();

console.log(`Run-rate MRR: $${mrr.toFixed(2)}`);

// Top 3 contributing subscriptions
const top = breakdown.sort((a, b) => b.total - a.total).slice(0, 3);
for (const row of top) {
  console.log(
    `  Sub ${row.subscriptionId}: $${row.total.toFixed(2)} ` +
      `(recurring $${row.recurring.toFixed(2)}, ` +
      `seats $${row.seatBased.toFixed(2)}, ` +
      `usage $${row.usage.toFixed(2)})`,
  );
}
```

### What this returns, in plain English

* **`mrr`** projected monthly revenue if the last 30 days keep up this pace.
* **`breakdown`** one row per active subscription with its contribution split into recurring, seat, and usage parts. The three add up to `total`, and all the `total`s add up to `mrr`.

***

## 7. `analytics.committedMrr`

**What it does in plain English:** the floor. Tells you the monthly revenue your contracts guarantee, regardless of whether customers actually use the product. Recurring fees, seat fees, and usage minimums only.

Use this to answer "what's the least we'll bill next month?" By definition, committed MRR is always less than or equal to run-rate MRR (because run-rate counts actual usage, committed only counts minimums).

### Signature

```typescript theme={null}
await mf.analytics.committedMrr(params?: MrrParams): Promise<CommittedMrrResult>
```

### Parameters

Same as `analytics.mrr` and `analytics.runRateMrr`: optional `customerId` and `subscriptionId`.

### Response shape

```typescript theme={null}
interface CommittedMrrResult {
  mrr: number;
  breakdown: CommittedMrrBreakdownRow[];
}

interface CommittedMrrBreakdownRow {
  subscriptionId: string;
  customerId: string;
  agentId: string;
  planId: string;
  recurring: number;
  seatBased: number;
  usage: number; // minimum commitment × rate, not actual usage
  total: number;
}
```

### Example

```typescript theme={null}
const { mrr: committed } = await mf.analytics.committedMrr();
const { mrr: runRate } = await mf.analytics.runRateMrr();

console.log(`Committed MRR (floor): $${committed.toFixed(2)}`);
console.log(`Run-rate MRR (actual pace): $${runRate.toFixed(2)}`);

const gap = runRate - committed;
console.log(`Usage upside: $${gap.toFixed(2)}`);
```

### What this returns, in plain English

* **`mrr`** the monthly revenue your contracts guarantee. Recurring fees always apply. Seat fees use the booked seat count. Usage uses the minimum commitment on each pricing strategy (zero when there's no minimum set).
* **`breakdown`** same shape as `runRateMrr`'s breakdown, but the `usage` column uses the minimum-commitment floor instead of actual events.

***

## Field types at a glance

All dollar amounts are `number`, not `string`. Values come back at full precision. Round at render time, not in storage:

```typescript theme={null}
const metrics = await mf.analytics.revenue({ ... });

// Right: one round at the UI boundary
display(metrics.revenue.toFixed(2));

// Wrong: stored rounded numbers lose precision for downstream math
store(Number(metrics.revenue.toFixed(2)));
```

`marginPercent` is `number | null`. A `null` means "no revenue yet, so margin can't be computed." Render it as a dash, not as `0%`.

See [Types Reference](/sdk/types-reference) for the full interface definitions.

***

## When to call which method

| Question                                             | Method                    |
| ---------------------------------------------------- | ------------------------- |
| What did we earn this month?                         | `analytics.revenue`       |
| Where is our money going?                            | `analytics.costBreakdown` |
| How much activity in the last 24 hours?              | `analytics.agentEarned`   |
| What's in A/R?                                       | `analytics.invoiceTotals` |
| What was last month's MRR (stable reporting number)? | `analytics.mrr`           |
| What's our current run rate?                         | `analytics.runRateMrr`    |
| What's our contractual floor?                        | `analytics.committedMrr`  |

***

## Next steps

* **[Types Reference](/sdk/types-reference)**: every interface in one place
* **[Subscriptions with Revenue](/sdk/subscriptions)**: attach canonical revenue to a single subscription fetch
* **[Customers with Revenue](/sdk/customers)**: same, but for customers
* **[Tracking Events](/sdk/tracking-events)**: the write side. Send events in so these methods have something to read
