SaaS Data Model

Overview

A B2B subscription software business modeled end to end — from the marketing that brings accounts in, through trials, subscriptions and seat expansion, to the product usage, support experience and billing that decide whether they stay. Recurring revenue is the spine: every plan change flows through an MRR movement (new, expansion, contraction, churn), while engagement, support and payment health all feed the retention story.

Example Questions

  • What is net revenue retention, and how much of it is expansion versus what contraction and churn take back?
  • Which acquisition channels pay back their cost fastest once you account for the revenue those accounts actually retain?
  • Do accounts with deeper product usage and better support experiences expand more and churn less?

Explore on canvas →

Account Data Mart

Account

Every customer account (a company), with the signals that frame the whole relationship: industry, size and region, the plan tier they are on, a revenue size band, the channel that acquired them, their success-manager owner, a product-health score, and where they sit in the lifecycle (trial, active, at-risk, churned). The dimension you slice the entire business by.

Fields

ColumnTypeAliasDescription
account_idSTRINGAccount IDPK. Unique account identifier.
nameSTRINGNameCompany/account name.
industrySTRINGIndustryIndustry vertical of the account.
employee_bandSTRINGEmployee BandCompany-size bucket by headcount.
plan_tierSTRINGPlan TierSubscription plan tier.
mrr_bandSTRINGMRR BandMonthly-recurring-revenue size bucket.
regionSTRINGRegionSales/geographic region.
acquisition_channelSTRINGAcquisition ChannelMarketing channel that sourced the account — blended-CAC join key.
signup_dateDATESignup DateDate the account first signed up.
csm_ownerSTRINGCSM OwnerCustomer success manager who owns the account.
health_scoreINTEGERHealth Score0–100 product-health composite.
lifecycle_stageSTRINGLifecycle Stagetrial / active / at-risk / churned.

Invoices Data Mart

Invoices

Every invoice and how it was paid — amount, tax, discounts and credits, payment status, and where it sits in the dunning cycle when a payment fails. This is where voluntary revenue meets involuntary churn: failed payments and collections stages that quietly erode the customer base.

This is the model's revenue mart, and it carries two figures on purpose: amount is what was billed on every invoice regardless of outcome, and net_amount is what was actually recognised — zero for a voided or written-off invoice, equal to amount otherwise. Any revenue total should sum net_amount, never amount. The four collection signals — status, dunning_stage, is_failed and paid_at — always agree with each other: a paid invoice always has a paid_at and never is_failed or a write-off, and is_write_off is exactly dunning_stage = 'write_off'.

Fields

ColumnTypeAliasDescription
invoice_idSTRINGInvoice IDPK. Unique invoice identifier.
account_idSTRINGAccount IDAccount billed. FK to Account
subscription_idSTRINGSubscription IDSubscription being billed. FK to Subscription
issued_atDATEIssued AtDate the invoice was issued.
period_startDATEPeriod StartStart of the billing period.
period_endDATEPeriod EndEnd of the billing period.
amountNUMERICAmountInvoice amount before tax. Booked on every status, including void and write-off — see net_amount for the recognised figure.
net_amountNUMERICNet AmountRecognised revenue: 0 when the invoice is void or written off, otherwise equal to amount. Sum this for revenue totals, not amount.
taxNUMERICTaxTax charged on the invoice.
statusSTRINGStatusPayment status of the invoice.
currencySTRINGCurrencyInvoice currency, aligned to the account's region.
discount_amountNUMERICDiscount AmountDiscount applied to the invoice, if any. Always a fraction of amount, never larger than it.
credit_appliedNUMERICCredit AppliedAccount credit applied to the invoice, if any.
dunning_stageSTRINGDunning StageCollections stage: none / retry_1 / retry_2 / final_notice / write_off.
paid_atDATEPaid AtDate the invoice was paid. Set if and only if status = 'paid'.
is_failedBOOLEANIs FailedFailed payment — involuntary-churn signal. Never true on a paid invoice.
is_write_offBOOLEANIs Write OffUncollectible invoice, exactly dunning_stage = 'write_off'. Use this to gate money rather than string-matching the dunning label.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account billed on this invoice.
Subscriptionsubscription_id = subscription_idN:1The subscription this invoice bills.

Marketing Spend Data Mart

Marketing Spend

Marketing investment by channel, campaign and day — cost, leads, and signups — attributed to the cohort of accounts each channel brings in rather than to individual accounts. The numerator and denominator of customer acquisition cost, and the starting point for payback analysis.

Fields

ColumnTypeAliasDescription
spend_idSTRINGSpend IDPK. Unique identifier for each spend record.
spend_dateDATESpend DateDay the spend was incurred.
channelSTRINGChannelAcquisition channel the spend targeted — attributed to the cohort of accounts acquired through it, not to individual accounts. FK to Account
campaignSTRINGCampaignCampaign the spend belongs to.
costNUMERICCostMoney spent — the CAC numerator.
leadsINTEGERLeadsLeads generated by the spend.
signupsINTEGERSignupsAccounts created — CAC denominator.

Relationships

Related data martOnCardinalityMeaning
Accountchannel = acquisition_channelN:NAccounts acquired through this channel — a cohort, not one account per row.

Plan Data Mart

Plan

The price book: every sellable plan and price point — four tiers (Starter, Pro, Business, Enterprise), each offered monthly or annually, with its list price and whether it is currently sellable. The reference that gives every subscription its list pricing.

Fields

ColumnTypeAliasDescription
plan_idSTRINGPlan IDPK. Unique plan/price-point identifier.
plan_nameSTRINGPlan NameHuman-readable plan name (tier + billing interval).
tierSTRINGTierProduct tier: Starter / Pro / Business / Enterprise.
billing_intervalSTRINGBilling IntervalBilling cadence: monthly / annual.
list_priceNUMERICList PricePublished list price for this plan at this interval.
currencySTRINGCurrencyCurrency of the list price.
is_activeBOOLEANIs ActiveWhether the plan is currently sellable.

Subscription Data Mart

Subscription

The billing spine: one row per subscription showing which account is on which plan, at what monthly recurring revenue and seat count, on what cadence, and with any negotiated discount. It also reveals where a subscription's plan has drifted from the account's current tier under grandfathered or discounted pricing. The source of truth for what each customer pays today.

Fields

ColumnTypeAliasDescription
subscription_idSTRINGSubscription IDPK. Unique subscription identifier.
account_idSTRINGAccount IDAccount that owns the subscription. FK to Account
plan_idSTRINGPlan IDPlan the subscription is billed on. May differ from the account's current tier under grandfathered pricing or a negotiated discount. FK to Plan
statusSTRINGStatusSubscription status: active / past_due / canceled.
seats_licensedINTEGERSeats LicensedNumber of seats licensed on the subscription.
mrrNUMERICMRRContracted monthly recurring revenue — the rate on the agreement, unchanged by cancellation.
active_mrrNUMERICActive MRRLive monthly recurring revenue: still contracted unless canceled, when it drops to 0. Equal to mrr for active and past_due subscriptions alike — a failed payment does not by itself stop the contract.
currencySTRINGCurrencyBilling currency, aligned to the account's region.
billing_intervalSTRINGBilling IntervalBilling cadence: monthly / annual.
started_atDATEStarted AtDate the subscription started.
current_period_startDATECurrent Period StartStart of the current billing period.
current_period_endDATECurrent Period EndEnd of the current billing period.
canceled_atDATECanceled AtDate the subscription was canceled, if it was.
discount_pctNUMERICDiscount %Negotiated discount fraction applied to list price, if any.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account paying for this subscription.
Planplan_id = plan_idN:1The plan this subscription is billed on.

Subscription Events Data Mart

Subscription Events

The movement history behind recurring revenue: one row per subscription change — new, expansion, contraction, reactivation, or churn — with the signed revenue and seat deltas and the running revenue after each change. This is what reconstructs the revenue waterfall and the retention rates the business lives or dies by.

Running revenue reconciles with each subscription's own contracted rate. A subscription still on the books finishes the series on that rate; a subscription that churned was carrying it in the period immediately before the churn and the series steps away from it — so a churn reads as revenue lost from a customer's full rate, not as a downgrade on the way up to it.

Fields

ColumnTypeAliasDescription
event_idSTRINGEvent IDPK. Unique subscription-event identifier.
account_idSTRINGAccount IDAccount the event belongs to. FK to Account
subscription_idSTRINGSubscription IDSubscription the event belongs to. FK to Subscription
event_tsTIMESTAMPEvent TimeWhen the subscription change occurred.
event_typeSTRINGEvent TypeMRR-movement type: new / expansion / contraction / reactivation / churn.
plan_fromSTRINGPlan FromPlan before the change.
plan_toSTRINGPlan ToPlan after the change.
mrr_deltaNUMERICMRR DeltaSigned MRR change — the MRR-movement waterfall. Always the step between two consecutive levels, so the deltas of a subscription add up to the movement in its recurring revenue over the period.
seats_deltaINTEGERSeats DeltaSigned change in seat count.
mrr_afterNUMERICMRR AfterTotal MRR after the change, reconciled with the subscription's contracted rate: a subscription with no churn in the period ends on that rate, and one that churned was carrying it immediately before the churn.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account whose recurring revenue moved.
Subscriptionsubscription_id = subscription_idN:1The subscription this change was made to.

Support Tickets Data Mart

Support Tickets

Every support interaction — priority, topic, satisfaction score, time to first response, and resolution time. Support experience is an early churn-risk signal: unhappy, slow-to-resolve accounts are the ones that quietly leave.

Fields

ColumnTypeAliasDescription
ticket_idSTRINGTicket IDPK. Unique support-ticket identifier.
account_idSTRINGAccount IDAccount that opened the ticket. FK to Account
user_idSTRINGUser IDUser who opened the ticket, if attributable. FK to User
opened_atTIMESTAMPOpened AtWhen the ticket was opened.
closed_atTIMESTAMPClosed AtWhen the ticket was closed.
prioritySTRINGPriorityTicket priority level.
categorySTRINGCategoryTicket topic/category.
csat_scoreINTEGERCsat ScoreCustomer satisfaction rating for the ticket.
first_response_minsINTEGERFirst Response MinsMinutes to first agent response.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account that opened the ticket.
Useruser_id = user_idN:1The seat that opened the ticket, when known.

Trials Data Mart

Trials

Every trial and how it ended — converted to paid or not — with when it started and expired, where it came from (self-serve, sales-assisted, product-led upsell), and the plan it was evaluating. The top of the funnel for new recurring revenue.

Fields

ColumnTypeAliasDescription
trial_idSTRINGTrial IDPK. Unique trial identifier.
account_idSTRINGAccount IDAccount running the trial. FK to Account
started_atTIMESTAMPStarted AtWhen the trial began.
ends_atTIMESTAMPEnds AtScheduled trial expiry.
converted_atTIMESTAMPConverted AtWhen the trial converted to a paid plan, if it did.
is_convertedBOOLEANIs ConvertedTrial-to-paid outcome flag.
trial_sourceSTRINGTrial SourceWhere the trial came from (self-serve, sales-assisted, PLG upsell).
requested_planSTRINGRequested PlanPlan tier the trial is evaluating.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account that ran the trial.

Usage (daily) Data Mart

Usage (daily)

Daily product engagement at the account-and-user level: active minutes, high-value actions taken, and how many distinct features were touched — the breadth signal for activation. The behavioral pulse that explains why accounts expand, stall, or churn.

Fields

ColumnTypeAliasDescription
usage_idSTRINGUsage IDPK. Unique daily-usage record identifier.
account_idSTRINGAccount IDAccount that generated the usage. FK to Account
user_idSTRINGUser IDUser that generated the usage. FK to User
usage_dateDATEUsage DateCalendar day of the usage.
active_minutesINTEGERActive MinutesMinutes the user was active in-product.
key_actionsINTEGERKey ActionsCount of high-value actions taken.
distinct_features_usedINTEGERDistinct Features UsedCount of distinct product features touched that day — activation breadth.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account that generated the usage.
Useruser_id = user_idN:1The seat that generated the usage.

User Data Mart

User

The people inside each account: one row per user seat, with role, seat type, when they were invited, and how recently they were active. Seat-level activity is what turns a licensed account into an adopted one — and idle seats are early signs of shrinking value.

Fields

ColumnTypeAliasDescription
user_idSTRINGUser IDPK. Unique user identifier.
account_idSTRINGAccount IDOwning account. FK to Account
emailSTRINGEmailUser's email address.
roleSTRINGRoleUser's role within the account.
seat_typeSTRINGSeat TypeType of seat assigned (e.g. full / viewer).
invited_atTIMESTAMPInvited AtWhen the user was invited.
last_active_atTIMESTAMPLast Active AtMost recent activity timestamp.
is_activeBOOLEANIs ActiveWhether the seat is currently active.

Relationships

Related data martOnCardinalityMeaning
Accountaccount_id = account_idN:1The account a seat belongs to.

Apply to your project

  1. 1

    Install the Import Model plugin

    One plugin, installed once, in your own OWOX workspace.

    Get the plugin →

  2. 2

    Import this model

    Point it at this bundle and it creates every data mart above, joins and all.

    Open the model →

  3. 3

    Plug in your data and destinations

    Connect your own sources and send the results where your team already works.

    Browse connectors →

4. Optional — customize as you wish. Rename a column, drop a mart, add your own: once it is imported it is yours, and nothing here syncs back.