Appearance
Promotions Engine
The Promotions Engine delivers time-windowed marketing campaigns. Operators schedule campaigns; the engine computes each campaign's live status from its start/end dates and handles claiming, which grants a bonus through the Bonus Engine.
Public surface
| Member | Returns | Description |
|---|---|---|
getPromotions(options?) | Promise<Promotion[]> | Campaigns; ACTIVE by default, scheduled/expired on demand |
claim(promotionId, payload?) | Promise<PlayerBonus> | Claim a campaign; grants its linked bonus |
Typed structures
typescript
type PromotionStatus = 'SCHEDULED' | 'ACTIVE' | 'EXPIRED'; // computed from dates; never stored
// What claiming grants; the same union as bonus templates:
type PromotionRewardType = BonusTemplate['type']; // 'DEPOSIT_MATCH' | 'FREE_SPINS' | 'CASHBACK' | 'NO_DEPOSIT'
interface Promotion {
id: string;
name: string;
description: string;
rewardType: PromotionRewardType;
status: PromotionStatus;
templateId: string; // the Bonus Engine template granted on claim
startDate: string; // ISO-8601
endDate: string; // ISO-8601
imageUrl?: string;
claimed: boolean; // has THIS player already claimed it
minDeposit?: number; // deposit eligibility for deposit-linked rewards
}
interface GetPromotionsOptions {
includeScheduled?: boolean; // default false
includeExpired?: boolean; // default false
}
interface ClaimPayload {
depositTransactionId?: string; // explicit deposit linkage (see below)
}The reward's grant shape comes entirely from the linked bonus template (typed per variant; see the Bonus Engine); the promotion only adds the time window and eligibility values.
Computed status
text
GIVEN a campaign with startDate S and endDate E
WHEN the current time is before S / between S and E / after E
THEN its status is SCHEDULED / ACTIVE / EXPIRED respectively.
GIVEN a getPromotions() call without options
WHEN it resolves
THEN only ACTIVE campaigns are returned; scheduled and expired are opt-in
via includeScheduled / includeExpired (teaser and history UIs),
with deterministic ordering: startDate descending, then id.Claiming a campaign
Claiming is a cross-engine transaction: the Promotions Engine validates the campaign and hands off to the Bonus Engine, which grants the bonus from the linked template.
javascript
const bonus = await sdk.engagement.promotions.claim('promo_welcome_100', {
depositTransactionId: 'tx_123' // optional explicit deposit linkage
});
bonus.name; // '100% Welcome Bonus'
bonus.wageringRequirement; // from the template's wageringMultiplier
bonus.status; // 'ACTIVE'text
GIVEN an ACTIVE, unclaimed campaign whose eligibility is met
WHEN claim(promotionId, payload?) is called
THEN a bonus is granted per the linked template's variant rules,
the campaign's claimed flag flips for this player,
and the bonus appears in getActiveBonuses().
GIVEN a DEPOSIT_MATCH campaign with a campaign-level minDeposit and a linked
template with its own minDeposit
WHEN the effective minimum Dmin is needed
THEN Dmin = the GREATER of the campaign-level and template-level values.
GIVEN a DEPOSIT_MATCH campaign with effective minimum Dmin
WHEN claim is called with an explicit depositTransactionId
THEN that deposit is the linkage: if its amount ≥ Dmin and it completed
within the campaign window, the bonus grants
matchRate × depositAmount (capped at maxBonusAmount) in the deposit's
currency; otherwise the claim rejects with
ENG_PROMOTION_NO_QUALIFYING_DEPOSIT.
GIVEN a DEPOSIT_MATCH campaign with effective minimum Dmin
WHEN claim is called WITHOUT an explicit deposit
THEN the player's LATEST qualifying deposit within the campaign window
(amount ≥ Dmin) is the linkage; if none exists, the claim rejects
with ENG_PROMOTION_NO_QUALIFYING_DEPOSIT.
GIVEN an already-claimed campaign
WHEN claim is called again
THEN it rejects with ENG_PROMOTION_ALREADY_CLAIMED (one grant per player per campaign).Tournament-entry giveaways are not a promotion type: joining a tournament is optIn; the engines stay separate on purpose.
Error contract
| Code | Error class | HTTP status | Trigger condition | Retryable |
|---|---|---|---|---|
ENG_PROMOTION_NOT_FOUND | PromotionNotFoundError | 404 | Unknown promotionId | No |
ENG_PROMOTION_NOT_ACTIVE | PromotionNotActiveError | 409 | Claim on a non-ACTIVE campaign | No (until it starts) |
ENG_PROMOTION_ALREADY_CLAIMED | PromotionAlreadyClaimedError | 409 | Second claim by the same player | No |
ENG_PROMOTION_NO_QUALIFYING_DEPOSIT | NoQualifyingDepositError | 400 | No qualifying deposit in the campaign window | No (deposit first, then claim) |
ENG_PROMOTION_NOT_CLAIMABLE | PromotionNotClaimableError | 409 | claim on a CASHBACK campaign; the bonus grants automatically at each period close (see Bonus Engine), so CASHBACK campaigns are display-only | No |
Common errors are defined once in the Error Reference.
Operator-configurable values
| Value | Type | Default / range | Effect |
|---|---|---|---|
| Campaign catalog + schedules | per campaign | operator-defined | Which campaigns run, and when |
rewardType + linked template | per campaign | operator-defined | What claiming grants |
minDeposit eligibility | number | operator-defined | Deposit-linked campaign gating |
Which campaigns a player sees is decided by the backend per player; targeting is not part of the SDK surface. Status computation and the claim/grant rules are platform-fixed.
Sandbox
Seeds cover every scheduling state, including a campaign activating in seconds, so the SCHEDULED→ACTIVE transition is demonstrable without waiting: an ACTIVE deposit-match, an ACTIVE already-claimed campaign, an ACTIVE cashback campaign, a SCHEDULED teaser, and an EXPIRED entry for history UIs.