Skip to content

Free Spins & Free Chips ​

Give your players free rounds in Slotty Labs games: free spins on the slot games (Slotty Slots and Popworks) and free chips on the other games. Each free round is played at a stake you choose; it costs the player nothing and nothing is debited from their balance. Only the winnings are paid, as cash or as bonus money, as you decide per offer.

You create offers in your dashboard and grant them to players in the dashboard or through the API.

Direct integrations

This page is for casinos integrated directly with Slotty Labs. If you reach our games through an aggregator, use your aggregator's free-round tools.

How it works ​

  1. Slotty Labs enables free spins and/or free chips for your casino. Until then, Free spins & chips in your dashboard says they are not enabled for your casino; contact Slotty Labs to enable them.
  2. Switch the feature on in the games you want (Games → game → Config → Bonus features, or the Bonus features matrix: see Operator Dashboard).
  3. Create an offer under Free spins & chips → Create, save it as a draft, then activate it (with a reason).
  4. Grant it to existing players: in the dashboard, or with POST /api/v1/wallet/offers/grants.
  5. The player opens one of the offer's games. The game shows the offer and plays the free rounds at the offer's stake.
  6. Follow it with the offer.* webhooks and the figures on the offer's page.

Welcome spins

Subscribe to the player.created webhook and grant your welcome offer when it arrives. It fires at the player's first launch, so the player exists and the grant is accepted.

Creating an offer ​

SettingMeaning
KindFree spins (slot games) or free chips (the other games)
NameFor your team only
TitleWhat players see in the game: up to 40 characters, without % < > & "
ReferenceOptional: your own reference for the offer, used as campaignRef in the API
GamesUp to 16 games of that kind; a game is usable when the feature is switched on there
Stake per currencyThe stake of each free round. A currency left empty is not offered. Stakes are checked against each game's bet limits
Free rounds per grantHow many free spins (or chips) one grant gives
ValidityHours a player has to use a grant
DatesWhen grants can be issued (no end date = open-ended)
BudgetThe most grants this offer can issue to real players (empty = no limit)
Per-player limitHow many grants of this offer one player can receive
PayoutCash (default) or Bonus: see below

An offer goes Draft → Active ⇄ Paused → Archived. Grants can be issued only while it is active and inside its dates. Pausing or archiving an offer stops new grants; grants already issued keep running until they are used, expire or are cancelled (the offer's page has Cancel all grants).

If Slotty Labs pauses one of your offers, the offer's history shows it with the reason, and only Slotty Labs can resume it: contact us. You can still archive it.

Frozen once active. After activation only the name, the end date, the budget (never below the grants already issued) and the per-player limit can change. To change anything else, duplicate the offer.

Payout: cash or bonus ​

  • Cash. Winnings are paid to the player's real (withdrawable) balance at once, even while the player has an open deposit bonus. A free round can win up to the game's own maximum win.
  • Bonus. Winnings go to the bonus balance and must be wagered (the wagering multiplier × the winnings) before they become cash. You set the wagering multiplier, how long the bonus money stays wagerable, the largest bet allowed while it is wagered, and optionally the most one grant can turn into cash (per currency).

UK licence: 10× wagering cap

If your casino holds a UK (UKGC) licence, bonus-paid offers are limited to 10× wagering. Cash payout is not affected. A grant call for a bonus offer above the cap for your current licences is refused with wagering_cap.

Cash winnings from offers, and bonus money that converts to cash, count as payouts in your GGR, the same way as your bonuses. The free rounds themselves move no money.

Granting ​

In the dashboard ​

On the offer's page, Grant takes up to 1,000 player ids (or a CSV), the currency, Real players or Test (play money), and a reason, and shows the result per player. Test in Demo link grants a test offer to your Demo link player, so you can try the offer in the game in play money.

Through the API ​

http
POST https://api.slottylabs.com/api/v1/wallet/offers/grants
Authorization: Bearer sk_production_...
Content-Type: application/json

{
  "campaignRef": "welcome-fs",
  "requestId": "crm-20261105-0001",
  "players": [{ "playerId": "ext-123", "currency": "USDC", "externalRef": "crm-77" }],
  "expiresAt": "2026-11-12T00:00:00Z"
}
FieldRules
campaignId or campaignRefThe offer: its id (UUID) or your reference
requestIdYour idempotency key: 8–64 characters (letters, digits, _ . : -)
players1–1,000 players, each { playerId, currency, externalRef? }: your player id (1–128 characters), one of the offer's currencies, and optionally your own reference for the grant (1–64 characters, unique per offer)
expiresAtOptional ISO 8601 time. A grant expires at the earlier of this and the offer's validity, and never less than 1 hour after the grant

The answer is 201 (or 200 for a replay):

json
{
  "success": true,
  "data": {
    "requestId": "crm-20261105-0001",
    "environment": "production",
    "granted": 1,
    "refused": 0,
    "results": [
      { "playerId": "ext-123", "status": "granted", "grantId": "0f6b7a52-3c2e-4d7f-9a51-1b2c3d4e5f60", "reason": null }
    ]
  }
}

Idempotency. Sending the same requestId with the same body again returns the first result: granted players come back replayed and nothing is granted twice (a player the first call refused comes back refused with reason not_granted). The same requestId with a different body is refused with 409 idempotency_conflict. A 503 with retryable: true (reason busy) means nothing was written: send the same request again.

A refused player does not fail the call. Their result has status: "refused" and a reason:

reasonMeaning
player_not_foundNo player with this id in your casino yet (a player exists from their first launch)
player_blockedThe player is suspended or excluded
per_player_limitThe player already holds the most grants of this offer
currency_not_offeredThe offer has no stake in that currency
budget_exhaustedThe offer's budget is used up (real-player grants)
duplicate_playerThe player is listed twice in this request
external_ref_takenThis externalRef is already used by another grant of this offer
not_grantedA replay: the original call refused this player

A refused call grants nothing: see Error Codes → Free spins & chips.

Real players and tests ​

Every grant has an environment:

  • Real players (production): played only in real-money sessions.
  • Test (sandbox): played only in play money: launch the player with demo: true (or use the Demo link), and nothing reaches their real balance. Test grants never count against the budget and are kept apart from real-player figures.

A sandbox API key grants and sees test grants only. Real-player grants through the API are enabled per casino by Slotty Labs: until they are enabled for your casino, every API grant is a test grant and the API lists and cancels test grants only, whatever key you use. Real-player grants can be made in the dashboard. The environment of the answer always says which kind you made.

Reading and cancelling ​

MethodPathReturns
GET/api/v1/wallet/offers/campaignsYour active and paused offers: id, reference, kind, games, free rounds, validity, dates, limits, payout and the stake per currency
GET/api/v1/wallet/offers/grantsGrants, newest first: { items, next }. Query: playerId, campaignId, status, limit (1–200, default 50), before (the previous page's next)
GET/api/v1/wallet/offers/grants/:grantIdOne grant
POST/api/v1/wallet/offers/grants/:grantId/cancelCancels an active grant. Body { "reason": "…" } (3–500 characters); returns the cancelled grant
GET/api/v1/wallet/offers/playerA player's open offers, for your lobby (active grants, paused ones included, soonest expiry first). Query: playerId, currency (optional)

A key reads and cancels only the grants of its own environment. Cancelling a grant that is no longer active is refused (409 grant_not_active); a round already under way still finishes.

The grant object ​

The grant calls, the reads and the offer.* webhooks all carry this object:

json
{
  "grantId": "0f6b7a52-3c2e-4d7f-9a51-1b2c3d4e5f60",
  "campaignId": "7d9e8f00-1111-4222-8333-444455556666",
  "campaignRef": "welcome-fs",
  "externalRef": "crm-77",
  "playerId": "ext-123",
  "kind": "freespin",
  "title": "10 welcome spins",
  "environment": "production",
  "currency": "USDC",
  "stake": "1.000000",
  "stakeMinor": "1000000",
  "quantity": 10,
  "used": 3,
  "remaining": 7,
  "games": ["slotty-slots"],
  "payout": { "mode": "cash" },
  "totalWin": "12.500000",
  "totalWinMinor": "12500000",
  "status": "active",
  "paused": false,
  "pausedReason": null,
  "startsAt": "2026-11-05T10:00:00.000Z",
  "expiresAt": "2026-11-12T00:00:00.000Z",
  "endedAt": null,
  "endReason": null
}
  • Money is a decimal string at the currency's precision (stake, totalWin) plus the same amount in integer minor units (stakeMinor, totalWinMinor). Amounts of different currencies are never added together.
  • payout is { "mode": "cash" } or { "mode": "bonus", "wageringMultiplier", "bonusExpiresHours", "maxBet", "maxBetMinor", "maxCashout", "maxCashoutMinor" } (maxCashout null = no limit).
  • status: active, completed (every free round used), expired (the expiry passed with free rounds left), cancelled, or forfeited (the player self-excluded or the account was closed).
  • paused (with pausedReason): the grant cannot be played right now, because Slotty Labs switched the feature off for your casino (not_entitled), none of the offer's games has it switched on (games_switched_off), or the player is suspended or on a timed exclusion (player_blocked). Its expiry keeps running, and it resumes by itself if the cause goes away before then.

A round that is already under way finishes even if its grant expires or is cancelled meanwhile (not if it is forfeited). If a free round is refunded (its round rolled back), the free round is given back to the grant.

Webhooks ​

EventSent when
offer.grantedA grant was issued
offer.completedThe player used every free round of a grant
offer.expiredA grant reached its expiry with free rounds left
offer.cancelledA grant was cancelled (dashboard or API)
offer.forfeitedA grant ended because the player self-excluded or the account was closed

The data of each is the grant object plus playerUuid (our internal player id). Test grants send them too: check data.environment. Subscribe under Settings → Webhooks; see Webhooks.

With the SDK ​

SDK version 1.9.0 or later has client.offers (reference):

typescript
import { SlottyClient, SlottyApiError } from '@slottylabs/sdk';

const client = new SlottyClient({ apiKey: process.env.SLOTTY_API_KEY!, webhookSecret: process.env.SLOTTY_WEBHOOK_SECRET! });

try {
  const res = await client.offers.grant({
    campaignRef: 'welcome-fs',
    requestId: 'welcome-ext-123',
    players: [{ playerId: 'ext-123', currency: 'USDC' }],
  });
  for (const r of res.results) {
    if (r.status === 'refused') console.warn(`${r.playerId}: ${r.reason}`);
  }
} catch (err) {
  if (err instanceof SlottyApiError && err.reason === 'not_entitled') {
    // Free spins are not enabled for your casino yet
  } else {
    throw err;
  }
}

Figures ​

The offer's page shows, per currency (real players and tests apart): grants, players, free rounds granted and used, the stake value used, winnings, paid as cash, added to bonus, and converted. Return (winnings ÷ free-play stake) is shown to you only, never to players.