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

# Propensity segments: rank by predicted score and save as an audience

> Turn the CDP's trained predictive scores (churn propensity, lifetime value) into saved segments — rank the scoring population, band it into tiers, save the top-N as a segment, and keep it current with the per-model activation schedule.

# Propensity segments

Propensity segments are saved segments whose membership comes from a predictive model's ranking rather than from filter rules. Instead of "contacts where lifecycle\_stage equals customer," the segment is "the top of the rankings the churn model or value model computed." You score a population with a trained model, the scores rank, and the top of that ranking becomes the saved audience.

The same surface backs the dashboard pages under **Audience → Propensity segments** and **Audience → Churn risk**. The API and the UI drive the same workflows.

## 1. How propensity segments differ from static segments

A static segment (a rule-based segment or a once-materialized list) says "membership was decided at the moment we saved." A propensity segment says "membership was decided by a model's ranking." The difference matters for both what the segment contains and how it stays current:

* **Origin of membership.** A rule-based segment's members match filter predicates. A propensity segment's members are the contacts the trained model ranked highest — the top-N of the scoring population.
* **What the segment answers.** A rule-based segment answers "who matches this condition?" A propensity segment answers "who is most likely to churn?" or "who is predicted to be worth the most?" — questions no field predicate alone can answer.
* **Staleness profile.** A rule-based segment with `auto_refresh = true` re-evaluates its filter on a schedule; its membership drifts only when the underlying attributes change. A propensity segment's snapshot drifts when the model's scores drift — re-training or re-scoring the model can reorder the ranking even if no contact attribute changed.

The saved segment itself is always a static snapshot (`auto_refresh = false`). Renewal is driven from the model side — the activation schedule or a manual re-activate re-materializes the current ranking, which is the propensity surface's equivalent of "recompute this segment."

## 2. Prerequisites

Before you create a propensity segment, the model that produces the score must be trained and its scoring population must be non-empty:

* **A trained predictive model.** Train `churn_propensity` or `lifetime_value` first via `POST /api/v1/cdp/predictive-models/:model/train`. An untrained model replies `422` with `status: "insufficient_data"` on both `/score` and `/activate`, and the dashboard's propensity surfaces show the same "not enough history" inline alert — the [predictive models guide](/guides/cdp-predictive-models) covers what counts as enough history for each model.
* **A non-empty scoring population.** Each model scores a fixed server-side predicate — still-active contacts for churn, contacts without a recorded value for CLV. If the predicate matches no one, the score and activate calls return `materialized: false` without persisting anything.

The bound input the model relies on is the trained fit itself (the learned coefficients), not a contact field you bind at request time. The features it reads — tenure, recency, message and event counts — are pulled from each contact's profile at score time, so a contact with no profile activity is simply low-ranking rather than excluded.

## 3. Create a propensity segment from a model output

Activation is one call. Pass the server-owned model key; the server re-scores the population and materializes the top-N as a new static segment:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/predictive-models/churn_propensity/activate \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "limit": 100,
    "name": "At-risk — top 100"
  }'
```

The body is minimal because the ranking logic lives server-side:

* `limit` (optional, 1–500) — how many contacts from the top of the ranking to save. Omit it to save the whole ranked population.
* `name` (optional) — the saved segment's name. A readable name matters here because the segment will surface in the segments list and the campaign / journey audience pickers under this exact label.

The response returns `materialized: true`, the created `segment`, `member_count`, and `activation_ready: true`. The segment is immediately visible in `GET /api/v1/contacts/segments` and targetable by campaigns. Use the `lifetime_value` key the same way to save a high-value cohort:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/predictive-models/lifetime_value/activate \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "limit": 100, "name": "Predicted high-value — top 100" }'
```

The dashboard's **Audience → Propensity segments** page calls this same endpoint when you click "Save top N as a segment" — the UI and the API materialize identically.

## 4. Read membership and the score histogram

Before you save the top-N, preview the ranking. A bare `score` call (no `contact_id`) returns the top-N plus `population_scored`, so you can size the audience before materializing it:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/predictive-models/churn_propensity/score \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "limit": 100 }'
```

The response ranks the population descending and echoes `count` (rows returned) against `population_scored` (total ranked) — that second number is the denominator the top-N limit is a slice of. Each returned row carries the model's score (`score` for a classifier, `predicted_value_cents` for the value model) per contact, which you read as the ranking histogram: the distribution of scores across the returned top-N tells you whether the top tier is meaningfully separated from the tail or whether the ranking is compressed.

Once a segment is materialized, read its membership the same way as any other segment — `GET /api/v1/contacts/segments/:id/members` returns the saved contact list, and the segment carries the prediction that produced it as a static label rather than a live score.

## 5. Use the segment in a campaign or activation

The saved propensity segment is a normal segment in the segments list — it appears in the campaign and journey audience pickers, can be exported through [audience export](/guides/audience-export-adhoc), and can be synced through [reverse ETL to a warehouse](/guides/reverse-etl-warehouse-exports). Target it exactly as you would a rule-based segment:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/campaigns \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Retention save-a-list",
    "segment_id": "<id returned from the activate call>",
    ...
  }'
```

Scoring decided who is in the segment; targeting it decides nothing about how a message is sent. The same propensity scores also feed the per-contact scores endpoint (`GET /api/v1/contacts/:id/scores`) that gate automations read when they condition on a contact's risk or value band.

## 6. Refresh cadence

A materialized propensity segment is a frozen snapshot until someone re-materializes it. Renewal happens on the model side, not the segment side:

* **Scheduled re-activation.** The per-model activation schedule (`PUT /api/v1/me/predictive-activation-schedule`) re-scores the model on an hourly / daily / weekly cadence and re-saves the top-N to a stable segment name. This is the propensity-surface equivalent of `auto_refresh` on a rule-based segment. The [predictive models guide](/guides/cdp-predictive-models) covers the schedule contract.
* **Manual re-activation.** POST `/activate` again with a new name. The new snapshot sits alongside the old one in the segments list — you see "At-risk — top 100" and "At-risk — top 100 (2026-08-31)" as separate named segments, and you can diff their membership.
* **The underlying scores also refresh on their own cadence.** The nightly scorer rewrites the persisted per-contact score fields (`propensity_score`, `ltv_estimate_cents`) every tick. A segment materialized today reflects tonight's ranking only when you re-activate; the scores the nightly job writes do not retroactively edit the frozen segment's membership.

Check the model's drift monitor (`GET /api/v1/cdp/predictive-models/:model/drift`) before trusting a segment materialized against an old fit — when the drift report shows the input distribution has moved, refresh the fit before you refresh the segment.

## 7. Failure modes

* **Model not trained.** A `422` with `status: "insufficient_data"` on score or activate means the fit could not be computed — fewer than 50 labelled rows, or a classifier with only one outcome class. Train the model once history lands, then retry.
* **Empty scoring population.** An activate call that returns `materialized: false` (HTTP 200) means the model's scoring predicate matched no one. The segment was not created; nothing to delete. Check that the predicate's base (still-active contacts, or contacts with no recorded value) actually has members — a fresh workspace often has none.
* **Empty histogram / low population\_scored.** A score call that returns `count: 0` or a small `population_scored` against your expected base is the same predicate-empty condition at the preview stage. The top-N cannot exceed the ranked population, so a top-100 on a population-scored 12 saves 12 contacts.
* **Stale fit behind the scores.** Long gaps between train and activate mean the ranking the segment was built on is an old fit. The drift endpoint surfaces this; re-train, then re-activate.
* **Segment exists but is empty in a picker.** A segment saved earlier can look fine by name and still send to no one if the snapshot is stale relative to where the scores have drifted — re-activate with a current fit rather than editing the segment by hand.

## Dashboard twin

The same endpoints back two dashboard pages under **Audience**:

* **Propensity segments** — the ranking preview, the top-N bound, the tier badges (high / growth / emerging on value, high / medium / low on churn), and the one-click activation button.
* **Churn risk** — a dedicated home for the churn model's at-risk ranking with the same save-as-segment flow.

Nothing the UI does is hidden from the API; the pages are a rendering of the same contracts.

## See also

* [CDP predictive models](/guides/cdp-predictive-models) — the model catalog, train/score/activate contract, and the per-model activation schedule
* [CDP audiences](/guides/cdp-segments) — rule-based segments, computed traits, and account scoring
* [Audience export](/guides/audience-export-adhoc) — exporting a materialized segment
* [Campaign end-to-end](/guides/campaign-end-to-end) — targeting the saved propensity segment
* [Segments API reference](/api-reference/segments) — the saved segment's contract (membership, exports, overlap)
