Skip to main content

Loyalty API

Loyalty is a points-and-tiers rewards program computed on top of the events you’re already sending to the Customer Data Platform — there’s no separate ledger table to sync. Points accrue from a contact’s behavioral CDP events per rules you define (e.g. 100 pts for account.created, 1 pt per $1 of order-completed), points burn through redemptions, and a contact’s balance + tier are recomputed on demand from that event history. Base path: /api/v1/loyalty Authentication: API key (X-API-Key) or session JWT. Reads require contacts:read; writes require contacts:write.

Program config

A program has earn rules and tiers:
  • perEvent rule — a flat point grant every time a named event fires (e.g. 50 points on review.submitted).
  • perUnit rulepoints = value_in_event_property × pointsPerUnit, rounded per the rule’s rounding mode (e.g. 1 point per $1 of an order’s total).
  • Tiers — an ordered ladder (BronzePlatinum), each with a lifetime-points threshold and operator-authored benefits copy. The base tier’s threshold is 0 so every contact always has a current tier.
  • pointsExpiryDays — optional; when set, points lapse this many days after they’re earned. Omit for points that never expire.
A tenant that hasn’t configured its own program still gets a sensible default: a welcome bonus, points-per-dollar-spent, a review bonus, and a four-tier Bronze→Platinum ladder with a 365-day expiry.
Node
POST /program (create) and GET /program return this same envelope — create responds 201, read includes program_source: "default" until you author a program. DELETE /program resets to the default and responds 204 with an empty body (the points ledger is untouched — only the config reverts).

Preview before you commit

POST /preview runs the accrual/tier engine against sample events you supply — either your current program or an override — with no database write. Use it to tune earn rules and tier thresholds before adopting them.
Node
Omit program to run against your saved (or default) program; send program to evaluate a candidate override. Either way the response projects the balance and tier those events would produce for one contact:

Members, balance, and redemption

GET /members lists every contact with loyalty activity, each with a computed balance and tier. Pages use limit (1–100, default 25) and offset as documented in Pagination — the members list is one of the offset-based endpoint families.
Node
GET /members/{contactId} returns one contact’s full state — available points, lifetimeEarned (drives tier; redemptions never lower it), expiringSoon, and nextExpiryAt.
Node
POST /members/{contactId}/redeem atomically debits points — concurrent redemptions against the same contact can’t overspend a shared balance. It returns 409 INSUFFICIENT_POINTS when the requested amount exceeds the available balance.
Node
A successful redemption responds 201 with the post-debit state:
When the requested amount exceeds the balance, the error body names both figures so you can surface them to the customer:
POST /members/{contactId}/adjust is the operator override: {"direction": "credit", "points": 200, "reason": "Customer service gesture"} grants points directly; {"direction": "debit", ...} removes them through the same overspend-safe path as redemption. Credit:
Node
Debit: send "direction": "debit" with the same points / reason / reference fields. A debit responds 201 with direction: "debit", the deducted points, and the post-debit state; it returns the same 409 INSUFFICIENT_POINTS envelope as redeem when the balance can’t cover the deduction.

Segments and journeys

Every member’s computed state is exposed as traits — loyalty_points_balance, loyalty_lifetime_points, loyalty_tier, loyalty_tier_id — usable directly as entry conditions in Segments and flow triggers, e.g. loyalty_points_balance >= 500.

See also