Skip to content

Rounds, Transactions & Reports ​

APIs for querying game records and money flows. Use them for player bet history screens, admin round lookups, wallet ledger reconciliation, and daily GGR aggregation.

MethodPathPurpose
GET/operator/v1/roundsRound list per player
GET/operator/v1/rounds/{round_id}Round details (player view / full round)
GET/operator/v1/sessions/{session_id}Details of one seating at a buy-in flow table (table session)
GET/operator/v1/transactionsWallet transaction ledger (for reconciliation)
GET/operator/v1/reports/ggrGGR aggregated by day and game

Sign every request according to the Authentication & signing rules. GET requests have no body and are signed with an empty string.

Common query parameters ​

List APIs read the period and page size parameters the same way.

ParameterFormatDescription
fromRFC 3339 (2026-10-18T00:00:00Z) or YYYY-MM-DDStart of the range (inclusive). If omitted, 24 hours before to
toRFC 3339 or YYYY-MM-DDEnd of the range (exclusive). If omitted, now (UTC)
limitintegerRows per page. Default 100, max 500 (values above 500 become 500)
  • YYYY-MM-DD means 00:00 UTC on that date. To query a day in another time zone, send RFC 3339 with an offset (e.g. 2026-10-18T00:00:00+08:00).
  • A malformed from or to is replaced with the default (last 24 hours) without an error. If results are not what you expect, check the parameter encoding first.
  • Percent-encode query values and sign the exact string you send. In particular, the + in +08:00 must be encoded as %2B. If it is not encoded, + is read as a space, date parsing fails, and the default is applied. Sending UTC (Z) avoids this problem.

Practice games are not recorded

Hands where no real money changes hands, such as practice games where a player sits alone against the computer, are not recorded in round or transaction records.

GET /operator/v1/rounds ​

Returns the rounds in which this operator's players took part, one row per player × round. If three of your players were in one round, there are three rows. Only rounds that have been settled (settled) or voided (void) are listed; rounds in progress appear after they finish.

Query parameters ​

ParameterTypeRequiredDescription
from, totimeNoRange [from, to) based on the round start time. Even with sort=settled_at, the range filters by start time
external_idstringNoOnly this player's rounds
game_codestringNoOnly rounds of this game (Game codes)
sortstarted_at | settled_atNoSort key. Default started_at
orderdesc | ascNoSort direction. Default desc (newest first)
limitintegerNoDefault 100, max 500
cursorstringNonext_cursor from the previous response

Request example ​

http
GET /operator/v1/rounds?external_id=user_1001&from=2026-10-18T00:00:00Z&to=2026-10-19T00:00:00Z&limit=100 HTTP/1.1
Host: {API_HOST}
X-Operator: acme
X-Api-Key: ak_live_…
X-Timestamp: 1760839200123
X-Nonce: 9f2c4e1a7b3d5e6f8a9b0c1d
X-Signature: 3b6f…

Response example ​

json
{
  "rounds": [
    {
      "round_id": "rnd_0199f3a2c4e57b1d9a2e4f6081c3d5e7",
      "game_code": "live_holdem",
      "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
      "started_at": "2026-10-18T14:10:00.123456Z",
      "settled_at": "2026-10-18T14:11:03.5Z",
      "status": "settled",
      "external_id": "user_1001",
      "bet": 12000,
      "win": 30400,
      "rake": 600,
      "currency": "PHP",
      "hand_no": 66,
      "table_name": "Live Hold'em 1",
      "table_names": { "en": "Live Hold'em 1", "ko": "라이브 홀덤 1" }
    }
  ],
  "next_cursor": "AZnzosTlex2aLk9ggcPV5wGZ86LE5XsdmiwvYIHD1ec"
}

Response fields ​

FieldTypeDescription
round_idstringRound ID (rnd_…)
game_codestringGame code
table_idstringTable ID (tbl_…)
started_at, settled_attimeStart time / settlement (or void) time
statusstringsettled (settled) or void (voided — a round whose bets were returned)
external_idstringPlayer
betint64Amount paid in this round (sum of bets, blinds, antes, calls, etc.)
winint64Amount received in this round (after rake). 0 for a lost round
rakeint64Rake / commission taken from this player's share
currencystringTable currency
hand_noint64Round number within the table
table_name, table_namesstring, objectTable name / names by language
next_cursorstring | nullCursor for the next page. null on the last page
  • Profit/loss is win − bet. Buy-in flow games (Hold'em, etc.) do not call the wallet every round, so this value is profit/loss in chips; wallet transactions occur separately per table session (buy_in, top_up, cash_out). → GET /sessions/{session_id}
  • Player rows of the same round are ordered by round ID, then by player.

Pagination ​

Pass next_cursor as cursor together with the same sort, order, and filters. The cursor is an opaque string; do not parse or construct it. If a page has fewer rows than limit, next_cursor is null (if the row count is exactly limit, the next call may return an empty list).

When sorting with sort=settled_at, rows without a settlement time are treated as the latest.

Errors ​

HTTPCodeSituation
500INTERNAL_ERRORTemporary server error — retry after a short while

Authentication, signature, and rate limit errors are common to all endpoints. → Error codes

GET /operator/v1/rounds/{round_id} ​

Details of a single round. There are two views depending on whether you pass external_id.

ViewRequestPurposeContents
Player view?external_id=user_1001Bet details the site shows to the member themselvesThat member's hole cards (cards.player), bet summary (bets), that member's share of the rake, that member's wallet transactions
Full roundNo external_idOperator admin lookups and dispute checksRound money (money), progress log (history), card flow (hands), and dealer card input (card_log) for your own table in independent mode

Both views include the round money (money).

Path and query parameters ​

ParameterTypeRequiredDescription
round_idstring (path)YesRound ID (rnd_…)
external_idstring (query)NoThe player to view the round as. If that player was not in this round, ROUND_NOT_FOUND

Visibility ​

  • A round in which none of this operator's players took part returns 404 ROUND_NOT_FOUND. A round in progress returns the same error because there is no participant settlement record yet.
    • Exception: rounds on an independent mode operator's own dedicated table can be opened in the full round view (without external_id) even with no participants (to check dealer card input). In this case money is an empty shape.
  • Players of other operators sit at shared tables too. For those players, only the seat and amounts are visible; their identity (external_id, nickname) and unrevealed hole cards are not.
  • Wallet transactions and bet items include only this operator's players.

Do not pass the player view response to members as-is

The player view (external_id) response also includes money in its full round shape, which contains other members' external_id, nicknames, and amounts at the same round, as well as those members' wallet transactions (money.transactions). On the member screen, show only the member's own data such as cards, bets, rake, and transactions.

Request example ​

http
GET /operator/v1/rounds/rnd_0199f3a2c4e57b1d9a2e4f6081c3d5e7 HTTP/1.1
Host: {API_HOST}
X-Operator: acme
X-Api-Key: ak_live_…
X-Timestamp: 1760839200123
X-Nonce: 1c7d0e5b9a2f4c6e8d1b3a5f
X-Signature: 8e1a…

Response — top-level fields ​

FieldTypeDescription
round_id, game_code, table_id, hand_noRound identification
statusstringsettled or void (rounds on your own table in independent mode can also show open while in progress)
started_at, settled_attimeStart / settlement time
currencystringTable currency
resultobject | nullRaw outcome from the game engine. The shape differs per game (see result below)
video_urlnullReserved field. Currently always null
moneyobjectRound money — who paid and received how much (money)
cardsobject{ "player": [...] } — in the player view, that member's final cards; otherwise null
betsarrayIn the player view, a one-line summary [{ "spot": "hand", "amount": amount paid, "win": amount received }]; otherwise []
rakeint64In the player view, that member's share; otherwise the rake for the whole round (money.totals.rake)
transactionsarrayIn the player view, that member's wallet transactions; otherwise the same as money.transactions
historyarrayFull round view only. Progress log (history)
handsarrayFull round view only. Card flow per player of this operator (hands)
card_logarrayFull round view + rounds on your own table in independent mode only. Dealer card input records (same shape as entries of GET /dealer-card-log)

bets in the player view is a one-line summary regardless of the game. Baccarat per-spot bets and Blackjack per-box bets are in money.bets.

money — round money ​

FieldTypeDescription
kindstringp2p (player vs. player — Texas Hold'em, Pot-Limit Omaha, Live Hold'em, Seotda, Badugi, Matgo, Go-Stop, Tong-its) or banked (against the house — Speed Hold'em, Blackjack, Baccarat, Red Dog)
currencystringCurrency
players[]arrayTotals per participant (table below)
pots[]arrayMoney movement per pot. Hold'em family, Seotda, and Badugi only, and only for rounds with records (omitted otherwise)
returns[]arrayUncalled excess bets returned [{seat, external_id, amount}] (omitted if none)
rake_ruleobjectRake rule applied to the round (Hold'em family; omitted if none)
rake_on_profitboolIf true, the dealer fee is taken from the winner's net profit rather than the pot (Seotda, Badugi). In this case the pot shares are amounts before the dealer fee, and the actual dealer fee is the per-seat rake
bets[]arrayBet items of this operator's players (banked)
transactions[]arrayThis operator's players' wallet transactions tied to this round (banked. For buy-in flow games, wallet transactions are tied to the table session, so this is usually an empty array)
totalsobjectTotals

players[]

FieldTypeDescription
external_idstring | nullThe value if the player belongs to this operator; null for another operator's player
nicknamestringOnly for this operator's players (omitted otherwise)
seatintSeat number. -1 for games without seats (Baccarat, etc.)
debitint64Amount paid in this round
creditint64Amount received in this round (after rake)
rakeint64This player's share of rake / commission
netint64credit − debit
stack_before, stack_afterint64Seat chips before / after the round (only for games played with seat chips — P2P, Red Dog)
mineboolWhether this is this operator's player

bets[] — { external_id, spot, amount, win, status }. spot differs per game: for Baccarat player, banker, tie, player_pair, banker_pair, either_pair, perfect_pair, player_bonus, banker_bonus; for Speed Hold'em and Blackjack box_N (Blackjack additional decisions are box_N:extra). win is the amount received (including the stake), and status is settled or rolled_back.

transactions[] — { tx_id, external_id, type, reason, amount, status, balance_after, created_at, completed_at, ref_tx_id }. Each field has the same meaning as in GET /transactions.

totals — { debit, credit, rake, house, balanced }

FieldDescription
debit, credit, rakeSum over all participants (including other operators)
houseHouse profit/loss = debit − credit. For P2P, equal to the rake
balancedFor P2P, true if debit = credit + rake (no chips leaked). Always true for banked

pots[] (Hold'em family example)

FieldDescription
index0 = main pot, 1+ = side pots
amountPot total
rake, rake_full, rake_cumRake actually taken / rake that would have been taken at the full rate (before the cap) / cumulative from the main pot up to this pot
cappedWhether less was taken because the per-round maximum fee (cap) was reached
flooredAmount not taken due to rounding down fractions (only when present)
eligibleSeats eligible to win this pot
contributions[], shares[]Seats and amounts that put in / received {seat, external_id, amount} (external_id: null for other operators' players)
rankWinning hand rank (only when present)
balancedSum put in = pot total = sum received + rake

rake_rule — { percent, cap, no_flop_no_drop, floor?, skip? }. percent is in bp (500 = 5%), cap is the per-round maximum fee (0 = none), and skip is the reason no rake was taken (off, no_flop).

history — progress log ​

A chronological list of how the round progressed. Each item has ts and kind; the remaining fields are filled depending on kind (fields without a value are omitted).

kindMeaningMain fields
startRound startno (round number), seats[] ({seat, player, chips}), button, sb, bb, amount (ante)
dealHole card dealseat, player, cards
cardA single card (live dealer input / automatic)position, cards, stage, source (scanner, wedge, manual, virtual, auto), dealer, reason (rejection code)
streetNext streetstage, board
bettingBetting opened / closedaction (open, closed), stage, amount
actionPlayer decisionseat, player, action (fold, check, call, bet, raise, allin, hit, stand, go, stop …), amount, total, pot, stack, auto (automatic decision on timeout)
bet, bet_undoBanked bet / bet cancellationplayer, amount, items[] ({spot, amount, win, status})
showdownHand revealseat, player, cards, rank, best
resultOutcome / distributionpots[] ({index, amount, rake, rank, winners[]}), rake, board, outcome
txWallet transactionplayer, action (debit, credit, rollback), reason, amount, status
voidVoidreason
settleFinal settlement (always the last line)rows[] ({seat, player, invested, won, rake, net, stack_after}), amount, won, rake
eventOther eventstype, data
  • player is { "external_id", "nickname" }; for another operator's player it is { "other": true }.
  • A null element in cards is a hidden card (another operator's player's unrevealed hole card).
  • The dealer name (dealer) is filled only for rounds on your own table in independent mode.
  • The progress log is for display. kind values and fields may be added, so implement it to skip unknown values. Base money calculations on money.

hands — card flow ​

For each of this operator's players, summarizes first cards received → cards exchanged/discarded during the round → cards after the round on one line. Sorted by seat; other operators' players are excluded.

FieldDescription
seat, playerSeat / {external_id, nickname}
dealtFirst cards received (Hold'em family hole cards, 4 cards in Badugi, 2 cards in Seotda, opening hand in Tong-its / Matgo)
steps[]Hand changes during the round {stage?, out, in} — out cards discarded (played), in cards received. In Badugi, stage is the draw turn (draw_1 morning, draw_2 afternoon, draw_3 evening), and turns with no exchange remain as empty arrays
heldCards left in hand at the end of the round (only when different from final — Matgo, Go-Stop)
finalCards after the round (for Matgo and Go-Stop, the captured cards)
boardCommunity cards (Hold'em family)
best, rankBest cards and hand rank when the hand was revealed

Cards are written as 2-character playing card codes (AS, TD, 9H); hwatu cards are 2-digit month + letter (01a, 05b).

Example 1 — Texas Hold'em (P2P, full round view) ​

json
{
  "round_id": "rnd_0199f3a2c4e57b1d9a2e4f6081c3d5e7",
  "game_code": "holdem_nlh",
  "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
  "hand_no": 66,
  "status": "settled",
  "started_at": "2026-10-18T14:10:00.123456Z",
  "settled_at": "2026-10-18T14:11:03.5Z",
  "currency": "PHP",
  "result": {
    "board": ["2C", "3D", "4H", "9S", "TC"],
    "awards": [{ "pot_index": 0, "seats": [3], "amount": 1520, "shares": [{ "seat": 3, "amount": 1520 }], "rake": 80, "rank": "two_pair", "best5": ["9S", "9D", "4H", "4C", "TC"] }],
    "rake": 80,
    "showdown": true,
    "pots": [ "…" ],
    "returns": [],
    "rake_rule": { "percent": 500, "cap": 0, "no_flop_no_drop": true }
  },
  "video_url": null,
  "money": {
    "kind": "p2p",
    "currency": "PHP",
    "players": [
      { "external_id": null, "seat": 3, "debit": 800, "credit": 1520, "rake": 40, "net": 720, "stack_before": 20000, "stack_after": 20720, "mine": false },
      { "external_id": "user_1001", "nickname": "lucky7", "seat": 4, "debit": 800, "credit": 0, "rake": 40, "net": -800, "stack_before": 32000, "stack_after": 31200, "mine": true }
    ],
    "pots": [
      {
        "index": 0, "amount": 1600, "rake": 80, "rake_full": 80, "rake_cum": 80, "capped": false,
        "eligible": [3, 4],
        "contributions": [ { "seat": 3, "external_id": null, "amount": 800 }, { "seat": 4, "external_id": "user_1001", "amount": 800 } ],
        "shares": [ { "seat": 3, "external_id": null, "amount": 1520 } ],
        "rank": "two_pair",
        "balanced": true
      }
    ],
    "rake_rule": { "percent": 500, "cap": 0, "no_flop_no_drop": true },
    "bets": [],
    "transactions": [],
    "totals": { "debit": 1600, "credit": 1520, "rake": 80, "house": 80, "balanced": true }
  },
  "cards": { "player": null },
  "bets": [],
  "rake": 80,
  "transactions": [],
  "history": [
    { "ts": "2026-10-18T14:10:00.2Z", "kind": "start", "type": "hand_start", "no": 66, "button": 3, "sb": 3, "bb": 4,
      "seats": [ { "seat": 3, "player": { "other": true }, "chips": 20000 }, { "seat": 4, "player": { "external_id": "user_1001", "nickname": "lucky7" }, "chips": 32000 } ] },
    { "ts": "2026-10-18T14:10:00.3Z", "kind": "deal", "seat": 3, "player": { "other": true }, "cards": [null, null] },
    { "ts": "2026-10-18T14:10:00.3Z", "kind": "deal", "seat": 4, "player": { "external_id": "user_1001", "nickname": "lucky7" }, "cards": ["AS", "KD"] },
    { "ts": "2026-10-18T14:10:05.1Z", "kind": "action", "type": "action", "seat": 3, "player": { "other": true }, "action": "call", "amount": 200, "pot": 400, "stack": 19800 },
    { "ts": "2026-10-18T14:10:09.8Z", "kind": "street", "stage": "flop", "board": ["2C", "3D", "4H"] },
    { "ts": "2026-10-18T14:11:02.9Z", "kind": "showdown", "seat": 3, "player": { "other": true }, "cards": ["9D", "4C"], "rank": "two_pair", "best": ["9S", "9D", "4H", "4C", "TC"] },
    { "ts": "2026-10-18T14:11:03.5Z", "kind": "settle", "amount": 1600, "won": 1520, "rake": 80,
      "rows": [ { "seat": 3, "player": { "other": true }, "invested": 800, "won": 1520, "rake": 40, "net": 720, "stack_after": 20720 },
                { "seat": 4, "player": { "external_id": "user_1001", "nickname": "lucky7" }, "invested": 800, "won": 0, "rake": 40, "net": -800, "stack_after": 31200 } ] }
  ],
  "hands": [
    { "seat": 4, "player": { "external_id": "user_1001", "nickname": "lucky7" },
      "dealt": ["AS", "KD"], "final": ["AS", "KD"], "board": ["2C", "3D", "4H", "9S", "TC"], "best": ["AS", "KD", "TC", "9S", "4H"], "rank": "high_card" }
  ]
}

Querying the same round with ?external_id=user_1001 drops history and hands and changes the following values.

json
{
  "cards": { "player": ["AS", "KD"] },
  "bets": [ { "spot": "hand", "amount": 800, "win": 0 } ],
  "rake": 40,
  "transactions": []
}

Example 2 — Live Baccarat (banked) ​

In banked games the wallet moves every round, so money.bets holds the per-spot bets and money.transactions holds that round's debits and credits. There are no pots.

json
{
  "round_id": "rnd_0199f3b0aa127c3e8f45d6a7b8c9d0e1",
  "game_code": "live_baccarat",
  "status": "settled",
  "currency": "PHP",
  "result": {
    "winner": "banker",
    "player": { "cards": ["9H", "7C", "KD"], "total": 6, "natural": false, "pair": false, "perfect": false },
    "banker": { "cards": ["8S", "QH"], "total": 8, "natural": true, "pair": false, "perfect": false },
    "shoe_no": 12, "hand_no": 31,
    "winning_spots": ["banker"], "push_spots": [],
    "total_bet": 150000, "total_win": 97500, "players": 14,
    "winners": [ { "name": "lucky7", "amount": 9500 } ]
  },
  "money": {
    "kind": "banked",
    "currency": "PHP",
    "players": [
      { "external_id": "user_1001", "nickname": "lucky7", "seat": -1, "debit": 6000, "credit": 9500, "rake": 0, "net": 3500, "mine": true }
    ],
    "bets": [
      { "external_id": "user_1001", "spot": "banker", "amount": 5000, "win": 9500, "status": "settled" },
      { "external_id": "user_1001", "spot": "tie", "amount": 1000, "win": 0, "status": "settled" }
    ],
    "transactions": [
      { "tx_id": "tx_0199f3b0ab3c7d118e2f3a4b5c6d7e8f", "external_id": "user_1001", "type": "debit", "reason": "bet", "amount": 6000,
        "status": "ok", "balance_after": 1244000, "created_at": "2026-10-18T14:20:11.402Z", "completed_at": "2026-10-18T14:20:11.455Z", "ref_tx_id": null },
      { "tx_id": "tx_5c0e9a7d3b1f5e2a8c4d6b0f9e1a3c5d", "external_id": "user_1001", "type": "credit", "reason": "settle", "amount": 9500,
        "status": "ok", "balance_after": 1253500, "created_at": "2026-10-18T14:20:41.020Z", "completed_at": "2026-10-18T14:20:41.071Z", "ref_tx_id": null }
    ],
    "totals": { "debit": 6000, "credit": 9500, "rake": 0, "house": -3500, "balanced": true }
  }
}

(Some fields are omitted from the example above.)

Example 3 — hands by game ​

Badugi — the three draws (steps) are kept together with the turn names.

json
{ "seat": 2, "player": { "external_id": "user_1001", "nickname": "lucky7" },
  "dealt": ["AS", "2H", "KS", "QS"],
  "steps": [
    { "stage": "draw_1", "out": ["KS", "QS"], "in": ["3D", "QS"] },
    { "stage": "draw_2", "out": [], "in": [] },
    { "stage": "draw_3", "out": ["QS"], "in": ["4C"] }
  ],
  "final": ["AS", "2H", "3D", "4C"], "rank": "badugi" }

Matgo / Go-Stop — steps are the cards played, final the captured cards, and held the cards left in hand at the end of the round.

json
{ "seat": 0, "player": { "external_id": "user_1001", "nickname": "lucky7" },
  "dealt": ["01a", "05b", "07c"],
  "steps": [ { "out": ["01a"], "in": [] } ],
  "held": ["05b", "07c"],
  "final": ["01a", "01b"] }

result — raw game outcome ​

result is the raw outcome the game engine records when it finishes a round, so its shape differs per game and fields may be added without notice. Use it as a reference when displaying the outcome on screen, but use money for amount calculations and reconciliation.

GameMain fields
Hold'em family (holdem_nlh, holdem_plo4, live_holdem)board, awards[] ({pot_index, seats, amount, shares, rake, rank, best5}), rake, showdown, pots, returns, rake_rule
Baccarat (live_baccarat)winner (player, banker, tie), player, banker ({cards, total, natural, pair, perfect}), shoe_no, hand_no, winning_spots, push_spots, total_bet, total_win, players, winners[] ({name, amount})
Voided round (all games){ "void_reason": "…" }

Errors ​

HTTPCodeSituation
404ROUND_NOT_FOUNDMalformed ID, nonexistent round, a round without any of this operator's players, a round in progress, or the external_id player was not in this round
500INTERNAL_ERRORTemporary server error

GET /operator/v1/sessions/{session_id} ​

Details of a table session (from sitting down once until standing up) in buy-in flow games (Texas Hold'em, Pot-Limit Omaha, Live Hold'em, Seotda, Matgo, Tong-its, Red Dog, etc.). These games do not call the wallet every round; they create wallet transactions only when the player sits down (buy_in), tops up (top_up), or leaves the seat (cash_out), and the round_id of those transactions is the table session ID (tss_…). This API aggregates the per-round profit/loss during the session and shows whether the buy-in and cash-out match the per-round profit/loss.

Get the session_id from the round_id of GET /transactions (a value starting with tss_), or from the round_id in the wallet callback body.

Path parameters ​

ParameterTypeRequiredDescription
session_idstringYesTable session ID (tss_…)

Request example ​

bash
curl {API_BASE}/operator/v1/sessions/tss_0199f2e1d0c3b4a5968778695a4b3c2d \
  -H 'X-Operator: acme' -H 'X-Api-Key: ak_live_…' \
  -H 'X-Timestamp: 1760839200123' -H 'X-Nonce: 5e8f…' -H 'X-Signature: 0d4c…'

Response example ​

json
{
  "session_id": "tss_0199f2e1d0c3b4a5968778695a4b3c2d",
  "external_id": "user_1001",
  "nickname": "lucky7",
  "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
  "table_name": "Hold'em 2/4",
  "table_names": { "en": "Hold'em 2/4" },
  "game_code": "holdem_nlh",
  "currency": "PHP",
  "status": "closed",
  "opened_at": "2026-10-18T13:02:11.08Z",
  "closed_at": "2026-10-18T14:30:45.91Z",
  "buy_in_total": 960000,
  "cash_out": 302582,
  "chips": 0,
  "rounds": [
    { "round_id": "rnd_0199f3a2c4e57b1d9a2e4f6081c3d5e7", "hand_no": 68, "status": "settled",
      "started_at": "2026-10-18T13:05:00.1Z", "settled_at": "2026-10-18T13:06:12.4Z",
      "seat": 3, "debit": 324000, "credit": 944000, "rake": 8000, "net": 620000, "stack_after": 944000 }
  ],
  "transactions": [
    { "tx_id": "tx_7a1b2c3d4e5f60718293a4b5c6d7e8f9", "external_id": "user_1001", "type": "debit", "reason": "buy_in", "amount": 480000,
      "status": "ok", "balance_after": 1520000, "created_at": "2026-10-18T13:02:11.1Z", "completed_at": "2026-10-18T13:02:11.2Z", "ref_tx_id": null },
    { "tx_id": "tx_1f2e3d4c5b6a79880f1e2d3c4b5a6978", "external_id": "user_1001", "type": "credit", "reason": "cash_out", "amount": 302582,
      "status": "ok", "balance_after": 1342582, "created_at": "2026-10-18T14:30:45.9Z", "completed_at": "2026-10-18T14:30:46.0Z", "ref_tx_id": null }
  ],
  "totals": { "rounds": 15, "debit": 1997529, "credit": 1340111, "rake": 58319, "net": -657418 },
  "reconcile": { "paid_in": 960000, "paid_out": 302582, "unsettled": 0, "expected_end": 302582, "actual_end": 302582, "balanced": true, "closed": true }
}

Response fields ​

FieldTypeDescription
session_id, external_id, nicknameSession / player
table_id, table_name, table_names, game_code, currencyTable information
statusstringopening (buy-in in progress), seated (seated), kept (Live Hold'em — the player has left the seat with chips held), closing (leaving the seat in progress), closed (finished)
opened_at, closed_attimeSit-down / leave time (closed_at is null until finished)
buy_in_totalint64Total of requested buy-ins and top-ups. Includes requested amounts whose wallet debit failed, so use reconcile.paid_in for reconciliation
cash_outint64 | nullCash-out amount (null until finished)
chipsint64Current seat chips (usually 0 for a finished session)
rounds[]arrayRounds played during the session (in start order, up to 5,000 rounds): round_id, hand_no, status, started_at, settled_at, seat, debit, credit, rake, net, stack_after
transactions[]arrayWallet transactions of this session (in creation order)
totalsobjectSums of rounds (number of rounds), debit, credit, rake, net
reconcileobjectSession reconciliation result (below)

reconcile

FieldDescription
paid_inMoney actually received from the wallet = successful debits (buy-in, top-up) − successful rollbacks of those debits
paid_outMoney paid out to the wallet = sum of cash-out credits (not failed)
unsettledNumber of credits and rollbacks not yet finished (pending, uncertain)
expected_endpaid_in + Σcredit − Σdebit — the ending amount calculated from per-round profit/loss
actual_endpaid_out for a finished session; current chips for an open session
balancedtrue if expected_end = actual_end and unsettled = 0
closedWhether the session has finished through cash-out

balanced: false together with unsettled > 0 means the cash-out credit is still being retried. Query again after a short while.

Errors ​

HTTPCodeSituation
404SESSION_NOT_FOUNDMalformed ID, nonexistent session, or another operator's session
500INTERNAL_ERRORTemporary server error

GET /operator/v1/transactions ​

The ledger of debit, credit, and rollback transactions the platform sent to this operator's wallet (balance queries are not recorded). Use it for reconciliation, matching against the operator wallet ledger by tx_id.

Query parameters ​

ParameterTypeRequiredDescription
from, totimeNo[from, to) based on the time the platform created the transaction (created_at). Default: last 24 hours
statusok | failed | pending | uncertainNoOnly this status
typedebit | credit | rollbackNoOnly this type
external_idstringNoOnly this player
limitintegerNoDefault 100, max 500
cursorstringNonext_cursor from the previous response

Request example ​

http
GET /operator/v1/transactions?from=2026-10-18T00:00:00Z&to=2026-10-19T00:00:00Z&limit=500 HTTP/1.1
Host: {API_HOST}
X-Operator: acme
X-Api-Key: ak_live_…
X-Timestamp: 1760839200123
X-Nonce: 2b4d6f8a0c1e3a5b7d9f1a3c
X-Signature: c71e…

Response example ​

json
{
  "transactions": [
    {
      "tx_id": "tx_0199f3b0ab3c7d118e2f3a4b5c6d7e8f",
      "type": "debit",
      "ref_tx_id": null,
      "external_id": "user_1001",
      "amount": 6000,
      "currency": "PHP",
      "reason": "bet",
      "round_id": "rnd_0199f3b0aa127c3e8f45d6a7b8c9d0e1",
      "game_code": "live_baccarat",
      "table_id": "tbl_0198d2e3f4a57b6c9d0e1f2a3b4c5d6e",
      "status": "ok",
      "operator_tx_id": "w-1a2b3c4d5e6f",
      "balance_after": 1244000,
      "created_at": "2026-10-18T14:20:11.402Z",
      "completed_at": "2026-10-18T14:20:11.455Z"
    },
    {
      "tx_id": "tx_7a1b2c3d4e5f60718293a4b5c6d7e8f9",
      "type": "debit",
      "ref_tx_id": null,
      "external_id": "user_2002",
      "amount": 480000,
      "currency": "PHP",
      "reason": "buy_in",
      "round_id": "tss_0199f2e1d0c3b4a5968778695a4b3c2d",
      "game_code": "holdem_nlh",
      "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
      "status": "ok",
      "operator_tx_id": "w-9f8e7d6c5b4a",
      "balance_after": 1520000,
      "created_at": "2026-10-18T13:02:11.1Z",
      "completed_at": "2026-10-18T13:02:11.2Z"
    }
  ],
  "next_cursor": "tx_7a1b2c3d4e5f60718293a4b5c6d7e8f9"
}

Response fields ​

FieldTypeDescription
tx_idstringTransaction ID (tx_…). Same as the tx_id in the wallet callback body
typestringdebit, credit, rollback
ref_tx_idstring | nullFor a rollback, the tx_id of the debit it cancels. Otherwise null
external_idstringPlayer
amountint64Amount (in the currency's minor unit, 0 or more)
currencystringCurrency
reasonstringReason (buy_in, top_up, cash_out, bet, ante, double, split, settle, rollback reasons, etc.). For the list of values, see debit, credit, and rollback
round_idstring | nullAs sent in the wallet request — the round (rnd_…) for the bet flow, the table session (tss_…) for the buy-in flow
game_code, table_idstring | nullAs sent in the wallet request
statusstringSee the table below
operator_tx_idstring | nullThe operator_tx_id the operator returned in the wallet response
balance_afterint64 | nullbalance from the wallet response (null if it was not in the response)
created_attimeWhen the platform created the transaction (just before the first attempt)
completed_attime | nullWhen it finished (became ok, failed, or uncertain). null while in progress
next_cursorstring | nullCursor for the next page. null on the last page

status

ValueMeaningExpected state in the operator wallet
okThe wallet responded with OKApplied
failedThe debit was rejected — the wallet returned a status other than OK, or in prepaid mode the prepaid credit was insufficient and the debit was failed without calling the walletNot applied (the call may not have been made at all)
uncertainNo response was received for the debit (timeout, connection error, UNKNOWN_ERROR). The platform immediately sends a rollback with this debit as ref_tx_idDebit and its rollback combined net to zero
pendingIn progress, or a credit/rollback being retried until it receives OKNot final yet — check again later
  • Credits and rollbacks are retried until they receive OK, so their final status is ok. If a credit or rollback stays pending for a long time, check the wallet-side responses. → Wallet callback overview
  • A debit that stays pending for too long is cleaned up by the platform sending a rollback.

Sorting and pagination ​

  • Results are in descending tx_id order. New transaction IDs increase over time, but IDs derived deterministically from other transactions, such as settlement credits and rollbacks, are not time-ordered, so this is not strictly newest-first. If you need chronological order, sort by created_at after receiving the data.
  • next_cursor is the tx_id of the last row on that page. Passing it as cursor with the same filters and the same from and to continues from smaller tx_id values. With a fixed range, there are no gaps or duplicates between pages.
  • If there are fewer rows than limit, next_cursor is null (if there are exactly limit rows, the next call may return an empty list).
  • If to is omitted, "now" changes on every call. When fetching everything, fix from and to.

Fetching everything — example code ​

ts
import { createHmac, randomBytes } from 'node:crypto';

const ORIGIN = '{API_BASE}';        // Per-environment address (provided during onboarding)
const OPERATOR = process.env.OPERATOR_CODE!;     // e.g. acme
const API_KEY = process.env.OPERATOR_API_KEY!;
const API_SECRET = process.env.OPERATOR_API_SECRET!;

// Signed GET — sign the path exactly as encoded, including the query, and send it as-is
async function signedGet<T>(path: string): Promise<T> {
  const ts = String(Date.now());
  const nonce = randomBytes(12).toString('hex');
  const sig = createHmac('sha256', API_SECRET)
    .update(`${ts}\n${nonce}\nGET\n${path}\n`) // GET has no body (empty string)
    .digest('hex');
  const res = await fetch(ORIGIN + path, {
    headers: { 'X-Operator': OPERATOR, 'X-Api-Key': API_KEY, 'X-Timestamp': ts, 'X-Nonce': nonce, 'X-Signature': sig },
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json() as Promise<T>;
}

type Tx = { tx_id: string; type: string; ref_tx_id: string | null; external_id: string; amount: number; status: string; created_at: string };

export async function fetchAllTransactions(from: string, to: string): Promise<Tx[]> {
  const all: Tx[] = [];
  let cursor: string | null = null;
  do {
    const q = new URLSearchParams({ from, to, limit: '500' }); // ':' is encoded as %3A
    if (cursor) q.set('cursor', cursor);
    const page = await signedGet<{ transactions: Tx[]; next_cursor: string | null }>(`/operator/v1/transactions?${q}`);
    all.push(...page.transactions);
    cursor = page.next_cursor;
  } while (cursor);
  return all;
}

// e.g. all of yesterday (UTC). Fix `to` sufficiently in the past
const rows = await fetchAllTransactions('2026-10-18T00:00:00Z', '2026-10-19T00:00:00Z');
python
import hashlib, hmac, os, secrets, time
from urllib.parse import urlencode
import requests

ORIGIN = "{API_BASE}"
OPERATOR = os.environ["OPERATOR_CODE"]
API_KEY = os.environ["OPERATOR_API_KEY"]
API_SECRET = os.environ["OPERATOR_API_SECRET"]

def signed_get(path: str) -> dict:
    """path is the string encoded including the query — send it exactly as signed."""
    ts = str(int(time.time() * 1000))
    nonce = secrets.token_hex(12)
    msg = f"{ts}\n{nonce}\nGET\n{path}\n".encode()  # GET has no body
    sig = hmac.new(API_SECRET.encode(), msg, hashlib.sha256).hexdigest()
    r = requests.get(ORIGIN + path, timeout=10, headers={
        "X-Operator": OPERATOR, "X-Api-Key": API_KEY,
        "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sig,
    })
    r.raise_for_status()
    return r.json()

def fetch_all_transactions(frm: str, to: str) -> list[dict]:
    rows, cursor = [], None
    while True:
        params = {"from": frm, "to": to, "limit": 500}
        if cursor:
            params["cursor"] = cursor
        page = signed_get("/operator/v1/transactions?" + urlencode(params))
        rows.extend(page["transactions"])
        cursor = page["next_cursor"]
        if not cursor:
            return rows

rows = fetch_all_transactions("2026-10-18T00:00:00Z", "2026-10-19T00:00:00Z")

Calls are limited to 200 per second per operator. When fetching in bulk, use limit=500 to reduce the number of calls.

Errors ​

HTTPCodeSituation
500INTERNAL_ERRORTemporary server error

A malformed cursor is ignored without an error, and the first page is returned.

GET /operator/v1/reports/ggr ​

GGR (gross gaming revenue) aggregated by date × game over settled rounds.

Query parameters ​

ParameterTypeRequiredDescription
from, totimeNo[from, to) based on the round's settlement time. Default: last 24 hours
game_codestringNoOnly this game
external_idstringNoOnly this player's bets, wins, and rake. For a nonexistent player, an empty result { "currency": "", "rows": [] }

Other parameters (grouping key, time zone, etc.) are not supported and are ignored if sent.

Request example ​

http
GET /operator/v1/reports/ggr?from=2026-10-01T00:00:00Z&to=2026-10-19T00:00:00Z HTTP/1.1
Host: {API_HOST}
X-Operator: acme
X-Api-Key: ak_live_…
X-Timestamp: 1760839200123
X-Nonce: 7c9e1a3b5d7f9a1c3e5b7d9f
X-Signature: 52aa…

Response example ​

json
{
  "currency": "PHP",
  "rows": [
    { "day": "2026-10-18", "game_code": "live_baccarat", "rounds": 640, "players": 57, "bet": 18250000, "win": 17310000, "rake": 0, "ggr": 940000 },
    { "day": "2026-10-18", "game_code": "live_holdem", "rounds": 1203, "players": 81, "bet": 0, "win": 0, "rake": 412000, "ggr": 412000 }
  ]
}

Response fields ​

FieldTypeDescription
currencystringCurrency of the amounts (see the note below)
rows[].daystringDate YYYY-MM-DD — the settlement time cut by UTC date
rows[].game_codestringGame code
rows[].roundsint64Number of rounds
rows[].playersint64Number of participating players (distinct within that day and game)
rows[].bet, winint64Total bets and total payouts for banked games. 0 for P2P games
rows[].rakeint64Rake attributed to this operator's players' share in P2P games. 0 for banked
rows[].ggrint64bet − win + rake
  • For P2P games (Hold'em, etc.), GGR is the rake. Chips exchanged between players are not included in GGR.
  • For banked games (Baccarat, etc.), GGR is bet − win.
  • Rows are ordered by date, then game code. Voided rounds do not affect the GGR amounts, but depending on the game they may be counted in rounds and players.

Currency

All amounts are integers in the currency's minor unit (e.g. for PHP, centavos: 1250000 = ₱12,500.00). Check the number of decimal places with exponent of GET /currencies.

The response has only one currency, and rows has no currency column. Aggregation is done separately per currency internally, so for operators using tables in two or more currencies, the same date/game row appears multiple times, once per currency, and they cannot be told apart. If you use multiple currencies, aggregate per-currency figures yourself with GET /rounds (currency on every row).

Errors ​

HTTPCodeSituation
500INTERNAL_ERRORTemporary server error

Reconciliation ​

Because the operator wallet is the source of truth for player balances, we recommend regularly checking that the platform ledger (GET /transactions) and the operator wallet ledger match transaction by transaction. The common key between the two ledgers is tx_id.

Preparation — what to keep in the operator wallet ledger ​

When processing wallet callbacks (debit, credit, rollback), store at least the following.

ItemReason
tx_id (unique key)Reconciliation key and idempotency key
type, amount, currency, external_idComparing amounts and targets
ref_tx_id (rollback)Which debit was cancelled
round_id, reasonTracing the cause (if tss_…, you can follow up with GET /sessions)
Processing result (applied / rejected / ignored)Whether a debit was rejected, or a rollback did nothing because there was no target
The operator_tx_id you responded withCross-check against operator_tx_id in the platform ledger

Procedure ​

  1. Choose a range. E.g. all of yesterday, [2026-10-18T00:00:00Z, 2026-10-19T00:00:00Z). to should be at least a few minutes in the past so that in-flight transactions appear in their finished state. Fetch with a fixed from and to.
  2. Fetch the entire platform ledger. Repeat until next_cursor becomes null, as in the example code above.
  3. Prepare the operator ledger for the same range. The platform's created_at is when the platform created the transaction; the time the operator received the request may be slightly later (on retries) or much later. To avoid missing rows at the boundaries, extract the operator side with a margin before and after the range (e.g. ±1 hour), then check separately only those not found among the platform rows.
  4. Match by tx_id. Judge each platform row by the rules below.
Platform type / statusExpected state in the operator ledgerIf it does not match
debit / okThe same amount debited with the same tx_idAnomaly if there is no debit record or the amount differs — the platform has already granted chips / accepted the bet
debit / failedNot debited (only a rejection record, or no record because the call was never made due to insufficient prepaid credit)If it was debited, the player's money was taken but not reflected in the game — a refund is needed
debit / uncertainWhether or not it was debited, combined with the rollback whose ref_tx_id is this debit, the net change is zeroAnomaly if it was debited but the rollback was not applied
rollback / okReversed if the original debit was applied; if the original debit was never received, do nothing and return OKAnomaly if the original debit remains applied
credit / okThe same amount credited with the same tx_id (also record settle with amount 0)Anomaly if there is no credit record or the amount differs
All types / pendingNot final yetCheck again at the next reconciliation. Credits and rollbacks are retried until they receive OK, so check the wallet response status
  1. Check the opposite direction as well. A tx_id that exists in the operator ledger but not in the platform ledger (even over the wider range) may be a request that came from another environment or another operator code, or one that bypassed signature verification — investigate it.
  2. Verify totals (optional). For each player, Σ(ok debit) − Σ(ok rollbacks whose original debit was ok) − Σ(ok credit) should equal the same calculation on the operator side.

Additional checks for buy-in flow games ​

Buy-in flow games such as Hold'em have no per-round wallet transactions, so when a player reports that "chips disappeared", check at the table session level.

  1. Get the round_id (tss_…) from that player's buy_in, top_up, and cash_out transactions.
  2. Look at reconcile.balanced of GET /sessions/{session_id}. If true, buy-in + per-round profit/loss = cash-out.
  3. For per-round details, query the round details with each round_id in rounds[].

To be agreed

The reconciliation schedule, and the adjustment procedure and contact point when a mismatch is found, will be agreed upon at contract time.

Docs v1.0.0 · Operator API v1