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
| type | What the player gets | Carries |
|---|---|---|
| free_spins | A series of free spins at a fixed stake | spin_count, bet_amount; optional campaign_limit, min_cashout_multiplier |
| fortune_wheel | A wheel of 16 prizes: money or free spins | exactly 16 prizes, money or free_spins |
| treasure_chest | A pick of 16 chests | exactly 16 prizes, money only |
| golden_dragon | One draw | 1 to 100 prizes, money only |
| buy_bonus | One bonus feature bought for the player, paid by the game provider | bet_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
/v1/bonus-campaigns
Place a bonus campaign. Idempotent on reference.
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | yes | free_spins, fortune_wheel, treasure_chest, golden_dragon or buy_bonus. |
| name | string | yes | Shown to the player in the game. Up to 100 characters. |
| currency | string | yes | ISO 4217. A game provider may take bonuses in some currencies only; another answers BONUS_CAMPAIGN_REFUSED. |
| game_ids | string[] | yes | 1 to 50 catalogue ids from GET /v1/games, all of one game provider. |
| player_ids | string[] | yes | 1 to 1000 of your player references, up to 120 characters each. A campaign is never for everybody. |
| valid_from | string (date-time) | no | When it opens. Now when absent. |
| valid_until | string (date-time) | yes | In the future and after valid_from, at most 90 days after it. |
| triggers | object | yes | At least one trigger. See Triggers. |
| spin_count | integer | no | free_spins only, required there: 1..10000 spins. |
| bet_amount | string (money) | no | free_spins and buy_bonus only, required there: the stake of one spin, or the price of the bought bonus.Example: "0.20" |
| prizes | object[] | no | The drawn types only, required there. See Prizes. |
| campaign_limit | string (money) | no | free_spins: the total the campaign pays before it ends. |
| min_cashout_multiplier | string | no | free_spins on crash games: the least multiplier a spin cashes out at, from 1.3 to below 100. |
| reference | string | yes | Your id for the campaign, up to 120 characters. A repeat under it answers the first campaign and places nothing twice. |
{
"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"
}{
"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
| error | HTTP | Retry | When |
|---|---|---|---|
| VALIDATION_FAILED | 422 | no | A 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_REFUSED | 409 | no | The 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_UNAVAILABLE | 409 | no | The 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.
| Trigger | Shape | Fires when |
|---|---|---|
| first_game_launch | true | The 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.
| kind | Fields | Allowed on |
|---|---|---|
| money | amount (money), weight | fortune_wheel, treasure_chest, golden_dragon |
| free_spins | spins (at least 1), bet_amount (money), weight | fortune_wheel only |
fortune_wheelandtreasure_chestcarry exactly 16 prizes;golden_dragon1 to 100.free_spinscarries noprizes; the drawn types carry nospin_count.
What a game takes
/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.
{
"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
| error | HTTP | Retry | When |
|---|---|---|---|
| VALIDATION_FAILED | 422 | no | A 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_UNAVAILABLE | 409 | no | The 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
/v1/bonus-campaigns/{id}
The campaign as it stands.
| status | Meaning |
|---|---|
| pending | Accepted, not yet confirmed by the game provider |
| active | Live for the named players |
| ended | Past valid_until |
| failed | The game provider refused it; failure_reason says why |
Possible errors
| error | HTTP | Retry | When |
|---|---|---|---|
| NOT_FOUND | 404 | no | No 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.
/v1/bonus-campaigns?reference={reference}
Find a campaign by your reference.
Possible errors
| error | HTTP | Retry | When |
|---|---|---|---|
| NOT_FOUND | 404 | no | No such session, grant, bonus campaign, transaction or round for your operator. |
| VALIDATION_FAILED | 422 | no | A 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.
/v1/bonus-campaigns?player_id={player_id}
The campaigns of one player, newest first, at most 100.
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.
{
"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
| error | HTTP | Retry | When |
|---|---|---|---|
| VALIDATION_FAILED | 422 | no | A 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
referencewhen the outcome is unknown; the answer is the first campaign. When you did not keep the id, look it up withGET /v1/bonus-campaigns?reference=. - There is no cancel. Game providers have no way to end a campaign early: set
valid_untilandplayer_idswith that in mind. - Spins a campaign hands over reach your wallet as
betandwinwithfree_spins_idequal to the campaign'scampaign_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_refusedas one ofplayer_idsanswersBONUS_CAMPAIGN_REFUSED. See Scenario values.
