Skip to content

Promos

Promos are the “marketing lever” for the money layer. Three shapes:

TypeEffectExample
bonus_multiplierMultiplies bonus coins on the next purchase (e.g. 2x bonus for first-time buyers).”First purchase: 2× bonus”
coin_grantGrants a flat number of bonus coins to eligible users (segment-scoped).”Welcome back: here’s 100 coins”
trial_extExtends the TwistCue+ trial or comps N free days on subscribe.”Extended 14-day trial”

Monetization → Promos (/monetization/promos).

FieldTypeNotes
Codestring, unique, optionalBlank = auto-applied to matching users. Set = user must enter the code.
TypeenumSee above.
ConfigJSONShape depends on type; see below.
Budget (coins)int, optionalCap total coins minted by this promo. Once hit, promo auto-deactivates.
Spent (coins)intRead-only. Live counter.
Window{start, end} ISO datetimesPromo active only during this window.
Segmentsegment_id, optionalRestrict eligibility (e.g. “lapsed users who spent > $10”).

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
}

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.

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.

  • Budget is coin count, not USD. A coin_grant of 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.
ActionEndpoint
ListGET /v1/admin/promos
DetailGET /v1/admin/promos/:id
CreatePOST /v1/admin/promos
UpdatePUT /v1/admin/promos/:id
DeleteDELETE /v1/admin/promos/:id

Full details in API: Monetization.