Skip to main content

Next-best-product recommendations

Orbit ships next-best-action (decisioning) and next-best-channel (channel optimizer) — the recommender on this page closes the third gap: next-best-product. Given one profile’s purchase and browse history and a candidate catalog, it ranks the items that profile is most likely to buy next and returns each pick with a blended score and a per-factor affinity breakdown. The Recommendations tester in the dashboard lets you exercise it interactively; the same ranking is exposed programmatically on the CDP API.

Section 1 — How affinity ranking works

The engine is pure content-affinity scoring. Every request carries its own inputs — it derives the ranking from the request body and stores nothing:
  • Candidate catalog — the products eligible for this ranking. Each item carries a product id and, optionally, a title, category, brand, unit price, free-form tags, a popularity prior in [0,1], and an in-stock flag. A request ranks at most 2,000 candidates; items with no product id, out of stock, in exclude_product_ids, or already purchased by the profile (when suppression is on — the default) are excluded before scoring.
  • Signals — the profile’s recent interactions: purchases, cart adds, wishlist saves, product views, and clicks. Each signal carries the category, brand, or product id it touched, a monetary value for purchases, and an age in days.
  • Profile traits (optional) — computed CDP affinities: a category-affinity map, a brand-affinity map, and a list of preferred tags. Traits merge with the behavioural signals, so an affinity the CDP already learned counts even when you do not restate it as an event.
  • Profile id (optional) — a contact id (cnt_…) or an external CDP profile id. The rank does not need the profile id — it derives from the entered signals — but providing it ties the ranking to a stored profile and, in an experiment, makes A/B assignment reproducible per profile.
Signals are weighted by intent — a purchase outweighs a cart add, which outweighs a view — and decayed by recency with a configurable half-life (default 30 days, so a month-old interaction counts half as much as a fresh one). Purchase values build the profile’s observed average spend, which the price-fit factor uses below.

The five factors

Each surviving candidate is scored on up to five factors, each normalized to [0,1], blended with relative weights: Two properties of the blend matter in practice:
  • Weights are relative. They need not sum to 1; the final score is a weighted average over the applicable factors. Set only the weights you want to change — every unset one keeps its default.
  • Only applicable factors are blended. A cold-start profile with no purchase history has no spend average and no tag preference. Instead of being penalized for absent evidence, its score is divided only by the factors that have any signal — so cold-start items rank fairly on what is known.
The result is deterministic: ties break by score, then popularity, then product id, so the same request always returns the same ordering.

Section 2 — The Recommendations tester

The tester (Audience → Recommendations in the dashboard) exercises the ranker end to end — nothing you enter is sent to a customer or persisted into the CDP; only your working draft is saved to your account so a refresh, a new session, or another device restores the grid you left.
  1. Enter the candidate catalog. Add one row per product: id, title, category, brand, price, comma-separated tags, and the in-stock switch.
  2. Enter the profile’s signals. One row per interaction: type (purchase, cart, wishlist, view, or click), the category/brand/product it touched, its age in days, and the purchase value where one applies.
  3. Optionally set the scope. A profile id ties the run to a stored profile — the tester resolves the id as you type and warns when no profile matches, so a mistyped id never passes for real. Limit caps the number of returned picks (default 10, at most 100); half-life days set the recency decay; the suppress-purchased switch removes items the signals say the profile already bought.
  4. Optionally tune factor weights. Leave a weight blank to keep the engine default; the others override it.
  5. Rank. The result table lists the picks in rank order with the blended score, plus the run metadata — candidates considered, excluded, returned, and the half-life in effect.

Reading the per-factor breakdown

Every row expands into the five factor scores that produced the blended score. This is how you explain a ranking rather than merely read it:
  • A pick with a high category but low brand score won on the profile’s interest in the category overall, not on a favourite brand — the typical “what should the carousel’s next slot be” answer.
  • A weak price fit on an otherwise strong pick tells you the item is priced away from the profile’s comfort zone; tune the price-fit weight and re-rank to see the sensitivity.
  • When every factor reads low but one item still ranks first, the ranking was decided by exclusion — the stronger candidates were out of stock, excluded, or already purchased — not by affinity. The excluded count above the table confirms it.

Section 3 — Using the ranker

The tester is the interactive front door; the same ranking is available programmatically at POST /api/v1/cdp/recommendations/rank with the same inputs and response shape (snake_case field names, full parameter detail in the CDP API reference). It is role-gated to owner, admin, or developer, like the other CDP compute surfaces. Typical uses:
  • E-commerce recovery flows. Rank a cart profile’s recent signals against the current catalog and render the top picks into an RCS or WhatsApp in-thread carousel — personalization for the abandoned-cart win-back message rather than a static best-seller list.
  • Loyalty and loyalty-store merchandising. Rank the reward or cross-sell catalog for a points-heavy member so the loyalty surface leads with items that match demonstrated affinities.
  • A/B measurement of ranking strategies. Pass an experiment with named variants, each with its own factor weights. Every profile is deterministically bucketed by a stable hash of the experiment key and profile id — the same profile always lands in the same variant — and the assigned variant is echoed back so conversions attribute to a strategy. No RNG, fully replayable.
The ranker only ranks — it never sends. Rendering the picks into a message and dispatching them belongs to the caller (the catalog send path or a campaign orchestrator), so the ranking can be exercised and measured without any outbound traffic.

Section 4 — Limits: what this is not

Scope the recommender deliberately — it is a scoring primitive, not a hosted recommendation service:
  • You bring the inputs. The engine stores nothing and reads no tables at request time. Rolling events up into signals and traits, and keeping the candidate catalog current (including the in-stock flag), is the caller’s job; the tester exists to make that visible.
  • Content-based affinity only. It scores category, brand, tag, price, and popularity matches. It does not learn embeddings, run collaborative filtering (“customers who bought X bought Y”), or model sequence. If your use case needs those, treat this ranker as the fast, explainable first layer and a full recommendation engine as the deep layer behind it.
  • Per-profile, per-request. One request ranks one profile against the supplied candidate set (60 requests per minute per tenant). It is not a batch pipeline that pre-computes a ranking for every profile on a schedule.
  • Deterministic, not trained. Because the blend is a fixed, inspectable computation over the inputs you supply, the per-factor breakdown is the explanation. Nothing retrains between requests; a ranking changes only when its inputs change.

Cross-references