Skip to content

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 ​

text
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
js
// 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 hasMore is true, read the next page right away; false means you have caught up, so poll again later with the same nextCursor.
  • limit is 1–1000 (default 500).
  • The cursor looks like yyyymm.sequence; store it as an opaque string. A malformed cursor returns 400 INVALID_CURSOR.
  • from is ignored when cursor is present. The first sync without a cursor starts at the first record of the current UTC month, or at the time in from (aligned to the minute).
  • When you backfill from long ago, each call advances at most 3 months; hasMore stays true until the cursor reaches the current UTC month, so just keep following nextCursor.
  • recSeq only increases within a UTC month and restarts in the next month. Do not use it as a unique key.

Revisions (rev), recalculations and voids ​

statusMeaningrev
settledFirst settlementusually 1
recalculatedSettled again after the data source corrected the resulthigher
voidVoided (misdeal, dealer error, device failure…); stakes refunded in full (for Niu Niu together with the hold)higher
  • The highest rev of a slipId is 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).
  • delta is what this revision actually credited to the player's balance: payout for 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: disputed in GET /rounds/{roundId}).
  • In your reports, replace the old revision with the new one; for the difference use delta or subtract the two revisions' winLoss.

The bet record ​

json
{
  "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.

FieldNotes
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
stakeTotal stake (11,000)
payoutTotal payout, stake included (15,000 + 13,000 = 28,000)
winLossPlayer win/loss = payout − stake (17,000; positive means the player won); for Niu Niu the hold is subtracted too, see Niu Niu bets
validStakeValid 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
rollingRolling: total stake of lost bets (Super Six excluded); nothing was lost here, so 0
resultThe round's result code and card string, see Tables & round results
placedAt, settledAtTime 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:

json
{
  "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.

FieldNotes
variantcasino 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, rollingPlayer 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
rakeRake taken from this player in the hand (player-vs-player tables; 0 at player-vs-dealer tables)
onsitetrue 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.codeThis 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.cardInfoboard|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
handA 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); status is void.
  • Buy-ins and cash-outs at player-vs-player tables are wallet transfers, not bets; each hand's result is recorded by the POT bet.
  • On Hold'em tables another merchant shares with you (cross-merchant), your players' bets appear in your GET /bets as 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:

json
{
  "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.

FieldNotes
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
stakeTotal stake, without the hold (3,000)
holdTotal hold (1,000 + 2,000 = 3,000); "0" without double bets. Only Niu Niu slips have this field
payoutTotal returned, including the returned hold (1,950 + 500 + 975 + 5,850 = 9,275)
winLosspayout − stake − hold (9,275 − 3,000 − 3,000 = 3,275). For Niu Niu, payout − stake is not the win/loss; always reconcile with winLoss
validStakeEqual 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
rollingThe amount lost (only player 2 double lost, 500 × 2 = 1,000)
deltapayout 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.codeThe 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.cardInfofirst card|banker|player 1|player 2|player 3, 5 cards per hand (format)
result.hands, result.winnersEach 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/balance already excludes the hold, which comes back with the payout at settlement.
  • Void (status: void): stake and hold are refunded in full (payout is stake + hold, winLoss is 0, double bets have mult 0), and result has no hands or winners.
  • 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 (mult 0).
  • In the daily summary, Niu Niu slips count towards payout as stake + win/loss, without the returned hold.

Daily summary ​

text
GET /api/tenant/v1/bets/summary?date=2026-09-24

Per-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 ​

  • /bets is 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 ​

CodeHTTPCause
INVALID_CURSOR400cursor is not in the format returned by the previous page
INVALID_PARAMETER400from is not a valid time, or date is not a real YYYY-MM-DD date

Common errors are listed in Error codes.

elite Tenant Integration API v1