Tutorial: Churn Prevention
This tutorial walks through building a churn-prevention flow from a blank tenant. By the end you’ll have a working flow that scores customers by churn risk, qualifies the right retention offer, suppresses outreach for already-contacted customers, and improves with every recorded outcome. Business scenario: a telecom or SaaS provider wants to reduce monthly churn. The marketing team has three retention offers — a free month, a service upgrade, and a personal call. Each offer has different eligibility, different cost, and different effectiveness depending on the customer’s risk profile and tenure. The system has 24 hours to decide who gets which offer, and the business rule says no customer may receive more than one retention contact per week. What you’ll build:- A Customer schema with the fields the flow needs to score
- 3 retention offers, each with its own qualification rules and business impact
- A decision flow that combines propensity scoring, qualification gates, and a weekly contact cap
- An end-to-end test through
/recommendand/respond - A view into how the model learns from recorded outcomes
curl + jq (used here to capture IDs between steps).
Time: 25–30 minutes.
0. Set up your shell
Every request is tenant-scoped and authenticated with your API key. Export these once; the examples reuse them.1. Define the customer schema
The flow needs four fields to make a churn-aware decision: tenure (months), monthly spend, support tickets in the last 90 days, and current plan tier. It also needs acustomer_id column — the key the Enrich stage joins on at decision time.
ds_customer) you can populate via the Data → Schemas UI, a /api/v1/customers bulk insert, or a pipeline from any of the 80+ connectors. The id returned here is the schemaId the decision flow’s Enrich node references in step 5.
2. Define the three retention offers
Each offer represents a different retention play.priority encodes business preference when scores tie; businessValue (0–100) feeds the Impact term in ranking; revenueValue records the dollar figure the platform credits on a positive outcome. Offers are created draft by default, so set status: "active" — the Inventory stage only loads active offers.
3. Attach qualification rules
A qualification rule is a separate entity scoped to what it gates. Here each rule is anattribute_condition scoped to a single offer, at the eligibility stage (a hard pass/fail filter). The attribute names the enriched customer field — the Enrich stage loads schema columns under the customer. prefix, so the flow sees customer.tenureMonths, customer.monthlySpend, and so on.
attribute_condition operators: eq, neq, lt, lte, gt, gte, in, contains, exists, not_exists.
4. Add the weekly contact cap
The most common churn-prevention failure mode is over-messaging. Acustomer_total_cap policy caps all contacts to a customer within a period, so a free-month offer can’t be followed by an upgrade nudge the next day. Contact policies use a scope enum plus a per-ruleType config — this one is global (every candidate counts toward the cap).
periodType accepts daily, weekly, monthly, or alltime. Because this tutorial’s tenant runs only the retention program, a global cap is exactly “at most one retention contact per week.” To cap a specific category instead, create a Category entity, link offers to it via categoryId, and scope the policy with scope: "category" + scopeId: "<categoryId>".
5. Wire the decision flow
A decision flow is a versioned pipeline of typed nodes across three phases: Narrow (phase 1: inventory, enrich, qualify, contact policy), Score & Rank (phase 2), and Output (phase 3). Every node carries atype, phase, position, and a config validated against that node type. The flow must start with inventory, end with response, and contain exactly one score node.
qualify and contact_policy in mode: "all" evaluate every active rule/policy for the tenant.
Publish it to make it live for /recommend:
6. Run the recommendation
For a customer with long tenure, high spend, and recent support friction — the kind worth saving — call/recommend with the flow key. (This assumes you’ve populated ds_customer with a row where customer_id = 'cust_7'.)
offerName, score, rank, and priority; meta shows how many candidates survived each stage. To see why an offer won — the per-rule qualification reasons, the contact-policy status, and the propensity / relevance / impact / emphasis breakdown — add ?explain=true to the request. That adds an explanation object to each decision and a top-level rejectedOffers array listing anything the qualify or contact-policy stages dropped.
7. Record the outcome
When the customer accepts (or doesn’t), record the result so the model improves. Identify the offer withrecommendationId + rank from the recommend response, name a registered outcome (accept, convert, dismiss, … — the standard set is provisioned the first time you GET /api/v1/outcome-types), and pass an idempotencyKey (required, to prevent double-counting).
/respond records the interaction, rolls it into the customer’s contact-frequency summary (so the weekly cap sees it), and — when the tenant has an active adaptive model — updates that model’s scoped posterior for the offer.
8. Watch the model learn
Learning requires at least one active adaptive model (bayesian, thompson_bandit, epsilon_greedy, or online_learner). Create one via POST /api/v1/algorithm-models with status: "active", then point the Score node at it with "config": { "method": "propensity", "defaultModel": "<modelKey>" }.
With a model in place, each positive/negative /respond updates the offer’s Beta(α, β) posterior. The Model Health dashboard shows the Wilson confidence interval per scoped adaptation: it starts wide, and the maturity ramp holds an immature offer’s exposure below 1.0 until it has enough evidence. After roughly 30–50 outcomes the interval tightens below the tenant’s maturity-width threshold, the offer matures, and exposure goes to 1.0.
For the math (Wilson 1927 closed form, the width threshold, the cold-start floor decay), see Maturity Ramp (BCB-MR).
9. What’s next
- Add an uplift signal. Give the Score node a
formulawith anupliftWeightso ranking optimizes incremental response — see Cross-Sell for the full PRIE-U setup. - Tighten the cap for disengaged customers. Add an
engagementMultiplierblock to thecustomer_total_capconfig to shrink the cap for low-engagement customers and widen it for engaged ones. - Quiet hours. Add a
time_windowcontact policy so retention messages only land during the customer’s active window (see Winback). - Prove incremental revenue. Configure a flow-level holdout in
draftConfig.flowConfig.experimentand use the platform’s z-test uplift calculation to measure the program against a no-contact control.
See Industry Templates for ready-made starter kits, Starbucks NBA pipeline for a full retail walkthrough, or the API Reference for endpoint-by-endpoint details.