Skip to content

Loyalty and campaigns ​

6. Points, transactions and referrals ​

Source: routes/v1/loyalty_route.js → loyalty_controller.js, plus referral_route.ts.

#EndpointAuthHandlerParams
6.1GET /api/v1/mobile/loyalty/customerCustomergetLoyaltyUsernone
6.2GET /api/v1/mobile/loyalty/point_transactionCustomergetUserPointTransactionquery limit (int ≥1), offset (int ≥0)
6.3GET /api/v1/mobile/loyalty/scan_transactionCustomergetUserScanTransactionquery limit, offset
6.4GET /api/v1/mobile/loyalty/scanned_productsCustomergetUserScannedProductsquery limit, offset, group_by (only strain)
6.5GET /api/v1/mobile/loyalty/brandCustomergetLoyaltyBrandnone
6.6GET /api/v1/mobile/loyaltyBrandgetLoyaltyBrandnone — same handler as 6.5, guest variant
6.7GET /api/v1/mobile/loyalty/referredCustomergetAllReferralsnone
6.8GET /api/v1/mobile/loyalty/referral-transactionsCustomergetAllLoyaltyTransactionsnone

When limit/offset are supplied on 6.2 the response becomes { count, limit, offset, transactions }; without them it is the bare array. 6.3/6.4 always paginate.

6.9 POST /api/v1/mobile/loyalty/refer ​

Auth: Customer · Handler: postLoginReferral · Body payload: referralCode ✅ (string).

6.10 PUT /api/v1/mobile/loyalty/milestone/redeem/:milestoneId/:claimed ​

Auth: Customer · Handler: redeemMilestoneReward Path: milestoneId (number) ✅, claimed (number) ✅. Body: not wrapped in payload — send { "collectibles": [1, 2, 3] } (array of numbers, optional).

6.11 POST /api/v1/mobile/loyalty/create-user-point-transaction ​

Auth: Customer · Handler: createUserPointTransaction Body payload: reason ✅ (string), and exactly one (xor) of points_used, points_earned, amount_spend, amount_earned (numbers). Optional: type, note, reason_id (strings).


6.12 Points ledger service ​

Some brands' points, transactions and levels are held by a standalone points ledger rather than by the customer backend. These four routes are the backend proxying that service, resolving it by loyaltyBrandId from the token and falling back to brandId. Auth: Customer on all.

EndpointHandlerParams
GET /api/v2/brand/mobile/loyalty/pointsgetUserPointsnone
GET /api/v2/brand/mobile/loyalty/transactionsgetUserTransactionsquery limit, offset
GET /api/v2/brand/mobile/loyalty/brand-levelsgetBrandLevelsnone
POST /api/v2/brand/mobile/loyalty/initinitUsernone — call once after signup to create the loyalty profile

Read them instead of 6.1–6.4 when the brand is on the ledger; the endpoints above answer for brands that are not. Nothing in the mobile set tells a client which it is — that comes from the brand's own configuration.

⚠️ These pass the ledger's body straight back (res.send(result)), so there is no { success, result } envelope around them, and no handleCatch turning a failure into a 200. Parse them on their own terms.


7. Loyalty campaigns ​

Source: routes/v1/loyalty_campaign_route.ts → loyalty_campaign_controller.ts

Most campaign endpoints come as a guest pair + customer pair: the customer one adds per-user progress. Campaigns resolve against loyaltyBrandId from the token, falling back to brandId.

7.1 List campaigns ​

  • GET /api/v1/brand/mobile/loyalty/campaigns — Brand — getAllLoyaltyCampaigns
  • GET /api/v1/brand/mobile/user/loyalty/campaigns — Customer — getAllLoyaltyCampaignsForUser

Query (all optional): level_id (int, guest variant only — the user variant takes the level from the token), tag (comma-separated, [A-Za-z0-9_-], ≤200 chars), type (comma-separated, same pattern), active (boolean).

7.2 Campaigns grouped by tag ​

  • GET /api/v1/brand/mobile/loyalty/campaigns/tag — Brand — getLoyaltyCampaignsByTags
  • GET /api/v1/brand/mobile/user/loyalty/campaigns/tag — Customer — getLoyaltyCampaignsByTagsForUser

No parameters.

7.3 Campaigns grouped by tag, with banner (newer, prefer over 7.2) ​

  • GET /api/v1/mobile/loyalty/campaigns/group — Brand — getLoyaltyCampaignsByTagsWithBanner
  • GET /api/v1/mobile/user/loyalty/campaigns/group — Customer — getLoyaltyCampaignsByTagsWithBannerForUser (adds overall progress)

No parameters. Note these two sit under /api/v1/mobile/..., not /brand/.

7.4 GET /api/v1/brand/mobile/loyalty/campaigns/category/:categoryName ​

Auth: Brand · Handler: getLoyaltyCampaignsByCategory · Path: categoryName (string) ✅.

7.5 Campaign by id ​

  • GET /api/v1/brand/mobile/loyalty/campaigns/:id — Brand — getLoyaltyCampaignById
  • GET /api/v1/brand/mobile/user/loyalty/campaigns/:id — Customer — getLoyaltyCampaignByIdForUser

Path: id (integer) ✅. ⚠️ The customer route is declared twice in the route file (identical); the second declaration is dead — see §23.

7.6 POST /api/v1/brand/mobile/user/loyalty/campaigns/almost-completed ​

Auth: Customer · Handler: getAlmostCompletedCampaigns Body is validated directly, not under payload:

json
{ "event_item": { "event_type": "collectible_id", "collectible": { "collectible_id": 12 } } }

event_item ✅ must match exactly one of these shapes:

event_typeCompanion keyShape
orderorderany ✅
scan-authproductany ✅
collectible_idcollectible{ collectible_id: number ✅ }
user_updateuser{ firstName?, dateOfBirth?, phoneNumber?, email? } ✅
user_loginuser{ mode?, source?, medium? } ✅

Auth: Brand · Handler: getTrendingCollectibles · Query: tag (comma-separated, optional).

7.8 GET /api/v1/brand/mobile/loyalty/collectible/:collectible_id/campaign ​

Auth: Brand · Handler: getCampaignsForCollectible · Path: collectible_id (integer) ✅.

7.9 Collectible campaign progress ​

  • GET /api/v1/brand/mobile/user/loyalty/collectible/:collectible_id/pre_campaign_progress
  • GET /api/v1/brand/mobile/user/loyalty/collectible/:collectible_id/campaign_progress

Auth: Customer · Both hit the same handler getCampaignsForCollectibleSecondLastStep with identical behaviour. Path: collectible_id (integer) ✅.

Mini-games ​

A mini-game is played in the app, scored server-side and converted into points. It is held by the same points ledger as 6.12, so these routes are the backend proxying it and the responses come back unenveloped in the same way. Source: routes/v2/miniGames_routes.ts → app/v2/controller/miniGames_controller.ts.

EndpointAuthHandlerParams
GET /api/v2/brand/mobile/mini-gamesBrandlistMiniGamesquery limit, offset
GET /api/v2/brand/mobile/mini-game/userCustomergetMiniGameUsernone
GET /api/v2/brand/mobile/mini-game/user/allCustomergetAllMiniGameUserquery limit, offset
GET /api/v2/brand/mobile/mini-game/leaderboardCustomergetMiniGameLeaderboardnone
POST /api/v2/brand/mobile/mini-game/sessionCustomerpostMiniGameSessionbody payload: mini_game_id ✅ (number) → returns session_id
POST /api/v2/brand/mobile/mini-game/scoreCustomerpostMiniGameScorebody payload: session_id ✅ (string), score ✅ (number ≥0)
POST /api/v2/brand/mobile/mini-game/score-conversionCustomerpostMiniGameScoreConversionbody payload: session_id ✅ (string) → converts the score to points
POST /api/v2/brand/mobile/mini-game/consume-pointsCustomerpostMiniGameConsumePointsbody payload: item ✅ (only "lives"), mini_game_id (positive int)

One round is three calls in order: open a session, submit a score against it, then run the score-conversion that credits the points. Buying extra lives is consume-points, and spends points rather than earning them. Both score and score-conversion validate session_id as a string — send it as one whatever type it arrives in.

Batch platform, Shopify app and API documentation