Promos
Promos are the “marketing lever” for the money layer. Three shapes:
| Type | Effect | Example |
|---|---|---|
| bonus_multiplier | Multiplies bonus coins on the next purchase (e.g. 2x bonus for first-time buyers). | ”First purchase: 2× bonus” |
| coin_grant | Grants a flat number of bonus coins to eligible users (segment-scoped). | ”Welcome back: here’s 100 coins” |
| trial_ext | Extends the TwistCue+ trial or comps N free days on subscribe. | ”Extended 14-day trial” |
Monetization → Promos (/monetization/promos).
Common fields
Section titled “Common fields”| Field | Type | Notes |
|---|---|---|
| Code | string, unique, optional | Blank = auto-applied to matching users. Set = user must enter the code. |
| Type | enum | See above. |
| Config | JSON | Shape depends on type; see below. |
| Budget (coins) | int, optional | Cap total coins minted by this promo. Once hit, promo auto-deactivates. |
| Spent (coins) | int | Read-only. Live counter. |
| Window | {start, end} ISO datetimes | Promo active only during this window. |
| Segment | segment_id, optional | Restrict eligibility (e.g. “lapsed users who spent > $10”). |
Config schema per type
Section titled “Config schema per type”bonus_multiplier:
{ "multiplier": 2.0, "max_uses_per_user": 1, "package_skus": ["tc_coins_500_bonus100", "tc_coins_1200_bonus200"]}coin_grant:
{ "amount": 100, "max_uses_per_user": 1, "trigger": "manual_claim"}trial_ext:
{ "extra_days": 7, "applies_to_new_only": true}Budget & spent tracking
Section titled “Budget & spent tracking”Every mint against a promo increments spent_coins. When spent_coins ≥ budget_coins (if budget set), the promo flips to paused automatically. Finance sees the ceiling hit in the promo detail.
Redeeming a code
Section titled “Redeeming a code”Users with a code enter it in the mobile app Settings → Redeem code flow, which calls POST /v1/promos/redeem (public endpoint, not in this doc). Server-side:
- Verifies code exists and window is active
- Verifies user segment eligibility
- Verifies per-user uses remaining
- Mints coins via the ledger (or extends trial)
- Increments
spent_coins
Every promo create/update/delete/pause writes an audit log entry. Every redemption writes a CoinLedger entry with reason='promo_grant' and ref_id=<promo_id>: traceable back to the campaign.
Gotchas
Section titled “Gotchas”- Budget is coin count, not USD. A
coin_grantof 100 with budget 100000 = up to 1000 redemptions. - Deleting a promo with redemptions: API refuses (
in_use). Pause instead. - Windowed promos: the resolver checks
now()against the window on every attempted redemption. Timezone: UTC. - Code collisions: codes are unique across all promos. Pick namespaces like
WELCOME2026,RAMADAN25.
API calls
Section titled “API calls”| Action | Endpoint |
|---|---|
| List | GET /v1/admin/promos |
| Detail | GET /v1/admin/promos/:id |
| Create | POST /v1/admin/promos |
| Update | PUT /v1/admin/promos/:id |
| Delete | DELETE /v1/admin/promos/:id |
Full details in API: Monetization.