Documentation overview

Loyalty Program

Your brand can run a full points-and-tiers loyalty program: customers earn points on their spending, climb named tiers, and redeem points for rewards from your promo catalog. Everything runs automatically once configured — points accrue nightly, tier changes can trigger journeys, and balances are available in every email.

Where: Brand Settings → Loyalty (/brand?tab=loyalty).

What counts as "spend" — set this deliberately

This is the most important decision in the program, and it differs by industry:

  • Commerce / retail: award on completed purchases. Handle refunds with manual adjustments so returned orders give their points back.
  • SaaS / subscriptions: award on cleared subscription payments.
  • Gaming & betting: award on wagers, never on deposits. Deposits are refundable — if deposits earned points, a player could deposit, collect points, and withdraw. Rates also differ by product: sportsbook stakes typically earn the full rate, while slots/casino turnover earns a fraction (the same balance cycles through slots many times over).

Earning is an explicit allowlist: nothing earns points unless you list it. That's deliberate — a catch-all rate would quietly start minting points on refundable money (deposits) the moment your data begins sending it. You list the exact event types that earn and the rate for each:

  • purchase at 1 point per 1 [your currency]
  • sportsbook_bet at 1, casino_bet at 0.1 — and deposit simply isn't listed, so it can never earn.

Event types are picked from what your data actually sends (with event counts), plus a custom option for events you haven't started sending yet. If your brand's industry is recognized, the editor suggests a starting shape with the reasoning attached — apply it, then edit the event names to match your own data.

Simulate before you commit

"Simulate on my last 12 months" replays your proposed rules over your real revenue history — the same math the nightly engine runs — and shows:

  • total points that would have been issued, and to how many customers,
  • how points split across your spend types (this is where you catch a deposit accidentally earning),
  • where customers would land across your tiers,
  • worst-case reward liability: if you tell it what one point is worth in rewards, it prices every issued point as redeemed. Reality is cheaper (not all points get redeemed), but the worst case is the number to budget against.

Nothing is saved or awarded by simulating.

Tiers, and what happens when you change them

Tier standing comes from lifetime earned points — redeeming a reward never demotes anyone.

When you edit tiers on a running program, you choose the change policy:

  • Grandfather existing members (default): raising a threshold never demotes someone who already earned the status. Recommended.
  • Re-evaluate everyone: the new thresholds apply strictly on the next nightly run, in both directions.

Using the program in your marketing

  • Tokens in any email, SMS or message: {{p.points_balance}}, {{p.loyalty_tier_name}}, {{loyalty.terms_url}}. Liquid works too: {% if loyalty.points_balance > 1000 %}…{% endif %}.
  • Journeys: trigger on the custom event loyalty_tier_changed to congratulate climbers (the event carries from, to and points).
  • Customer 360: each person's points and tier appear in their value profile.

Integrating redemption into your product

Your customers spend points inside YOUR product — an account page, a rewards tab, a "redeem" button at checkout. Your backend talks to three endpoints, authenticated with an API key that has the Loyalty scope (Settings → API keys). Server-to-server only: never ship the key in browser code.

1. Show the balance (account page header):

bash
curl -H "X-Api-Key: theaicmo_..." \
  "https://theaicmo.com/api/loyalty/balance?identity=anna@example.com"

Returns points, lifetime points, tier, and the program shape (all tiers + thresholds + terms URL) so you can render "2,071 pts · Silver — 7,929 to Gold".

2. List the rewards shop:

bash
curl -H "X-Api-Key: theaicmo_..." \
  "https://theaicmo.com/api/loyalty/rewards"

Returns every active promo-catalog item that has a points price (points_cost). Rewards without a points price stay out of the shop — they're for journeys and manual grants.

3. Redeem (when the customer clicks the button):

bash
curl -X POST -H "X-Api-Key: theaicmo_..." \
  -H "Content-Type: application/json" \
  -d '{"identity": "anna@example.com", "promo_id": "<reward id>",
       "idempotency_key": "order-2041"}' \
  https://theaicmo.com/api/loyalty/redeem

The catalog's points price is charged automatically (you can override with points). The response is the receipt: what was spent, the grant id, and the new balance. Safety built in:

  • Idempotent: retrying with the same idempotency_key returns the original receipt — never a second debit.
  • No overdrafts: insufficient balance answers 402 with the current balance and the required amount.
  • The reward itself is delivered through the promo machinery like any other grant, and a promo_granted event lands in your data — so a journey can follow up ("your free spins are live").

Terms & conditions

Loyalty programs normally publish terms (how points are earned, expiry, how changes are handled). Link yours in the editor and include it in messages as {{loyalty.terms_url}}. Whether specific disclosures are required for your market stays your call — encode what your license mandates as your own guardrail rules.

Point expiry

If you set an expiry window, points age out nightly on FIFO terms: redemptions are assumed to spend your oldest points first, and whatever earned points fall outside the window (and weren't spent) are debited with an expire entry in the ledger. Expiry reduces the spendable balance only — lifetime points and tier standing are never touched.

Multiple brands

Points are a brand relationship. Each brand configures its own program, and a customer shared between two of your brands holds a separate balance with each — Brand B can never spend points earned at Brand A. With one program on the account the API resolves the brand automatically; with several, pass client_id to say which brand you mean (the API answers brand_required rather than guessing). In multi-brand accounts, only events stamped with a brand accrue to that brand's pot.

Honest limits (current version)

  • Refunds/chargebacks don't auto-debit points yet — use manual adjustments.