Appearance
Earn-and-spend flow (web)
Two halves. Earn: the rewards page reads campaign progress, which the server moves in reaction to scans, orders and profile events, and the customer claims a step's reward. Spend: a claimed reward is a discount, so spending it is a cart call, not a loyalty one.
Play the flow
shop.example.com/rewards
shop · rewards · scan
campaigns
Scan 3 products2 of 3 steps›
Place your first ordernot started›
500 ◉ your points balance
your rewards1 ready›
Page not designed yet — drawn from what the documented flow requires.
States
When a step counts as ready, why a claim is refused, and how a periodic campaign starts over, is the campaign step's lifecycle on Loyalty campaigns. The algorithm that moves progress is in campaign progress tracking.
API calls by stage
| Stage | Action | API call | Access |
|---|---|---|---|
| 1 | Campaigns with progress | GET /api/v1/user/loyalty/campaigns | Origin + Brand key + Customer |
| 1 | Points balance | GET /api/v1/user/loyalty/customer | Origin + Brand key + Customer |
| 2 | What did that event move? | POST /api/v1/brand/user/loyalty/campaigns/almost-completed — event_item at the top of the body | Origin + Brand key + Customer |
| 3 | One campaign | GET /api/v1/brand/user/loyalty/campaigns/:id | Origin + Brand key + Customer |
| 4 | Claim the step | PUT /api/v1/user/loyalty/milestone/redeem/:milestoneId/:claimed — campaign id, then the step's reward id | Origin + Brand key + Customer |
| 5 | Rewards held | GET /api/v1/user/discount/reward | Origin + Brand key + Customer |
| 6 | Which apply to this bag | POST /api/v1/user/discount/applicable | Origin + Brand key + Customer |
| 7 | Apply | PUT /api/v1/explicitDiscount — discounts[] | Origin + Brand key + Hub selected + Customer |
| 8 | Order | POST /api/v1/user/createOrder — the same discounts[] | Origin + Brand key + Hub selected + Customer |
Two naming traps on the claim
- The path says
milestone; it claims a campaign step.:milestoneIdis the campaign id and:claimedis the step's reward id. A campaign's steps are stored as itsrewards[]. - Its body skips the
payloadwrapper. Acollectiblecampaign requires{ "collectibles": [1, 2] }at the top level — one collectible id per step that has a collectible constraint, no duplicates. Other campaign types do not read it.