API Contracts
From v1.0.0 (2026-07-24) the MMM Framework follows semantic versioning strictly. This page enumerates the surfaces that promise means something for: the public Python API, the model spec keys, the data formats, the persisted model formats, the sessions-store schema, the estimand schema, and the REST API. Each is pinned by a CI gate — a breaking change fails the build rather than shipping silently.
Python API surface frozen
The lean core package (pip install mmm-framework) exposes the modeling surface;
the complete reference is the Sphinx API docs.
The load-bearing entry points:
| Import | Role |
|---|---|
mmm_framework.BayesianMMM | The core model: build, fit() (NUTS/numpyro; approximate map/advi/fullrank_advi/pathfinder/laplace; exact smc), predict(), sample_channel_contributions(), predict_under(), evaluate_estimands() |
mmm_framework.MFFLoader | MFF long-format loading + validation → PanelDataset |
mmm_framework.Dataset / load_dataset | Role-tagged wide-table contract (non-MFF entry point) |
mmm_framework.load_example | Bundled example datasets ("national", "geo") |
mmm_framework.builders.* | Fluent config builders (ModelConfigBuilder, MediaChannelConfigBuilder, …) |
mmm_framework.serialization.MMMSerializer | Model persistence (see persisted formats) |
mmm_framework.analysis | MMMAnalyzer, MarginalAnalysisResult, contribution summaries |
mmm_framework.estimands | The Estimand schema + built-in registry (see below) |
mmm_framework.reporting.MMMReportGenerator | Client HTML readouts (Augur), interactive + pre-fit readouts |
mmm_framework.platform.sessions | The sessions store (projects, experiments registry, artifacts) |
mmm_framework.agents.fitting | build_model(spec, …) / build_and_fit(…) — the spec-driven build path (importable without the [agents] LLM extra) |
Config enum values are contracts too — they are serialized into specs and saved
configs. Frozen value sets include FitMethod
(nuts, map, advi, fullrank_advi, pathfinder, laplace, smc),
SaturationType (logistic, hill, michaelis_menten, tanh, none) and
AdstockType (geometric, delayed, weibull, none). New values may be
added in minors; existing values never disappear outside a major.
Model spec keys frozen
The agent (and any programmatic caller of build_model) describes a model as a JSON
spec. The authoritative write-side contract is the consumed-paths registry
(agents/fitting.py::unconsumed_spec_path / unconsumed_prior_path):
a write the builder would silently ignore is rejected up front with an explanation, and every
key the builder reads is registered. The registry — not any TypedDict — is the contract.
Top-level keys
| Key | Type / values | Meaning |
|---|---|---|
kpi | str | KPI variable name (required) |
kpi_level | national | geo | KPI granularity |
time_granularity | weekly | daily | monthly | Cadence (default weekly) |
media_channels | list of dicts | Per channel: name, adstock{type, l_max}, saturation{type}, measurement_unit (spend|impressions|clicks|other), spend_column/cpm/cpc. Written per-field (media_channels.<name>.<field>) |
control_variables | list of dicts | name + causal role (confounder | precision) |
specification | additive | multiplicative | Functional form |
trend | dict | type: linear|piecewise|spline|gaussian_process|none + knots/changepoints |
seasonality | dict | Fourier orders {yearly, monthly, weekly}; 0/absent = off |
likelihood | dict | {family, link?, params?}, default normal/identity |
media_prior_mode | coefficient | roi | Default media-prior parameterization (agent default roi) |
priors | nested dict | See prior paths |
inference | dict | method (validated), chains, draws, tune, target_accept, random_seed, metrics_draws |
dataset | schema dict | Role-tagged (non-MFF) load path — a DatasetSchema dict |
dataset_path | internal | Session-state field set by the upload / Data Studio flows, not a registry-writable spec key |
estimands | list | Declared Estimand dicts, realized at fit |
experiments / experiment_ids | list | In-graph calibration measurements / staged registry ids |
garden_ref / model_params | dict | Model Garden class reference / bespoke CONFIG_SCHEMA params |
latent_factors | list | Structural-model shared factors (structural specs only) |
skip_quality_gate | bool | Bypass the pre-fit data-quality gate |
dag_model_type / dag_spec | internal | Stamped by the DAG builder; not registry-writable |
Prior paths
Plain (non-DAG) models:
| Path | Shape |
|---|---|
priors.media.<ch>.{coefficient, adstock_alpha, adstock_theta, saturation_kappa, saturation_slope} | PriorConfig dict {distribution, params} |
priors.media.<ch>.roi | {median|mu, sigma} — LogNormal on raw ROI |
priors.controls.<cv>.{coefficient, allow_negative} | PriorConfig / bool |
priors.intercept.{mu, sigma}, priors.media_default.{roi_mu, roi_sigma} | floats |
priors.seasonality.*, priors.trend.* | floats (per-component scales) |
DAG-routed extension models (nested_mmm, structural_nested_mmm,
multivariate_mmm, combined_mmm) reject the plain groups and accept
priors.mediator.<name>.*, priors.outcome.<name>.* and
priors.cross_effect.<src>__<tgt>.* instead — names typo-checked against
the DAG's variables.
Data contracts frozen
MFF long format
One row per (dimension-tuple, variable). Eight required columns (names remappable per config):
Period, Geography, Product, Campaign,
Outlet, Creative, VariableName, VariableValue.
Values are parsed forgivingly (currency symbols, parenthesized negatives, both decimal
conventions, null tokens); anything unparseable is a hard MFFValidationError with
samples. Weekly cadence anchors the completeness grid to the start date's own weekday; gaps
warn at load and block at fit via the data-quality gate (unless
skip_quality_gate). See Data Requirements.
Role-tagged Dataset
The wide-table entry point: one tidy table + a DatasetSchema
(schema_version "1.0") binding columns to DatasetRoles —
target, predictor, control, indicator,
group, time, offset, weight,
trials, auxiliary. The first four map 1:1 onto the MFF roles;
the rest serve non-MMM model families. Both role enums are frozen — they are serialized
inside saved configs.
Persisted model formats frozen
MMMSerializer (format version 1.1) writes an atomic directory:
| File | Content |
|---|---|
metadata.json | Self-description: model version + format_version, shapes, channel/control names, model_kind, fit_method + approximate, declared estimands (+ schema version), experiments, garden_ref/model_class_qualname, dataset schema. Extended models add model_flavor: "extended" |
configs.json | Full ModelConfig/TrendConfig/MFFConfig dumps (JSON-readable ground truth) |
scaling_params.json | y_mean/y_std/media_max/control scaling |
trace.nc(.gz) | ArviZ posterior (netcdf; h5netcdf engine) |
model.pkl | Extended flavor only (cloudpickle, graph/trace stripped) |
run_metadata.json (written per fit by the agent's build_and_fit)
additionally embeds the full normalized spec, diagnostics, metrics and paths.
internal — it contains absolute local paths and is not a
shareable bundle.
Sessions store internal schema, stable semantics
Platform state lives in one SQLite file (MMM_SESSIONS_DB, default
package-local under mmm_framework/platform/): 22 platform tables (sessions,
artifacts, projects, experiments, run_metrics, garden models, preferences, budget plans,
learning programs, …) plus 5 auth tables (organizations, members, tokens, invites, resets).
The schema may gain columns in minors (additive migrations only; downgrade
unsupported); the semantics exposed through the REST API are the contract.
Experiment lifecycle
The experiment registry's state machine is frozen:
draft → planned → running → completed → calibrated, with
abandoned reachable from any active state (and legacy terminal
cancelled). Illegal transitions are rejected (HTTP 409); every hop and
readout write appends to an audit history; changing a calibrated readout requires
overwrite_calibrated=true.
Estimand schema frozen
Estimand (schema version 1.0) is a declarative, serializable
counterfactual quantity: reduce( op( quantity | intervention, baseline ) ) / normalizer,
realized as mean + HDI with a pinned Realization (point rule + HDI method) that
keeps every number bit-stable against its pre-1.0 legacy implementation. The built-in
names are contracts (specs and saved models reference them):
contribution_roi, counterfactual_roi, marginal_roas,
contribution, plus demonstrators awareness_lift and
cost_per_conversion and the parametric constructors
(latent_scalar, fit_index, factor_loading).
Note contribution_roi (in-graph) and counterfactual_roi (zero-out
counterfactual) are different numbers by design.
REST API frozen
The server package (mmm-framework-server) exposes 165 paths / 200 operations,
fully documented on the REST API reference (generated from the
live OpenAPI schema; machine-readable at shared/openapi.json).
The (method, path) set is snapshot-pinned: removals or renames are major-version events.
The CI gates
Every contract above is enforced by a test:
tests/test_api_contracts.py— REST route snapshot (tests/contracts/rest_routes.json), public Python symbols, consumed spec/prior paths, frozen enum values, built-in estimand names.tests/test_lean_imports.py— the lean-core invariant: the whole core import surface works with the web/LLM stacks import-blocked.tests/test_estimands.py— bit-stability of estimand arithmetic vs the legacy implementations.tests/test_docs_snippets.py— every Python snippet on this site references real APIs.
Stability caveats
- Experimental modules (
mmm_extensions,continuous_learning,garden/Atelier,dag_model_builder, non-MMM families) may change in minor releases — see the stability grid. - The sessions DB has no schema-version stamp; migrations are additive and forward-only. Back up before downgrading a deployment.
run_metadata.jsonandgarden_ref.source_pathembed absolute local paths — treat model run directories as internal artifacts.- Estimand display labels derive from the estimand name — renaming a built-in is a breaking change by definition.
Related: Architecture & flow diagrams · REST API reference · Versioning & changelog · Getting started