Skip to content
Promotions / Bonus campaigns
Promotions

Bonus campaigns

Place a campaign for your players on games of one game provider: free spins, or a prize draw the player opens in the game. The game provider runs it; you name the players, the games and what sets it off.

Campaign types

typeWhat the player getsCarries
free_spinsA series of free spins at a fixed stakespin_count, bet_amount; optional campaign_limit, min_cashout_multiplier
fortune_wheelA wheel of 16 prizes: money or free spinsexactly 16 prizes, money or free_spins
treasure_chestA pick of 16 chestsexactly 16 prizes, money only
golden_dragonOne draw1 to 100 prizes, money only
buy_bonusOne bonus feature bought for the player, paid by the game providerbet_amount only: the price the game sells the bonus for

Not every game provider takes every type. Ask GET /v1/bonus-campaigns/options for the game first and offer only what it lists.

Place a campaign

POST

/v1/bonus-campaigns

Place a bonus campaign. Idempotent on reference.

SignedSandboxProduction
FieldTypeRequiredDescription
typestringyesfree_spins, fortune_wheel, treasure_chest, golden_dragon or buy_bonus.
namestringyesShown to the player in the game. Up to 100 characters.
currencystringyesISO 4217. A game provider may take bonuses in some currencies only; another answers BONUS_CAMPAIGN_REFUSED.
game_idsstring[]yes1 to 50 catalogue ids from GET /v1/games, all of one game provider.
player_idsstring[]yes1 to 1000 of your player references, up to 120 characters each. A campaign is never for everybody.
valid_fromstring (date-time)noWhen it opens. Now when absent.
valid_untilstring (date-time)yesIn the future and after valid_from, at most 90 days after it.
triggersobjectyesAt least one trigger. See Triggers.
spin_countintegernofree_spins only, required there: 1..10000 spins.
bet_amountstring (money)nofree_spins and buy_bonus only, required there: the stake of one spin, or the price of the bought bonus.Example: "0.20"
prizesobject[]noThe drawn types only, required there. See Prizes.
campaign_limitstring (money)nofree_spins: the total the campaign pays before it ends.
min_cashout_multiplierstringnofree_spins on crash games: the least multiplier a spin cashes out at, from 1.3 to below 100.
referencestringyesYour id for the campaign, up to 120 characters. A repeat under it answers the first campaign and places nothing twice.
request
{
  "type": "golden_dragon",
  "name": "Weekend dragon",
  "currency": "EUR",
  "game_ids": ["6ddd5f30-0b1c-4c52-9f3e-2a7c1b8e4d10"],
  "player_ids": ["p-1", "p-2"],
  "valid_until": "2026-10-31T00:00:00Z",
  "triggers": {
    "first_game_launch": true,
    "bet_amount": [{ "amount": "50.00", "single_bet": false }]
  },
  "prizes": [
    { "kind": "money", "amount": "5.00", "weight": 80 },
    { "kind": "money", "amount": "25.00", "weight": 20 }
  ],
  "reference": "dragon-42"
}
response 200
{
  "id": "4444c0de-…",
  "campaign_id": "bc_5f2e…",           // your wallet sees it as free_spins_id
  "type": "golden_dragon",
  "status": "active",                  // pending | active | ended | failed
  "name": "Weekend dragon",
  "currency": "EUR",
  "game_ids": ["6ddd5f30-0b1c-4c52-9f3e-2a7c1b8e4d10"],
  "player_ids": ["p-1", "p-2"],
  "valid_from": null,
  "valid_until": "2026-10-31T00:00:00Z",
  "triggers": {
    "first_game_launch": true,
    "bet_amount": [{ "amount": "50", "single_bet": false }],
    "spin_count": [],
    "loss_amount": null
  },
  "spin_count": null,
  "bet_amount": null,
  "prizes": [
    { "kind": "money", "amount": "5", "spins": null, "bet_amount": null, "weight": 80 },
    { "kind": "money", "amount": "25", "spins": null, "bet_amount": null, "weight": 20 }
  ],
  "min_cashout_multiplier": null,
  "campaign_limit": null,
  "reference": "dragon-42",
  "failure_reason": null,
  "created_at": "2026-10-02T12:00:00Z"
}

Possible errors

errorHTTPRetryWhen
VALIDATION_FAILED422noA field is missing, out of range or of the wrong JSON type, or the body is not valid JSON. Every broken field is listed in fields with the rule it broke; "body" when the JSON does not parse.
BONUS_CAMPAIGN_REFUSED409noThe game provider behind the games does not take bonus campaigns, or refused this one: a campaign type, trigger or currency it does not take, or a player it would not accept. message carries the reason.
GAME_UNAVAILABLE409noThe game is not in your window, whether or not it exists.

Every endpoint can also answer UNAUTHORIZED, ADDRESS_NOT_ALLOWED, RATE_LIMITED and UPSTREAM_UNAVAILABLE. See the Error reference.

Triggers

A campaign needs at least one. Each trigger that fires hands the bonus over once more: a player who opens the game and then reaches a bet threshold gets it twice.

TriggerShapeFires when
first_game_launchtrueThe player first opens one of the games during the campaign
bet_amount[{ "amount": "50.00", "single_bet": false }]The player's bets reach amount: one bet of at least that much with single_bet: true, bets adding up to it with false
spin_count[{ "min": 10, "max": 20 }]After a number of spins drawn between min (at least 1) and max (at least min)
loss_amount"20.00"The player has lost that much

Prizes

A drawn campaign pays one of its prizes. The chance of each is its weight over the sum of all weights; at least one weight must be above 0.

kindFieldsAllowed on
moneyamount (money), weightfortune_wheel, treasure_chest, golden_dragon
free_spinsspins (at least 1), bet_amount (money), weightfortune_wheel only
  • fortune_wheel and treasure_chest carry exactly 16 prizes; golden_dragon 1 to 100.
  • free_spins carries no prizes; the drawn types carry no spin_count.

What a game takes

GET

/v1/bonus-campaigns/options?game_id={id}

The campaign types and triggers the game provider of this game takes. What your campaign form should offer.

SignedSandboxProduction
response 200
{
  "game_id": "6ddd5f30-0b1c-4c52-9f3e-2a7c1b8e4d10",
  "types": ["free_spins", "fortune_wheel", "treasure_chest", "golden_dragon"],
  "triggers": ["first_game_launch", "bet_amount", "spin_count", "loss_amount"]
}

Empty lists: campaigns cannot be placed on this game.

Possible errors

errorHTTPRetryWhen
VALIDATION_FAILED422noA field is missing, out of range or of the wrong JSON type, or the body is not valid JSON. Every broken field is listed in fields with the rule it broke; "body" when the JSON does not parse.
GAME_UNAVAILABLE409noThe game is not in your window, whether or not it exists.

Every endpoint can also answer UNAUTHORIZED, ADDRESS_NOT_ALLOWED, RATE_LIMITED and UPSTREAM_UNAVAILABLE. See the Error reference.

Read a campaign

GET

/v1/bonus-campaigns/{id}

The campaign as it stands.

SignedSandboxProduction
statusMeaning
pendingAccepted, not yet confirmed by the game provider
activeLive for the named players
endedPast valid_until
failedThe game provider refused it; failure_reason says why

Possible errors

errorHTTPRetryWhen
NOT_FOUND404noNo such session, grant, bonus campaign, transaction or round for your operator.

Every endpoint can also answer UNAUTHORIZED, ADDRESS_NOT_ALLOWED, RATE_LIMITED and UPSTREAM_UNAVAILABLE. See the Error reference.

GET

/v1/bonus-campaigns?reference={reference}

Find a campaign by your reference.

SignedSandboxProduction

Possible errors

errorHTTPRetryWhen
NOT_FOUND404noNo such session, grant, bonus campaign, transaction or round for your operator.
VALIDATION_FAILED422noA field is missing, out of range or of the wrong JSON type, or the body is not valid JSON. Every broken field is listed in fields with the rule it broke; "body" when the JSON does not parse.

Every endpoint can also answer UNAUTHORIZED, ADDRESS_NOT_ALLOWED, RATE_LIMITED and UPSTREAM_UNAVAILABLE. See the Error reference.

GET

/v1/bonus-campaigns?player_id={player_id}

The campaigns of one player, newest first, at most 100.

SignedSandboxProduction

What a player has been given, for a support agent answering "where is my bonus": every campaign whose player_ids name this player, each as GET /v1/bonus-campaigns/{id} would answer it. A player in no campaign gets an empty list, not NOT_FOUND.

response 200
{
  "campaigns": [
    {
      "id": "6f1c2a7e-0b5d-4c1e-9a3f-2d8e4b7c9a10",
      "campaign_id": "6f1c2a7e-0b5d-4c1e-9a3f-2d8e4b7c9a10",
      "type": "free_spins",
      "status": "active",
      "name": "Weekend spins",
      "currency": "EUR",
      "game_ids": ["6ddd5f30-0b1c-4c52-9f3e-2a7c1b8e4d10"],
      "player_ids": ["player-1001", "player-1002"],
      "valid_from": "2026-10-02T12:00:00Z",
      "valid_until": "2026-10-09T12:00:00Z",
      "triggers": { "first_game_launch": true, "bet_amount": [], "spin_count": [], "loss_amount": null },
      "spin_count": 10,
      "bet_amount": "0.20",
      "prizes": [],
      "min_cashout_multiplier": null,
      "campaign_limit": null,
      "reference": "weekend-7",
      "failure_reason": null,
      "created_at": "2026-10-02T12:00:00Z"
    }
  ]
}

Send exactly one of reference and player_id: both, or neither, answers 422 VALIDATION_FAILED on the field query.

Possible errors

errorHTTPRetryWhen
VALIDATION_FAILED422noA field is missing, out of range or of the wrong JSON type, or the body is not valid JSON. Every broken field is listed in fields with the rule it broke; "body" when the JSON does not parse.

Every endpoint can also answer UNAUTHORIZED, ADDRESS_NOT_ALLOWED, RATE_LIMITED and UPSTREAM_UNAVAILABLE. See the Error reference.

Notes

  • A retry is safe. Repeat the same body under the same reference when the outcome is unknown; the answer is the first campaign. When you did not keep the id, look it up with GET /v1/bonus-campaigns?reference=.
  • There is no cancel. Game providers have no way to end a campaign early: set valid_until and player_ids with that in mind.
  • Spins a campaign hands over reach your wallet as bet and win with free_spins_id equal to the campaign's campaign_id. Take nothing for the bet; book the win as bonus money.
  • Games are checked against your window like a launch. Games that would go to two different game providers cannot share a campaign: place two.
  • In the sandbox, sandbox_player_bonus_refused as one of player_ids answers BONUS_CAMPAIGN_REFUSED. See Scenario values.