AI decisioning
AI decisioning answers one question per profile: given the actions you could take right now, which one is most likely to convert? You pass the candidate actions — each a message variant on a channel, optionally pinned to a send hour, with its observed results so far — and the decision endpoint returns the best pick plus every candidate’s score. This guide covers what the engine decides, what you feed it, how to read the output, the dashboard tester, and when decisioning is the right tool versus a campaign-triggered flow or a plain segment. The same endpoint backs the dashboard page at Audience → Decisioning. The UI and the API run the same decision.1. What AI decisioning is
Each call toPOST /api/v1/cdp/decisioning/decide makes one decision for one profile: which action to take next. An action is the combination of three things:
- Variant — which message variant to send (
subject_a,winback_v2). - Channel — where to send it (email, SMS, WhatsApp, push).
- Send hour — when to send it, as a local hour 0–23 (optional; an action with no hour is available at any time).
Offer decisioning solves eligibility — active windows, per-contact caps, budgets, exclusivity groups, segment and cart gates — and can hold out a control cohort so you measure incremental ROI. Use it when the question is “which promotion”, and AI decisioning when the question is “which variant, on which channel, at what time”. Recommendations ranks a product catalog, not message actions (see Decision output vs. the recommendations list).
2. Inputs the engine consumes
A decision request carries three kinds of input, all resolved by you before the call — the engine itself is stateless and stores nothing:- Candidate actions (
variants, 1–50 per call) — each with avariant_id, achannel, an optionalsend_time_hour, and the evidence that action has accumulated:trials(sends or impressions) andsuccesses(conversions). Build the candidates from your campaign setup; roll up the counts from your own event data — a conversion is an event you already track in the CDP, so the counts come out of your segments, computed traits, and event rollups rather than a new stored model. - Eligibility gates — an action marked
eligible: falseis never chosen, and aneligible_channelslist restricts the decision to channels the profile can actually receive on. Resolve consent and frequency-cap status for the profile first (the decisioning tester in the dashboard does this automatically — see the walkthrough below), then pass the allowlist so a suppressed channel is never the winning action. - Decision context — a
target_send_hour(when you intend to send; actions pinned to a different hour drop out of contention), apolicy(bayes_ucbdefault,ucb1, orepsilon_greedy), and optional tuning knobs (epsilon,exploration_z,prior_alpha,prior_beta). Most callers leave all of these at the defaults.
3. Decision output vs. the recommendations list
The response gives you the pick and the transparency to audit it:chosen— the action to take, ornullwhen nothing is eligible (for example a profile opted out on every channel). Always handlenull: the correct action then is to not send.explored—truewhen the bandit passed over the current best performer to gather evidence on a less-proven action. Expect occasional explore picks; that is how the engine discovers a new winner instead of locking onto an early leader.scores— the full ranking with each action’s estimated conversion rate (posterior_mean) and ranking score, so you can see why the winner won and what it beat.eligible_count/total_trials— how many actions were genuinely in contention and how much evidence the decision rested on.
bayes_ucb (the default) is deterministic and reproducible; ucb1 forces untried actions first; epsilon_greedy explores a fixed share of the time.
How this differs from POST /api/v1/cdp/recommendations/rank: recommendations answers “which products” — it ranks a product catalog by a profile’s affinities and returns a list (a top-N) with a per-factor breakdown, for personalizing a catalog or product grid. AI decisioning answers “which action” — it picks one winner per profile across variant, channel, and timing, and returns the audit breakdown around that single pick. A typical flow uses both: decide the action and channel here, then fill the message with ranked products from recommendations.
4. Dashboard walkthrough
Open Audience → Decisioning to run a decision interactively. The tester posts to the same endpoint, so what you see is what an integration gets.- Define the candidate actions. Each row is one action: a variant id, a channel from the channel picker, an optional send hour, and the trials and successes observed so far. Add or remove rows to match your campaign’s variants.
- Choose the policy. The policy selector defaults to
bayes_ucb; the advanced fields (epsilon,exploration_z, priors) map to the optional request parameters and can usually stay blank. - Narrow by profile and timing (optional). Enter a Profile id to resolve a real contact: the tester reads that profile’s consent and frequency-cap status and automatically holds out actions on channels the contact opted out of or where a frequency cap is exhausted — each exclusion is labelled with its reason on the result card. Set a Target send hour to decide for a specific hour; actions pinned to a different hour are excluded from the pick.
- Run the decision. The result shows the chosen action, whether the pick was an exploration, and the full scored table. When you set no target hour, the send-hour schedule panel shows the best action per send hour — the same timing gate the engine applies for a target hour, run across every candidate hour — so you can read a full day’s schedule in one view.
5. API usage
Decide the next-best action for one profile:target_send_hour: 18, subject_a (pinned to hour 9) is still scored and reported but cannot be chosen; the pick falls between the hour-18 email variant and the any-time SMS variant.
The feedback loop — record the outcome, fold it into the next call
Nothing travels back automatically: you decide, you send, you observe the conversion, and you increment the counts yourself. The loop for one profile:- Decide. The response’s
chosencarries thetrials/successesthe winner had at decision time. - Render and send. Read
chosen.variant_idandchosen.channel, build the message for that variant, and submit it through your campaign. When the response’schosenisnull, skip the send — the profile is out of contention on every eligible channel. - Fold the result back. Store the decision alongside its counts:
base_trials = chosen.trials,base_successes = chosen.successes. When the conversion arrives, your next call passestrials: base_trials + 1andsuccesses: base_successes + 1; when it never converts, onlytrialsmoves.
sms from eligible_channels — excluded actions still appear in scores with eligible: false, so the audit trail survives the suppression.
Sibling endpoints — runnable samples
The decision table at the top of this guide names two siblings; both are the same stateless, envelope-shaped kind of call as decisioning — decide, then act.Offer decisioning — POST /api/v1/cdp/offers/decide
data carries chosen, holdout, holdout_bucket (a deterministic 0–99 bucket per salt), plus evaluations, one row per candidate with eligible: false and ineligible_reason so you can audit why it was dropped.
Recommendations — POST /api/v1/cdp/recommendations/rank
data.recommendations sorted by score, each row with a per-factor factors breakdown (category / brand / tag / price_fit / popularity), plus weights. An experiment with per-variant weights returns deterministic A/B bucketing and echoes the assigned variant back. Feed it the decision the loop picked, and let it fill in the products for the chosen channel.
Errors and rate limits
Validation failures land on the standardVALIDATION_ERROR envelope, and the call surfaces it inline. The 422 is the shape a malformed body gets regardless of route, and the same envelope applies to the sibling endpoints. Two cases matter on an integrations page:
- Persisted
variantssemantics — the engine rejects a body without any actions, a channel outside the string bounds, or aneligible_channelsentry that is blank or longer than 64 characters. In every such case the 422 body below. eligible_channels: []— passing an empty allowlist does not make the request invalid; it narrows the decision set to zero. The call still returns200withchosen: nullandeligible_count: 0; handle that branch before sending, which is what the feedback loop above already teaches.
422 Error
RATE_LIMITED plus Retry-After:
retry_after (header or envelope) over your own doubling:
6. When to use what
A composed example: build a churn-risk audience with the predictive models, let decisioning pick the variant/channel/hour per member, hold the chosen variant out when a frequency cap blocks the channel, and send through your campaign.
See also
- CDP audiences — segments, computed traits, and activation
- CDP predictive models — churn, conversion intent, and value scores that feed your candidate pool
- Campaign end-to-end — executing the action decisioning picks
- Frequency caps — the cap status that feeds channel eligibility
- CDP API reference — full decisioning, offer, and recommendations endpoint contracts