Bet sync
Settled bets are read incrementally with GET /bets, in the order they were written. Keep the nextCursor from each page and send it back unchanged next time, and your system stays in sync without gaps or duplicates.
- All of one player's bets in one round form one slip (
slipId). - A slip appears after its round is settled (usually a few seconds after the result).
- When a result is corrected or a round is voided, the same slip appears again with a higher
rev, as a new record.
Sync loop
cursor = the cursor you saved last time (empty the first time)
repeat:
page = GET /bets?limit=1000&cursor=<cursor> (first time: from=<ISO time> instead)
in one database transaction:
insert page.items keyed on (slipId, rev) (skip existing ones)
save cursor = page.nextCursor
if page.hasMore is false: caught up, wait 5–30 seconds// Node.js, using call() from "Authentication & signing"
async function syncBets(db, auth) {
let cursor = await db.loadCursor(); // null the first time
for (;;) {
const query = cursor ? `cursor=${encodeURIComponent(cursor)}` : 'from=2026-09-01T00:00:00Z';
const page = await call('GET', `/api/tenant/v1/bets?limit=1000&${query}`, undefined, auth);
await db.transaction(async (tx) => {
for (const bet of page.items) await tx.upsertBet(bet); // unique key (slipId, rev)
await tx.saveCursor(page.nextCursor);
});
cursor = page.nextCursor;
if (!page.hasMore) await new Promise((r) => setTimeout(r, 10_000)); // caught up: poll again later
}
}- Write the bets first, then save the cursor (ideally in one transaction). If something fails in between, the next run re-reads from the old cursor and
(slipId, rev)removes the duplicates. - While
hasMoreistrue, read the next page right away;falsemeans you have caught up, so poll again later with the samenextCursor. limitis 1–1000 (default 500).- The cursor looks like
yyyymm.sequence; store it as an opaque string. A malformed cursor returns400 INVALID_CURSOR. fromis ignored whencursoris present. The first sync without a cursor starts at the first record of the current UTC month, or at the time infrom(aligned to the minute).- When you backfill from long ago, each call advances at most 3 months;
hasMorestaystrueuntil the cursor reaches the current UTC month, so just keep followingnextCursor. recSeqonly increases within a UTC month and restarts in the next month. Do not use it as a unique key.
Revisions (rev), recalculations and voids
status | Meaning | rev |
|---|---|---|
settled | First settlement | usually 1 |
recalculated | Settled again after the data source corrected the result | higher |
void | Voided (misdeal, dealer error, device failure…); stakes refunded in full (for Niu Niu together with the hold) | higher |
- The highest
revof aslipIdis final; older revisions stay available for audit. - Recalculation and void records are written at the time they happen, so they always appear after your current cursor: cursor sync never misses a revision (month boundaries included).
deltais what this revision actually credited to the player's balance:payoutfor the first settlement; the payout difference for a recalculation (can be negative, meaning winnings are taken back); stake (plus the hold for Niu Niu) minus the earlier payout for a void. The platform already adjusted the player's wallet; you do not need to transfer anything.- A recalculation can leave a player's balance negative (winnings taken back after the player withdrew them).
- Corrections and voids have guardrails: they are applied automatically only if they arrive within 30 minutes of settlement and the new result is complete and passes the drawing rules. Outside the guardrails the original result and amounts stand and the round is marked disputed (
status: disputedinGET /rounds/{roundId}). - In your reports, replace the old revision with the new one; for the difference use
deltaor subtract the two revisions'winLoss.
The bet record
{
"recSeq": 1024,
"slipId": "01K5Y0B8Z6R2M4N7P9Q3S5T8VW.7H3KQ2XA",
"rev": 1,
"status": "settled",
"username": "alice",
"currency": "TWD",
"tableId": "S01",
"game": "baccarat",
"variant": "nocomm",
"roundId": "01K5Y0B8Z6R2M4N7P9Q3S5T8VW",
"shoe": "260924-03",
"round": 12,
"bets": [
{ "zone": "B", "amount": "10000", "return": "15000" },
{ "zone": "S6", "amount": "1000", "return": "13000" }
],
"stake": "11000",
"validStake": "6000",
"rolling": "0",
"payout": "28000",
"winLoss": "17000",
"delta": "28000",
"result": { "code": "1", "cardInfo": "122334424000" },
"placedAt": "2026-09-24T03:00:41.120Z",
"settledAt": "2026-09-24T03:01:20.480Z"
}This is no-commission baccarat: 10,000 on banker plus 1,000 on Super Six, and the banker wins with 6.
| Field | Notes |
|---|---|
bets[] | Stake amount and return per zone (stake + net win; a push returns the stake; 0 when lost). Zones are listed in Tables & round results |
stake | Total stake (11,000) |
payout | Total payout, stake included (15,000 + 13,000 = 28,000) |
winLoss | Player win/loss = payout − stake (17,000; positive means the player won); for Niu Niu the hold is subtracted too, see Niu Niu bets |
validStake | Valid stake. On a banker win: the absolute difference between the banker net win and the player stake, plus the tie, pair and Super Six stakes (here 5,000 − 0 plus 1,000 = 6,000); a player win mirrors it; on a tie only tie and pair (and Super Six) stakes count. Dragon tiger: see Tables & round results |
rolling | Rolling: total stake of lost bets (Super Six excluded); nothing was lost here, so 0 |
result | The round's result code and card string, see Tables & round results |
placedAt, settledAt | Time of the first bet; settlement time of this revision |
Texas Hold'em bets
For Texas Hold'em (game: "holdem") each hand produces one record per participating player: roundId is the hand ID, round the hand number at the table, and shoe is an empty string. Three more fields are added: rake, onsite and hand:
{
"recSeq": 2048,
"slipId": "01K62H3P9R8Q7M6N5B4V3C2X1Z.3",
"rev": 1,
"status": "settled",
"username": "alice",
"currency": "TWD",
"tableId": "H01",
"game": "holdem",
"variant": "casino",
"roundId": "01K62H3P9R8Q7M6N5B4V3C2X1Z",
"shoe": "",
"round": 318,
"bets": [
{ "zone": "ANTE", "amount": "100", "return": "200" },
{ "zone": "CALL", "amount": "200", "return": "400" },
{ "zone": "AAB", "amount": "50", "return": "0" }
],
"stake": "350",
"validStake": "350",
"rolling": "350",
"payout": "600",
"winLoss": "250",
"delta": "600",
"result": { "code": "player_wins", "cardInfo": "AS KD 7H 7C 2S|AH 7D|QS 3C" },
"rake": "0",
"onsite": false,
"hand": { "handId": "01K62H3P9R8Q7M6N5B4V3C2X1Z", "no": 318, "outcome": "player_wins", "cards": "AS KD 7H 7C 2S|AH 7D|QS 3C" },
"placedAt": "2026-09-27T08:12:03.410Z",
"settledAt": "2026-09-27T08:13:10.020Z"
}This is a player-vs-dealer table: ante 100, call 200, AA bonus 50; the player's two pair beat the dealer's pair of queens, so the ante and the call each pay 1:1 and the AA bonus loses.
| Field | Notes |
|---|---|
variant | casino player vs dealer (Casino Hold'em), nlhe player vs player (no-limit) |
bets[] | Player vs dealer: ANTE ante, CALL call (absent when the player folds), AAB AA bonus; player vs player: one POT leg (everything put into the pot in the hand; return is the uncalled part given back). Rules are in Tables & round results |
validStake, rolling | Player vs dealer: stakes not returned (pushes and calls returned because the dealer did not qualify are excluded); player vs player: the amount put in during the hand. 0 when voided |
rake | Rake taken from this player in the hand (player-vs-player tables; 0 at player-vs-dealer tables) |
onsite | true for an on-site seat: a guest at the table owner's studio, registered by the dealer on the tablet and outside the wallet (username is "現場 #seat"); only in the table owner's bets |
result.code | This player's outcome: player vs dealer player_wins, dealer_wins, tie, dealer_not_qualified, fold; player vs player win, split, lose, fold; void when voided |
result.cardInfo | board|this player's two hole cards|the dealer's two cards, space-separated card codes (for example AS KD 7H 7C 2S|AH 7D|QS 3C); the dealer part only at player-vs-dealer tables |
hand | A summary of the hand (handId, no, outcome, cards), the same values as above for convenience |
- Voided hands: player-vs-dealer tables refund in full; player-vs-player tables return everything put in during the hand (blinds included);
statusisvoid. - Buy-ins and cash-outs at player-vs-player tables are wallet transfers, not bets; each hand's result is recorded by the
POTbet. - On Hold'em tables another merchant shares with you (cross-merchant), your players' bets appear in your
GET /betsas usual.
Niu Niu bets
Niu Niu (game: "niuniu", variant: "standard") works like baccarat: all of one player's bets in one round form one slip. Every round uses a fresh deck, so there is no shoe: shoe is the session (a new one every 60 rounds by default) and round is the round number within the session.
A double bet can lose more than its stake (up to 3× by default), so when a player bets double the platform holds stake × 2 from the balance on top of the stake, and returns the unused part together with the payout after the result. Niu Niu slips therefore carry a few hold fields:
{
"recSeq": 3072,
"slipId": "01K65N7Q2W4E6R8T0Y1V3J5K7P.7H3KQ2XA",
"rev": 1,
"status": "settled",
"username": "alice",
"currency": "TWD",
"tableId": "N01",
"game": "niuniu",
"variant": "standard",
"roundId": "01K65N7Q2W4E6R8T0Y1V3J5K7P",
"shoe": "260928-01",
"round": 7,
"bets": [
{ "zone": "P1E", "amount": "1000", "return": "1950" },
{ "zone": "P2D", "amount": "500", "return": "500", "hold": "1000", "mult": 2 },
{ "zone": "P3E", "amount": "500", "return": "975" },
{ "zone": "P3D", "amount": "1000", "return": "5850", "hold": "2000", "mult": 3 }
],
"stake": "3000",
"hold": "3000",
"validStake": "5500",
"rolling": "1000",
"payout": "9275",
"winLoss": "3275",
"delta": "9275",
"result": {
"code": "5893A",
"cardInfo": "7H|KS QD 10C 3S 5H|4C 6D JS 9C KH|2D 8S QC AH 2C|7D 3H KD 5C 5D",
"hands": { "B": 8, "P1": 9, "P2": 3, "P3": 10 },
"winners": { "P1": "P", "P2": "B", "P3": "P" }
},
"placedAt": "2026-09-28T06:30:12.300Z",
"settledAt": "2026-09-28T06:30:51.640Z"
}This slip bet 1,000 on player 1 equal, 500 on player 2 double (hold 1,000), 500 on player 3 equal and 1,000 on player 3 double (hold 2,000). The banker had Bull 8, player 1 Bull 9, player 2 Bull 3 and player 3 Niu Niu, so players 1 and 3 won and player 2 lost.
| Field | Notes |
|---|---|
bets[] | Zones P1E to P3D (see Tables & round results). Double bets add hold (that bet's hold) and mult (the multiplier actually applied: the player hand's when the player wins, the banker hand's when the player loses; 0 when refunded). return includes the returned hold: player 3 double returns 1,000 + 2,000 + 1,000 × 3 × 0.95 = 5,850; player 2 double lost to the banker's Bull 8 (×2), losing 1,000, so 500 + 1,000 − 1,000 = 500 comes back |
stake | Total stake, without the hold (3,000) |
hold | Total hold (1,000 + 2,000 = 3,000); "0" without double bets. Only Niu Niu slips have this field |
payout | Total returned, including the returned hold (1,950 + 500 + 975 + 5,850 = 9,275) |
winLoss | payout − stake − hold (9,275 − 3,000 − 3,000 = 3,275). For Niu Niu, payout − stake is not the win/loss; always reconcile with winLoss |
validStake | Equal bets: the stake; double bets: stake × the multiplier actually applied (1,000 + 500 × 2 + 500 + 1,000 × 3 = 5,500); refunded bets do not count |
rolling | The amount lost (only player 2 double lost, 500 × 2 = 1,000) |
delta | payout for the first settlement (9,275); stake + hold (6,000) was debited when the bets were placed, so the player won 3,275 net |
result.code | The 5-character result code (format); 5893A = banker Bull 8, player 1 Bull 9 wins, player 2 Bull 3 loses, player 3 Niu Niu wins |
result.cardInfo | first card|banker|player 1|player 2|player 3, 5 cards per hand (format) |
result.hands, result.winners | Each hand's rank (0 No Bull, 1–9 Bull 1–9, 10 Niu Niu…) and the outcome of each player hand against the banker (P player wins, B banker wins), so you do not need to decode the result code |
- The player's wallet is debited stake + hold when the bet is placed; until the result,
GET /wallet/balancealready excludes the hold, which comes back with the payout at settlement. - Void (
status: void): stake and hold are refunded in full (payoutis stake + hold,winLossis0, double bets havemult0), andresulthas nohandsorwinners. - Incomplete result (the data source only gives each hand's outcome): equal bets settle as usual; a double bet needs the winning hand's rank, and without it the stake and hold are refunded (
mult0). - In the daily summary, Niu Niu slips count towards
payoutas stake + win/loss, without the returned hold.
Daily summary
GET /api/tenant/v1/bets/summary?date=2026-09-24Per-player slips, stake, valid stake, rolling, payout, win/loss and rake (rake, Texas Hold'em player-vs-player tables) for one day in your tenant's time zone. Only the latest revision of each slip for that day counts, and voided slips add nothing to stake. At most 1,000 players are returned. Texas Hold'em on-site seats (slips with onsite: true) are not your members and are left out. Niu Niu slips count towards the summary payout as stake + win/loss, without the returned hold (a slip's own payout includes the hold). Use it to check your own daily totals. date must be a real date (for example 2026-02-30 returns 400 INVALID_PARAMETER).
Safety nets and recommendations
/betsis the source of truth: webhooks only speed things up and can be late or repeated; correctness always comes from cursor sync.- (Optional) periodic re-scan: for an extra safety net, scan the previous day again once a day with
from=<yesterday 00:00 UTC>and upsert on(slipId, rev); cursor sync on its own does not miss records. - Retention: bets can be read through the API for about 400 days on the paid plan and 7 days on the free demo plan (removed in whole months; the current and previous months are always kept). Store bets in your own database.
Error codes
| Code | HTTP | Cause |
|---|---|---|
INVALID_CURSOR | 400 | cursor is not in the format returned by the previous page |
INVALID_PARAMETER | 400 | from is not a valid time, or date is not a real YYYY-MM-DD date |
Common errors are listed in Error codes.