Appearance
Free Rounds
Availability
Free rounds are an opt-in add-on — enabled per partner after a short joint test (see checklist below). Ask your TS contact to enable it for your stage brand before integrating. Until it is enabled for your brand, no free-round traffic will ever reach your wallet API.
What a free round is in Tap Markets
Tap Markets is not a slot: a round is a tap — the player picks a price cell and the market settles it seconds later. A free round is therefore:
an operator-funded tap — the player places a trade at a stake they did not pay. A win credits them real money; a loss costs them nothing.
Because there are no reels, two things a slot takes for granted are explicit fields you choose when awarding: the stake per round (you price the promo) and the instrument scope (or all instruments).
Not supported by design: retriggers (a round awarding more rounds), lines, gamble features. Expected promo cost per round ≈ stake × RTP — ask TS for the measured payout figures before pricing a campaign.
Flow at a glance
Everything rides the wallet callbacks you already implement — free rounds add one new call from you to TS (the grant) and a few extra fields on /opentrade / /closetrade.
1. Awarding free rounds (you → TS)
POST https://<your-tap-host>/api/partner/free-rounds
Signed with the same HMAC v2 scheme you already verify on wallet callbacks (see Message Integrity) — same shared secret, same X-Sig-Version: v2, X-Timestamp, X-Nonce, X-Signature headers, just in the other direction: you sign, TS verifies.
Request:
json
{
"userID": "alice",
"count": 10,
"stake": "1.00",
"currency": "USD",
"reference": "campaign-2026-08-abc123",
"validUntil": "2026-08-31T23:59:59Z"
}| Field | Required | Meaning |
|---|---|---|
userID | yes | your user id — the same one you return from /authenticate |
count | yes | number of free taps (1–10000) |
stake | yes | stake per free tap, in currency — you price the promo |
currency | yes | must match the player's wallet currency |
reference | recommended | your campaign reference — makes the grant idempotent: resending the same reference returns the same grant, never a duplicate. Safe to retry on timeout. |
validUntil | optional | ISO-8601 UTC expiry; omit for no expiry |
Response:
json
{ "status": "Ok", "freeRoundId": "fr-a1b2c3d4e5f60718", "count": 10, "stake": "1.00", "currency": "USD" }freeRoundId is minted by TS and identifies the grant on every subsequent wallet callback. Grants can be awarded before the player's first launch.
Errors use the same status enum as the wallet callbacks (InvalidDigest, Unauthorized, plus InvalidRequest with a message for bad count/stake/expiry).
2. The free /opentrade — approve, never charge
Each free tap arrives as a normal /opentrade with two differences:
json
{
"token": "<walletToken>",
"amount": "0",
"currency": "USD",
"externalTradeType": "tap",
"externalTradeId": "12399",
"freeRoundId": "fr-a1b2c3d4e5f60718",
"data": [{
"userID": "alice",
"instrumentID": "30102",
"stake": "1.00",
"payoutRatio": "1.7",
"isFreeRound": "1",
"tStart": "2026-08-04T12:00:00.000Z",
"tEnd": "2026-08-04T12:00:02.000Z",
"pMin": "65430.00",
"pMax": "65432.00",
"currency": "USD"
}]
}- Top-level
amountis"0"— reserve nothing, deduct nothing.data[0].stakestill carries the promo's notional stake (for your reporting), but no money moves. freeRoundId+data[0].isFreeRound: "1"mark the round.- Respond exactly as usual:
{ "balance": "<unchanged>", "currency": "USD", "status": "Ok" }. - Idempotency by
externalTradeId, unchanged. - Answering
InsufficientFunds/Unauthorized-class rejections on a free open is read by TS as "the operator revoked this promo": the grant is cancelled and no further free rounds are offered. The round you rejected is already in flight on TS's side, so its/closetradewill still arrive — handle it normally (it moves at most the win amount; a loss closes with0).
3. The free /closetrade — where you pay the win
Settlement is a normal /closetrade with the free-round fields added:
json
{
"token": "<walletToken>",
"amount": "1.70",
"currency": "USD",
"externalTradeType": "tap",
"externalTradeId": "12399",
"freeRoundId": "fr-a1b2c3d4e5f60718",
"freeRoundsRemaining": 6,
"freeRoundCompleted": "0",
"data": [{ "tradeID": "12399", "userID": "alice", "stake": "1.0000000000",
"pnl": "0.70", "returnedAmount": "1.70", "isFreeRound": "1",
"instrumentID": "30102", "expiryPrice": "65431.50", "touchPrice": "65431.40",
"payout": "70", "rebate": "0" }]
}The "always update balance from amount" rule from Wallet / Trade Callbacks applies unchanged, and does the right thing automatically:
| Outcome | amount | Your action |
|---|---|---|
| Win | full return (stake × payoutRatio, e.g. 1.70) | credit it whole — the unpaid stake is not deducted (slot convention) |
| Loss | 0 | credit nothing; the round still closes — a 0-amount free close is normal, not an error |
| Cancelled | arrives as a cancellation, not a close | no-op, and the round goes back onto the grant |
Extra fields:
| Field | Meaning |
|---|---|
freeRoundsRemaining | rounds left on the grant after this one |
freeRoundCompleted | "1" on the grant's last round — the grant is then exhausted |
Values are snapshotted at settle time: a retried delivery repeats identical values under the identical externalTradeId.
Funding and reporting
- The operator funds free-round wins (your promo liability) — the same convention as slot free spins. Confirm this matches your accounting before go-live.
- Both callbacks carry
isFreeRound: "1", so you can split promo turnover from real-money turnover in your reports. TS excludes free-round volume from RTP/GGR statistics on its side the same way.
Expiry & lifecycle
| Situation | Behaviour |
|---|---|
| all rounds used | grant exhausted on the last close (freeRoundCompleted: "1") |
validUntil passes | unused rounds silently expire — no wire traffic |
you reject a free /opentrade | grant cancelled (promo revoked); the in-flight round still settles, no further free rounds |
| round opened just before expiry | always settles — expiry is checked at open only |
| grant currency ≠ wallet currency | free tap refused; re-award with the right currency |
Unused rounds are never converted to cash.
Test checklist
Run on stage with TS before enabling in production:
- Grant 3 rounds @ 1.00 → note
freeRoundId; resend the samereference→ samefreeRoundId, still 3 rounds. - Launch → banner shows 3 @ 1.00.
- Free tap →
/opentrade amount "0"received, player balance unchanged. - Win →
/closetrade amount "1.70"→ balance +1.70 exactly. - Loss →
/closetrade amount "0"→ balance unchanged, round consumed. - Last round →
freeRoundsRemaining: 0,freeRoundCompleted: "1". - Short-expiry grant → let it lapse → no wire traffic, rounds gone.
- Reject one free
/opentrade→ grant cancelled, remaining rounds gone.