Skip to content

Freeplays

Freeplays (free spins or tickets) are bets that an operator grants to a player. A player can spend a freeplay without a wallet debit. Each freeplay has a fixed stake, and optionally a fixed feature, set when the operator grants the package.

How It Works

Operators grant and revoke freeplay packages out-of-band, through the Operator API. The engine sends the currently available packages to your frontend as part of the normal session calls. There is no separate "claim" endpoint. Read freePlaysAvailable and spend a ticket with placeBet.

Reading Available Freeplays

freePlaysAvailable (an array of IFreePlayInfo) is included in:

typescript
const response = await login({ loginURL, launchToken });
if (response.success) {
  const freePlays = response.result.freePlaysAvailable ?? [];
}

Not echoed by placeBet

IPlaceBetReply does not carry freePlaysAvailable. See Keeping the List in Sync to update your local copy after you spend a ticket.

Each entry groups tickets that share a stake, a currency, and (optionally) a feature. A player can hold several entries at the same time:

typescript
interface IFreePlayInfo {
  currency: string; // ISO 4217 currency code
  stake: number; // Fixed stake amount for each free play
  count: number; // Number of free plays available
  feature?: string; // Optional feature to enter directly (e.g. "freespin")
}

Helpers

Two helper functions summarize the array. You do not need to reduce the array yourself:

typescript
import { getFreePlaysRemaining, getFreePlayStake } from '@hizi.io/engine-sdk';

const totalRemaining = getFreePlaysRemaining(freePlays); // sum of `count` across all entries
const firstStake = getFreePlayStake(freePlays); // first entry's `stake`, or undefined if none

TIP

getFreePlayStake only reads the first entry. If a player can hold packages at more than one stake or feature, do not rely on this helper alone. Iterate freePlaysAvailable yourself and build a picker.

Choosing a Freeplay

Sometimes more than one package is available, with different stakes, or a stake tied to a specific feature such as "freespin". Present the options so the player can pick which one to spend:

typescript
const options = freePlays.map((fp, i) => ({
  value: i,
  label: fp.feature ? `${fp.stake} · ${fp.feature} (${fp.count} left)` : `${fp.stake} (${fp.count} left)`,
}));

Spending a Freeplay

Call placeBet with useTicket: true, the ticket's own stake, and (if set) its feature as useTicketFeatureType:

typescript
const freePlay = freePlays[selectedIndex]; // e.g. { currency: 'EUR', stake: 100, count: 5, feature: 'freespin' }

const response = await placeBet({
  backendURL,
  token: sessionToken,
  stake: freePlay.stake,
  useTicket: true,
  useTicketFeatureType: freePlay.feature,
  config,
});

if (response.success) {
  const gameResult = response.result.result;
  // Handle exactly like any other placeBet result - multi-step rounds,
  // player choices, and wager/collect flows all still apply.
}

Still a normal round

A freeplay spin returns a normal IGameResult. It runs through the full game flow (multi-step rounds, player choices, wager/collect). The only difference is that the engine does not debit the stake from the player's balance.

Keeping the List in Sync

The reply from placeBet does not echo freePlaysAvailable. Spending a ticket does not update your copy of the array. Use one of two ways to keep it correct:

  1. Optimistic decrement. In your own client-side state, decrement the count of the entry you just spent. Drop the entry once its count reaches 0:

    typescript
    setFreePlays(prev =>
      prev
        .map(fp => (fp === freePlay ? { ...fp, count: fp.count - 1 } : fp))
        .filter(fp => fp.count > 0),
    );
  2. Resync from the engine. Call loadConfig() (or refresh()) again to pull the authoritative list from the RGS. Do this at minimum on every session refresh. Operators can grant or revoke packages while a game is open.

TIP

Prefer resyncing over trusting the optimistic decrement long-term. Treat the optimistic decrement as a fast local update between the moment the player spends a ticket and the next loadConfig()/refresh() call. Do not treat it as a permanent source of truth.

Next Steps

  • Game Flow - The full game cycle for a freeplay spin. It is the same cycle as any other bet.
  • Endpoints - placeBet parameter reference, including useTicket / useTicketFeatureType.
  • Types - Full IFreePlayInfo field reference.