Appearance
Mini-Games
Run browser-based games inside your customer app that convert gameplay scores into loyalty points.
What It Is
A mini-game is a script the customer app loads and runs. Playing costs a life, finishing a run produces a score, and the score converts to loyalty points at a rate you set. Each brand configures its own games, rates and limits.
What You Can Do With It
Give customers a daily reason to open the app
Free lives refill every 24 hours, so there is always something to come back for without spending points.
Config: daily_base_lives: 2, lives_reset_ttl_seconds: 86400
Create a points sink
Let customers spend points on extra plays, pulling balance back out of circulation.
Config: points_per_life: 100, lives_per_purchase: 1, and a max_lives ceiling so they cannot stockpile.
Cap your exposure
Bound what any customer can earn, per run and per day, so a scoring bug or an exploit cannot drain your points budget.
Config: point_cap_per_session for one run, point_cap_per_reset_duration for the whole window.
Run a scoreboard competition
Rank customers by their best score for a game and reward the top of the board.
Config: nothing extra — highest_score is tracked automatically and the leaderboard endpoint returns rankings.
How It Works
Customer opens game
│
├─► Has a life available?
│ ├─ NO → must buy one with points (or wait for the daily reset)
│ └─ YES → one life is deducted, a session is created
│
├─► Customer plays, session records a score
│
└─► Score converts to points
├─ raw = floor(score × score_to_points_ratio)
├─ capped = min(raw, point_cap_per_session)
└─ final = min(capped, remaining allowance in the reset window)The Lives Economy
Customers hold two separate pools of lives.
| Pool | Field | Behaviour |
|---|---|---|
| Free daily lives | base_lives | Refills to daily_base_lives once lives_reset_ttl_seconds has elapsed (default 24 hours). Does not carry over — it is a reset, not an accrual. |
| Purchased lives | lives_redeemed | Bought with loyalty points. Carries forward indefinitely. |
A customer's available total is base_lives + lives_redeemed.
Free lives are spent first
Starting a session always consumes a free life before touching purchased ones, so customers never burn points while free lives remain.
Buying lives
Customers spend points to top up:
points_per_life— points charged per purchaselives_per_purchase— lives granted per purchasemax_lives— hard ceiling; a purchase is rejected if it would pushbase_lives + lives_redeemedabove this
The purchase is recorded in the customer's loyalty transaction history.
Score Conversion and Caps
Three settings control how generous a game is.
| Setting | Field | Effect |
|---|---|---|
| Conversion rate | score_to_points_ratio | Points per unit of score. The result is rounded down. |
| Per-session cap | point_cap_per_session | Maximum points a single run can award, regardless of score. |
| Window cap | point_cap_per_reset_duration | Maximum points awarded across the whole reset window. Points already awarded in the window are subtracted from what a new session can earn. |
A session therefore awards min(floor(score × ratio), point_cap_per_session, remaining window allowance).
Set auto_award_points to control whether points are granted automatically when a session completes, or held for processing.
Session Lifecycle
Each play creates a session row that moves through these states:
| Status | Meaning |
|---|---|
pending | Session created, score not yet submitted |
processing | Score received, points being calculated |
awarded | Points granted to the customer |
failed | Conversion or award failed |
Sessions retain the score and the points awarded, so a run can be audited after the fact.
Configuration Reference
| Field | Purpose |
|---|---|
name | Display name in the customer app |
active | Whether the game is playable |
src_script | The game script the app loads |
score_to_points_ratio | Points per unit of score |
points_per_life | Point cost of one life purchase |
lives_per_purchase | Lives granted per purchase |
daily_base_lives | Free lives granted each reset |
max_lives | Ceiling on total held lives |
lives_reset_ttl_seconds | Seconds between free-life resets (default 86400) |
auto_award_points | Grant points automatically on session completion |
point_cap_per_session | Maximum points from one run |
point_cap_per_reset_duration | Maximum points across one reset window |
Leaderboards
Each customer's highest_score is tracked per game, and their leaderboard position is returned alongside their profile. Only the best score is retained — individual sessions do not appear on the leaderboard.
Customer-Facing Endpoints
Available under both web and mobile route prefixes:
| Method | Endpoint | Purpose |
|---|---|---|
GET | mini-game/user | The customer's state for one game — lives, highest score, leaderboard position |
GET | mini-game/user/all | The same across every active game |
GET | mini-game/leaderboard | Ranked scores for a game |
POST | mini-game/session | Start a run; deducts a life and returns a session |
POST | mini-game/score | Submit a score for a session |
POST | mini-game/score-conversion | Preview the points a score would yield |
POST | mini-game/consume-points | Spend points to buy lives |
Best Practices
- Set the caps before launching.
point_cap_per_sessionis the only thing standing between a scoring bug and an unbounded point payout. - Model the daily economy.
daily_base_livesmultiplied by the per-session cap is the most points a customer can earn per day without spending — check that number against your margins. - Keep
max_livesclose todaily_base_lives. A high ceiling lets customers stockpile purchased lives and burst-earn against the window cap. - Use
activeto retire a game rather than deleting it, so historical sessions and leaderboard history stay intact.