Skip to main content

Marketing Mix Model: split your budget across channels

The Marketing Mix Model answers the budgeting question that comes before attribution: given my channel spend and my revenue over time, what is each channel contributing, and how should I split the next budget? It regresses your daily channel spend against your daily revenue, then returns each channel’s incremental contribution, ROI, a diminishing-returns response curve, and a recommended budget reallocation. Because it works on aggregate daily totals, it also covers the channels bottom-up attribution cannot touch — linear TV, out-of-home, sponsorships, or any channel you only track as a daily cost total with no per-contact touch events. Run it from Insights → Media Mix Model in the dashboard, or over the API at POST /cdp/analytics/marketing-mix-model. No new instrumentation or storage is needed — it reads the same CDP event stream the attribution reports use. For full request/response schemas, see the CDP analytics API reference.

Where it fits: top-down vs bottom-up vs incrementality

Three measurement surfaces answer three different questions. Pick the one that matches the question: Use the mix model when you are allocating budget across channels, when a channel has no clean per-contact join, or when signal loss makes touch-level attribution thin. Use attribution when you need per-campaign or per-model credit inside the channels you send through Orbit. Use a holdout when you need causal proof for one specific campaign — the mix model estimates, a holdout tests.

Building a model

The report needs two aligned daily series: spend per channel and total revenue. You control how each is derived: Minimal call:
Practical window rules:
  • The fit needs at least 14 aligned days of spend and revenue — below that it refuses, and the dashboard shows the “Not enough history yet” state. Forty-five days + is where the fit starts to be trustworthy.
  • Missing days are treated as explicit zeros, not gaps — the model sees an evenly-spaced series, so a day with no spend is a real data point.
  • Both series must share one currency; the endpoint never converts FX.
  • More than 12 channels with recorded spend: the model fits the top 12 and flags truncated_channels: true. Pass an explicit channels whitelist to choose which 12.

Reading the response

The response concatenates per-channel rows with model-level fit figures. A worked example over a 60-day window with three channels:
Read the model-level fields as fit quality and context, per-channel rows as the decision:
  • r2 / rmse / mae — goodness of fit of the regression. Below roughly 0.5, treat every per-channel number as a hypothesis; above 0.8 the model is tracking revenue well. There is no separate confidence-interval output — r2 and the error terms are the confidence signal.
  • baseline_revenue_per_period — the per-day revenue the model expects with zero marketing: organic demand. The intercept stays visible so “we would have made it anyway” is explicit, not folded into channel credit.
  • total_attributed_revenue vs total_revenue — the share of revenue the model allocates to channels versus everything. A small attributed share means revenue is mostly baseline — or a spend channel with real effect is missing from the model.
  • contribution / contribution_share — modelled revenue from that channel over the window, and its share of the attributed total.
  • roicontribution / total_spend. Above, SMS returns ~2.9 per 1.0 of spend; direct_mail returns ~0.56 — it loses money on the fitted numbers.
  • coefficient — the raw regression weight in “revenue per unit of saturated spend.” It can come back ≤ 0 (multicollinearity or genuinely wasted spend); such channels are clamped to zero effect for contribution and reallocation, but the raw value stays visible so you can see it.
  • saturation_half_point — the adstocked spend level at ~63% of the channel’s maximum effect. Spending far above it buys little; it marks where the curve flattens.

The response curve

Each channel’s response_curve samples hypothetical steady per-day spend levels at 0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, and 2× its current average spend, with the modelled revenue at each. Because saturation is concave, marginal return shrinks with every point you move right. Read it as “expected revenue removed from the previous point”: SMS above gains ~141 going from 0 to 170/day, but only ~68 from 1020 to 1360. The 1× point is where you sit today; points below it show what you give up by cutting, points above show what further spend would add. A channel with no historical spend still gets an illustrative curve off a nominal 100/day base, so its shape renders once spend starts. total_recommended_budget defaults to your current average daily spend summed across channels, so the answer is “how to re-split what I already spend,” not “how much to spend overall.” Pass total_budget to model a bigger or smaller total. The allocator shifts every channel’s budget to the level where the marginal return is equal across channels — you always fund the channel with the highest next-dollar return. Channels with a non-positive fitted effect are funded zero, exactly like direct_mail above. Per channel, recommended_spend is the suggested per-day level, recommended_spend_delta is the shift versus today (positive = spend more), and recommended_contribution is the modelled daily revenue at that level. Caveats before moving real budget:
  • Correlation is not causation. The regression associates spend movement with revenue movement; it does not prove the spend caused it. Treat the split as a hypothesis and validate the big moves with a holdout test.
  • Confounding lives in the window. Promotions, seasonality, and price changes that land in the fitted window get absorbed into whichever channels moved at the same time. Refit periodically rather than trusting one vintage.
  • Fit quality gates trust. Low r2 or a window under ~45 days means the split is a rough guide, not a mandate.
  • Three channels can share the credit. Highly correlated channels split effect between themselves — compare coefficient values before deciding which one the model believes.

Prerequisites and gating

  • History: at least 14 aligned days of spend and revenue in the window; the endpoint returns 422 with status: "insufficient_data" below that, and the dashboard renders the history prompt.
  • Data shape: spend events must carry both the channel property and the cost property; revenue must arrive on the named event with a numeric value property. Days with no matching events count as explicit zeros.
  • Role and scope: an owner, admin, or developer key with the contacts:read scope — the same gate as the funnel and attribution read surfaces, since the model reads the PII-adjacent event stream.
  • Rate limit: 20 fits per minute per tenant — the fit is heavier than a plain read.