Skip to content

Open app

Issue a free-round grant

POST
/free-rounds

Issue free rounds (free spins / free tickets) to a player on one or more games (ADR-048). count is per game - games:[A,B] + count:10 issues 10 rounds on each (20 total); the response itemises the provider ref per game. Not tied to a session, but each game must pass the same compliance + capability gate as a launch.

Requires the write API-key scope (the standard operator key scope; the granular free_rounds.write is also accepted for bonus-only keys) and an Idempotency-Key header (financial idempotency - same key + same body replays the cached result; same key + different body → 409). Off by default per provider until onboarded. Mobule issuance, including a previously issued grant replay, requires current support approval for the exact organization bound to the operator key. For a platform key, the approval of the platform’s organization covers its brands; a suspended or retired brand is refused. Denial returns PROVIDER_ACCESS_REQUIRED (403) before provider I/O; authorization uncertainty returns PROVIDER_ACCESS_UNAVAILABLE (503). Existing free-round history and cancellation remain available after revoke.

Mobule free rounds are pull-activated. Issuing makes no provider call: we mint one freerounds_id per game (32 lowercase hex, returned as that game’s provider_grant_ref), the player’s next POST /v1/sessions launch of the game carries it to Mobule, and Mobule’s server then activates the rounds (all rounds of that game are accounted at activation) and, once the player has played them, completes them. The win is credited once, as a freespin transaction linked to free_round_grant_id, and reaches your wallet as a WIN with is_free: true and free_round_grant_id set; wins are not credited during play. A Mobule grant requires bet_amount plus integer provider_params.betlevel and provider_params.rate (both at least 1) and rejects coin_level (400 invalid_stake alongside bet_amount, bet_amount_required without it); a missing or non-integer betlevel or rate is 400 provider_params_required. The two integers are passed to Mobule as supplied, never derived from bet_amount. A game can be cancelled until Mobule activates its rounds, which follows the player’s launch (see POST /v1/free-rounds/{grant_id}/cancel). The win of rounds Mobule activated is credited even when it arrives after end_at, after the grant expired or after a cancel. Off until enabled for Mobule in the environment (403 free_rounds_disabled). The same switch governs the launch: while free rounds are disabled for the environment, a launch carries no freerounds_id and is ordinary paid play, even for a grant issued earlier; rounds already carried to Mobule still settle.

Idempotency-Key
required
string
>= 1 characters <= 256 characters

Unique key for idempotent retry semantics. A retry with the same key and the same body returns the cached response.

Reuse with a DIFFERENT body is endpoint-specific, because the two implementations differ. POST /v1/free-rounds hashes the request body (src/platform_api/free_rounds_flows.py, _request_hash) and answers a mismatch with 409 idempotency_conflict. POST /v1/sessions uses the Redis guard (src/infra/idempotency.py), which does not compare bodies: the cached response is replayed and the new body is not processed, except for a cached Mobule launch, whose retry body (game, player and a platform’s sub_operator_ref) is re-read to reauthorize it: a mismatch answers 403 and storage uncertainty 503 (see POST /v1/sessions). Never reuse a key across logically different operations.

A request arriving while the same key is still in flight returns 409 idempotency_in_progress. Over the length limit returns 400 invalid_idempotency_key; missing entirely returns 400 missing_idempotency_key.

object
provider_code
required
string
>= 1 characters <= 64 characters
apparat
player_id
required
string
>= 1 characters <= 128 characters
sub_operator_ref

Platform accounts only. Which brand’s players receive the grant. Same rules and error codes as sub_operator_ref on POST /v1/sessions (required for platform keys, rejected for others); the grant lands on the brand’s identity so settlement routes to the same brand the session did.

Because the grant is keyed on the brand, the same ref must be sent to GET /v1/free-rounds and POST /v1/free-rounds/{grant_id}/cancel to see or cancel it.

string
games
required

Our games.id UUIDs. Every game must support free rounds.

Array<string>
>= 1 items <= 200 items
count
required

Rounds PER GAME (not total). games:[A,B] + count:10 ⇒ 20 total.

integer
>= 1
currency
required
string
>= 3 characters <= 10 characters
EUR
country
required

ISO 3166-1 alpha-2 - gated at issue time (ADR-046).

string
>= 2 characters <= 2 characters
coin_level

TrueLabs stake level. Provide exactly one of coin_level / bet_amount. Rejected for Mobule (400).

integer
>= 1 <= 24
bet_amount

Stake per round in MAJOR units. Required for Apparat (voucher stake) and for Mobule (nominal stake per round, stored on the grant as bet_amount and internally on its activation and win records, never sent to Mobule; Mobule’s betlevel/rate come from provider_params, never from this amount).

number
> 0
start_at
required
string format: date-time
end_at
required
string format: date-time
campaign_id
string
<= 128 characters
funded_by
string
Allowed values: operator provider_promo
free_round_kind
string
default: voucher
Allowed values: freespin freeticket voucher
provider_params

Provider-specific issuance parameters, passed through as supplied. Mobule (required): betlevel (integer, at least 1, Mobule bet level) and rate (integer, at least 1, Mobule denomination, the coin value). Both are returned to Mobule verbatim when it activates the rounds (freerounds.activate); missing or non-integer values are a 400 provider_params_required. We never derive them from bet_amount.

object
key
additional properties
any

Idempotent replay of a previously issued grant.

object
grant_id
string format: uuid
status
string
Allowed values: active issue_failed
provider_code
string
player_id
string
currency
string
count_per_game
integer
total_rounds
integer
start_at
string format: date-time
end_at
string format: date-time
games
Array<object>
object
game_id
string format: uuid
provider_game_id
string
count
integer
rounds_used
integer
provider_grant_ref

Per-game provider ref: Apparat voucherId; for Mobule the freerounds_id we minted (32 lowercase hex), carried on the player’s game launch and activated by Mobule once.

string
nullable
status
string
Allowed values: pending_issue issue_failed active partially_consumed consumed pending_cancel cancel_failed cancelled expired observed
error
string
nullable

Grant issued for all games.

object
grant_id
string format: uuid
status
string
Allowed values: active issue_failed
provider_code
string
player_id
string
currency
string
count_per_game
integer
total_rounds
integer
start_at
string format: date-time
end_at
string format: date-time
games
Array<object>
object
game_id
string format: uuid
provider_game_id
string
count
integer
rounds_used
integer
provider_grant_ref

Per-game provider ref: Apparat voucherId; for Mobule the freerounds_id we minted (32 lowercase hex), carried on the player’s game launch and activated by Mobule once.

string
nullable
status
string
Allowed values: pending_issue issue_failed active partially_consumed consumed pending_cancel cancel_failed cancelled expired observed
error
string
nullable

Grant partially issued - some games failed (see per-game status).

object
grant_id
string format: uuid
status
string
Allowed values: active issue_failed
provider_code
string
player_id
string
currency
string
count_per_game
integer
total_rounds
integer
start_at
string format: date-time
end_at
string format: date-time
games
Array<object>
object
game_id
string format: uuid
provider_game_id
string
count
integer
rounds_used
integer
provider_grant_ref

Per-game provider ref: Apparat voucherId; for Mobule the freerounds_id we minted (32 lowercase hex), carried on the player’s game launch and activated by Mobule once.

string
nullable
status
string
Allowed values: pending_issue issue_failed active partially_consumed consumed pending_cancel cancel_failed cancelled expired observed
error
string
nullable

Validation failed, missing Idempotency-Key, or invalid stake (Mobule also bet_amount_required / provider_params_required).

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any

Missing, invalid, or revoked API key.

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any

Scope missing, provider disabled, jurisdiction blocked, or player self-excluded.

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any

Idempotency-Key conflict. Two causes, distinguished by the error field: idempotency_in_progress when a request with the same key is still in flight (retry shortly), and idempotency_conflict when the key was already used with a different request body. Only POST /v1/free-rounds detects the second case; POST /v1/sessions does not compare bodies and replays its cached response instead, after reauthorizing a cached Mobule launch (see POST /v1/sessions). POST /v1/free-rounds answers with the flat FreeRoundError body; POST /v1/sessions with a flat {error, message} body without status, and a Retry-After header. A platform’s brand selector answers the nested Error object on either endpoint.

On POST /v1/free-rounds for Mobule, whose issuance makes no provider call, an idempotency_in_progress that persists means the issuance stopped after creating the grant: its replay never gets a result, and the grant stays pending_issue, never reaches a launch and is closed as expired by the expiry sweep (every 30 minutes) after its end_at. Issue a new grant under a new key once it has closed (see POST /v1/free-rounds/{grant_id}/cancel).

One of:

The flat error body of the free-rounds endpoints’ own refusals (those of POST /v1/free-rounds/{grant_id}/cancel, and the idempotency_* 409s of POST /v1/free-rounds), not the nested Error object a platform’s brand selector answers with: error is the machine-readable code and status repeats the HTTP status.

object
error
required

The code, e.g. grant_not_found, not_cancellable, issuance_in_progress, provider_error, provider_unavailable.

string
issuance_in_progress
message
string
status
required
integer
409
grant_id
string format: uuid
grant_status

The grant’s status (409 only).

string
games

Mobule 409 only - the state of each game of the grant.

Array<object>
object
game_id
string format: uuid
status
string
error
string
nullable
Retry-After
integer

Seconds to wait before retrying; sent by POST /v1/sessions only.

Game does not support free rounds, abuse limit exceeded, or bet outside limits.

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any

Rate limit exceeded; consult Retry-After.

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any
Retry-After
integer

Seconds to wait before retry.

Provider rejected the issuance.

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any

Provider access authorization storage is temporarily unavailable.

object
error
required
object
code
required

Machine-parseable error code. See https://hub.aggregator.gg/error-reference/.

string
/^(E[0-9A-F][0-9]{3}|PROVIDER_ACCESS_REQUIRED|PROVIDER_ACCESS_UNAVAILABLE)$/
E0001
message
required
string
request_id

Correlation ID for support escalation.

string
details
object
key
additional properties
any