라운드 · 거래 · 리포트
게임 기록과 돈의 흐름을 조회하는 API 입니다. 플레이어 베팅 내역 화면, 관리자 라운드 조회, 지갑 원장 대사(reconciliation), 일별 GGR 집계에 씁니다.
| 메서드 | 경로 | 용도 |
|---|---|---|
| GET | /operator/v1/rounds | 플레이어별 라운드 목록 |
| GET | /operator/v1/rounds/{round_id} | 라운드 상세 (플레이어 시점 · 회차 전체) |
| GET | /operator/v1/sessions/{session_id} | 바이인형 테이블 착석 1회(테이블 세션) 상세 |
| GET | /operator/v1/transactions | 지갑 거래 원장 (대사용) |
| GET | /operator/v1/reports/ggr | 일 · 게임별 GGR 집계 |
모든 요청은 인증 · 서명 규칙대로 서명합니다. GET 요청은 본문 없이 빈 문자열로 서명합니다.
공통 조회 파라미터
목록 API 는 기간 · 페이지 크기 파라미터를 같은 방식으로 읽습니다.
| 파라미터 | 형식 | 설명 |
|---|---|---|
from | RFC 3339 (2026-10-18T00:00:00Z) 또는 YYYY-MM-DD | 구간 시작(포함). 생략하면 to 의 24시간 전 |
to | RFC 3339 또는 YYYY-MM-DD | 구간 끝(제외). 생략하면 지금(UTC) |
limit | 정수 | 한 페이지 행 수. 기본 100, 최대 500 (500 보다 크면 500) |
YYYY-MM-DD는 그 날짜 UTC 0시입니다. 다른 시간대의 하루를 조회하려면 오프셋을 붙인 RFC 3339 로 보냅니다(예:2026-10-18T00:00:00+08:00).- 형식이 잘못된
from·to는 오류 없이 기본값(최근 24시간) 으로 바뀝니다. 결과가 예상과 다르면 먼저 파라미터 인코딩을 확인하세요. - 쿼리 값은 퍼센트 인코딩해서 보내고, 보낸 문자열 그대로 서명합니다. 특히
+08:00의+는 반드시%2B로 인코딩해야 합니다. 인코딩하지 않으면+가 공백으로 읽혀 날짜 파싱이 실패하고 기본값이 적용됩니다. UTC(Z)로 보내면 이 문제를 피할 수 있습니다.
연습 게임은 기록되지 않습니다
플레이어가 혼자 앉아 컴퓨터와 하는 연습 게임처럼 실제 돈이 오가지 않는 판은 라운드 · 거래 기록에 남지 않습니다.
GET /operator/v1/rounds
이 오퍼레이터 플레이어가 참여한 라운드를 플레이어 × 라운드 한 행씩 돌려줍니다. 한 판에 우리 플레이어가 세 명 있으면 세 행입니다. 정산(settled)되거나 무효(void) 처리된 판만 나오며, 진행 중인 판은 끝난 뒤에 나타납니다.
쿼리 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
from · to | 시각 | 아니오 | 라운드 시작 시각 기준 구간 [from, to). sort=settled_at 이어도 구간은 시작 시각으로 거릅니다 |
external_id | string | 아니오 | 이 플레이어의 라운드만 |
game_code | string | 아니오 | 이 게임의 라운드만 (게임 코드) |
sort | started_at | settled_at | 아니오 | 정렬 기준. 기본 started_at |
order | desc | asc | 아니오 | 정렬 방향. 기본 desc(최신순) |
limit | 정수 | 아니오 | 기본 100, 최대 500 |
cursor | string | 아니오 | 직전 응답의 next_cursor |
요청 예시
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…응답 예시
{
"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"
}응답 필드
| 필드 | 타입 | 설명 |
|---|---|---|
round_id | string | 라운드 ID(rnd_…) |
game_code | string | 게임 코드 |
table_id | string | 테이블 ID(tbl_…) |
started_at · settled_at | 시각 | 시작 · 정산(무효 처리) 시각 |
status | string | settled(정산) · void(무효 — 베팅을 돌려준 판) |
external_id | string | 플레이어 |
bet | int64 | 이 판에 낸 금액 (베팅 · 블라인드 · 앤티 · 콜 등 합) |
win | int64 | 이 판에서 받은 금액 (레이크를 뗀 뒤). 진 판은 0 |
rake | int64 | 이 플레이어 몫으로 뗀 레이크 · 수수료 |
currency | string | 테이블 통화 |
hand_no | int64 | 테이블 안 판 번호 |
table_name · table_names | string · object | 테이블 이름 · 언어별 이름 |
next_cursor | string | null | 다음 페이지 커서. 마지막 페이지면 null |
- 손익은
win − bet입니다. 바이인형 게임(홀덤 등)은 판마다 지갑을 부르지 않으므로 이 값은 칩 기준 손익이고, 지갑 거래는 테이블 세션 단위(buy_in·top_up·cash_out)로 따로 납니다. → GET /sessions/{session_id} - 같은 판의 플레이어 행은 회차 ID → 플레이어 순으로 이어집니다.
페이지 넘기기
next_cursor 를 같은 sort · order · 필터와 함께 cursor 로 넘깁니다. 커서는 불투명 문자열이므로 해석하거나 만들지 마세요. 한 페이지 행 수가 limit 보다 적으면 next_cursor 는 null 입니다(행 수가 정확히 limit 이면 다음 호출이 빈 목록일 수 있습니다).
sort=settled_at 으로 정렬하면 정산 시각이 없는 행은 가장 늦은 것으로 취급됩니다.
오류
| HTTP | 코드 | 상황 |
|---|---|---|
| 500 | INTERNAL_ERROR | 일시적인 서버 오류 — 잠시 뒤 다시 시도 |
인증 · 서명 · 속도 제한 오류는 모든 엔드포인트에 공통입니다. → 오류 코드
GET /operator/v1/rounds/{round_id}
라운드 하나의 상세입니다. external_id 를 주는지에 따라 두 가지 보기가 있습니다.
| 보기 | 요청 | 용도 | 담기는 것 |
|---|---|---|---|
| 플레이어 시점 | ?external_id=user_1001 | 사이트가 회원 본인에게 보여 주는 베팅 상세 | 그 회원의 홀카드(cards.player), 베팅 요약(bets), 그 회원 몫 레이크, 그 회원 지갑 거래 |
| 회차 전체 | external_id 없음 | 오퍼레이터 관리자 조회 · 분쟁 확인 | 회차 머니(money), 진행 기록(history), 카드 흐름(hands), 독립운영 자기 테이블이면 딜러 카드 입력(card_log) |
두 보기 모두 회차 머니(money)를 싣습니다.
경로 · 쿼리 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
round_id | string (경로) | 예 | 라운드 ID(rnd_…) |
external_id | string (쿼리) | 아니오 | 플레이어 시점으로 볼 플레이어. 그 플레이어가 이 판에 없으면 ROUND_NOT_FOUND |
보이는 범위
- 이 오퍼레이터 플레이어가 한 명도 참여하지 않은 판은
404 ROUND_NOT_FOUND입니다. 진행 중인 판도 아직 참가자 정산 기록이 없으므로 같은 오류가 납니다.- 예외: 독립운영 오퍼레이터의 자기 전용 테이블 판은 참가자가 없어도 회차 전체 보기(
external_id없이)로 열립니다(딜러 카드 입력 확인용). 이때money는 빈 모양입니다.
- 예외: 독립운영 오퍼레이터의 자기 전용 테이블 판은 참가자가 없어도 회차 전체 보기(
- 공용 테이블에는 다른 오퍼레이터 플레이어가 함께 앉습니다. 그 플레이어는 좌석과 금액만 보이고 신원(
external_id· 닉네임)과 공개되지 않은 홀카드는 보이지 않습니다. - 지갑 거래 · 베팅 항목은 이 오퍼레이터 플레이어 것만 담깁니다.
플레이어 시점 응답을 회원에게 그대로 전달하지 마세요
플레이어 시점(external_id) 응답에도 money 가 회차 전체 모양으로 실리며, 같은 판에 앉은 다른 회원의 external_id · 닉네임 · 금액과 그 회원들의 지갑 거래(money.transactions)가 들어 있습니다. 회원 화면에는 cards · bets · rake · transactions 처럼 본인 몫만 골라 보여 주세요.
요청 예시
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…응답 — 최상위 필드
| 필드 | 타입 | 설명 |
|---|---|---|
round_id · game_code · table_id · hand_no | 라운드 식별 정보 | |
status | string | settled · void (독립운영 자기 테이블 판은 진행 중이면 open 도 나옵니다) |
started_at · settled_at | 시각 | 시작 · 정산 시각 |
currency | string | 테이블 통화 |
result | object | null | 게임 엔진의 판정 결과 원본. 게임마다 모양이 다릅니다(아래 result) |
video_url | null | 예약 필드. 현재 항상 null |
money | object | 회차 머니 — 누가 얼마를 내고 받았는지 (money) |
cards | object | { "player": [...] } — 플레이어 시점일 때 그 회원의 최종 카드, 아니면 null |
bets | array | 플레이어 시점일 때 [{ "spot": "hand", "amount": 낸 금액, "win": 받은 금액 }] 한 줄 요약, 아니면 [] |
rake | int64 | 플레이어 시점이면 그 회원 몫, 아니면 판 전체 레이크(money.totals.rake) |
transactions | array | 플레이어 시점이면 그 회원의 지갑 거래, 아니면 money.transactions 와 같음 |
history | array | 회차 전체 보기만. 진행 기록 (history) |
hands | array | 회차 전체 보기만. 이 오퍼레이터 플레이어별 카드 흐름 (hands) |
card_log | array | 회차 전체 보기 + 독립운영 자기 테이블 판만. 딜러 카드 입력 기록 (GET /dealer-card-log 의 entries 와 같은 모양) |
플레이어 시점의 bets 는 게임과 관계없이 한 줄 요약입니다. 바카라 스팟별 · 블랙잭 박스별 베팅은 money.bets 에 있습니다.
money — 회차 머니
| 필드 | 타입 | 설명 |
|---|---|---|
kind | string | p2p(플레이어끼리 — 홀덤 · 오마하 · 라이브 홀덤 · 섯다 · 바둑이 · 맞고 · 고스톱 · 통잇) · banked(하우스 상대 — 스피드 홀덤 · 블랙잭 · 바카라 · 레드독) |
currency | string | 통화 |
players[] | array | 참가자별 합계 (아래 표) |
pots[] | array | 팟별 머니 이동. 홀덤 계열 · 섯다 · 바둑이만, 기록이 있는 판만 (없으면 필드 생략) |
returns[] | array | 콜 받지 못해 돌려준 초과 베팅 [{seat, external_id, amount}] (없으면 생략) |
rake_rule | object | 그 판에 적용된 레이크 규칙 (홀덤 계열, 없으면 생략) |
rake_on_profit | bool | true 면 딜러비를 팟이 아니라 승자 순이익에서 뗍니다(섯다 · 바둑이). 이때 팟 shares 는 딜러비를 떼기 전 금액이고 실제 딜러비는 좌석별 rake 입니다 |
bets[] | array | 이 오퍼레이터 플레이어의 베팅 항목 (뱅크형) |
transactions[] | array | 이 회차에 걸린 이 오퍼레이터 플레이어의 지갑 거래 (뱅크형. 바이인형 게임은 지갑 거래가 테이블 세션에 걸리므로 보통 빈 배열) |
totals | object | 합계 |
players[]
| 필드 | 타입 | 설명 |
|---|---|---|
external_id | string | null | 이 오퍼레이터 플레이어면 값, 다른 오퍼레이터 플레이어면 null |
nickname | string | 이 오퍼레이터 플레이어만 (없으면 생략) |
seat | int | 좌석 번호. 좌석이 없는 게임(바카라 등)은 -1 |
debit | int64 | 이 판에 낸 금액 |
credit | int64 | 이 판에 받은 금액 (레이크 뒤) |
rake | int64 | 이 사람 몫 레이크 · 수수료 |
net | int64 | credit − debit |
stack_before · stack_after | int64 | 판 전 · 뒤 좌석 칩 (좌석 칩으로 치는 게임만 — P2P, 레드독) |
mine | bool | 이 오퍼레이터 플레이어인지 |
bets[] — { external_id, spot, amount, win, status }. spot 은 바카라 player · banker · tie · player_pair · banker_pair · either_pair · perfect_pair · player_bonus · banker_bonus, 스피드 홀덤 · 블랙잭 box_N(블랙잭 추가 결정은 box_N:extra) 처럼 게임마다 다릅니다. win 은 받은 금액(원금 포함), status 는 settled · rolled_back.
transactions[] — { tx_id, external_id, type, reason, amount, status, balance_after, created_at, completed_at, ref_tx_id }. 각 필드 뜻은 GET /transactions 와 같습니다.
totals — { debit, credit, rake, house, balanced }
| 필드 | 설명 |
|---|---|
debit · credit · rake | 참가자 전원(다른 오퍼레이터 포함)의 합 |
house | 하우스 쪽 손익 = debit − credit. P2P 는 레이크와 같습니다 |
balanced | P2P 는 debit = credit + rake 이면 true(칩이 새지 않음). 뱅크형은 항상 true |
pots[] (홀덤 계열 예)
| 필드 | 설명 |
|---|---|
index | 0 = 메인 팟, 1~ = 사이드 팟 |
amount | 팟 총액 |
rake · rake_full · rake_cum | 실제로 뗀 레이크 · 비율대로라면 뗐을 레이크(상한 전) · 메인 팟부터 이 팟까지 누적 |
capped | 판당 최고 수수료(상한)에 닿아 덜 뗐는지 |
floored | 소수점 버림으로 덜 뗀 금액 (있을 때만) |
eligible | 이 팟을 받을 자격이 있는 좌석 |
contributions[] · shares[] | 넣은 · 받은 좌석과 금액 {seat, external_id, amount} (다른 오퍼레이터 플레이어는 external_id: null) |
rank | 이긴 족보 (있을 때만) |
balanced | 넣은 합 = 팟 총액 = 받은 합 + 레이크 |
rake_rule — { percent, cap, no_flop_no_drop, floor?, skip? }. percent 는 bp(500 = 5%), cap 은 판당 최고 수수료(0 = 없음), skip 은 레이크를 떼지 않은 이유(off · no_flop).
history — 진행 기록
판이 어떻게 진행됐는지를 시각순으로 펼친 목록입니다. 항목마다 ts 와 kind 가 있고, 나머지는 kind 에 따라 채워집니다(값이 없는 필드는 생략).
kind | 뜻 | 주로 쓰는 필드 |
|---|---|---|
start | 판 시작 | no(판 번호) · seats[]({seat, player, chips}) · button · sb · bb · amount(앤티) |
deal | 홀카드 배분 | seat · player · cards |
card | 카드 한 장(라이브 딜러 입력 · 자동) | position · cards · stage · source(scanner · wedge · manual · virtual · auto) · dealer · reason(거절 코드) |
street | 다음 스트리트 | stage · board |
betting | 베팅 열림 · 마감 | action(open · closed) · stage · amount |
action | 플레이어 결정 | seat · player · action(fold · check · call · bet · raise · allin · hit · stand · go · stop …) · amount · total · pot · stack · auto(시간 초과 자동 결정) |
bet · bet_undo | 뱅크형 베팅 · 베팅 취소 | player · amount · items[]({spot, amount, win, status}) |
showdown | 패 공개 | seat · player · cards · rank · best |
result | 판정 · 분배 | pots[]({index, amount, rake, rank, winners[]}) · rake · board · outcome |
tx | 지갑 거래 | player · action(debit · credit · rollback) · reason · amount · status |
void | 무효 | reason |
settle | 마지막 정산 (항상 마지막 줄) | rows[]({seat, player, invested, won, rake, net, stack_after}) · amount · won · rake |
event | 그 밖의 이벤트 | type · data |
player는{ "external_id", "nickname" }이고, 다른 오퍼레이터 플레이어는{ "other": true }입니다.cards의 원소가null이면 가린 카드입니다(다른 오퍼레이터 플레이어의 공개되지 않은 홀카드).- 딜러 이름(
dealer)은 독립운영의 자기 테이블 판에서만 채워집니다. - 진행 기록은 화면 표시용입니다.
kind와 필드는 늘어날 수 있으니 모르는 값은 건너뛰도록 구현하세요. 돈 계산은money를 기준으로 합니다.
hands — 카드 흐름
이 오퍼레이터 플레이어마다 처음 받은 카드 → 판 중 바꾼 · 버린 카드 → 판이 끝난 뒤 카드를 한 줄로 정리합니다. 좌석 순으로 정렬되며, 다른 오퍼레이터 플레이어는 빠집니다.
| 필드 | 설명 |
|---|---|
seat · player | 좌석 · {external_id, nickname} |
dealt | 처음 받은 카드 (홀덤 계열 홀카드, 바둑이 4장, 섯다 2장, 통잇 · 맞고 첫 손패) |
steps[] | 판 중 손패 변화 {stage?, out, in} — out 버린(낸) 카드, in 받은 카드. 바둑이는 stage 가 교체 차례(draw_1 아침 · draw_2 점심 · draw_3 저녁)이고 바꾸지 않은 차례도 빈 배열로 남습니다 |
held | 판이 끝날 때 남은 손패 (final 과 다를 때만 — 맞고 · 고스톱) |
final | 판이 끝난 뒤 카드 (맞고 · 고스톱은 먹은 패) |
board | 공용 카드 (홀덤 계열) |
best · rank | 패를 공개했을 때의 베스트 카드 · 족보 |
카드 표기는 트럼프 카드 2글자(AS · TD · 9H), 화투는 월 2자리 + 글자(01a · 05b)입니다.
예시 1 — 텍사스 홀덤 (P2P, 회차 전체 보기)
{
"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" }
]
}같은 판을 ?external_id=user_1001 로 조회하면 history · hands 가 빠지고 다음 값이 바뀝니다.
{
"cards": { "player": ["AS", "KD"] },
"bets": [ { "spot": "hand", "amount": 800, "win": 0 } ],
"rake": 40,
"transactions": []
}예시 2 — 라이브 바카라 (뱅크형)
뱅크형은 판마다 지갑이 움직이므로 money.bets 에 스팟별 베팅이, money.transactions 에 그 판의 debit · credit 이 담깁니다. pots 는 없습니다.
{
"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 }
}
}(위 예시는 일부 필드를 줄였습니다.)
예시 3 — 게임별 hands
바둑이 — 세 번의 교체(steps)가 차례 이름과 함께 남습니다.
{ "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" }맞고 · 고스톱 — steps 는 낸 패, final 은 먹은 패, held 는 판이 끝날 때 손에 남은 패입니다.
{ "seat": 0, "player": { "external_id": "user_1001", "nickname": "lucky7" },
"dealt": ["01a", "05b", "07c"],
"steps": [ { "out": ["01a"], "in": [] } ],
"held": ["05b", "07c"],
"final": ["01a", "01b"] }result — 게임 판정 원본
result 는 게임 엔진이 판을 끝내며 남긴 판정 원본이라 게임마다 모양이 다르고 예고 없이 필드가 늘 수 있습니다. 화면에 판정을 보여 줄 때 참고하되, 금액 계산 · 대사에는 money 를 쓰세요.
| 게임 | 주요 필드 |
|---|---|
홀덤 계열(holdem_nlh · holdem_plo4 · live_holdem) | board · awards[]({pot_index, seats, amount, shares, rake, rank, best5}) · rake · showdown · pots · returns · rake_rule |
바카라(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}) |
| 무효 판(모든 게임) | { "void_reason": "…" } |
오류
| HTTP | 코드 | 상황 |
|---|---|---|
| 404 | ROUND_NOT_FOUND | 형식이 틀린 ID, 없는 라운드, 이 오퍼레이터 플레이어가 없는 판, 진행 중인 판, external_id 플레이어가 이 판에 없음 |
| 500 | INTERNAL_ERROR | 일시적인 서버 오류 |
GET /operator/v1/sessions/{session_id}
바이인형 게임(홀덤 · 오마하 · 라이브 홀덤 · 섯다 · 맞고 · 통잇 · 레드독 등)의 테이블 세션(한 번 앉아서 일어날 때까지) 상세입니다. 이 게임들은 판마다 지갑을 부르지 않고 착석(buy_in) · 충전(top_up) · 이석(cash_out) 때만 지갑 거래를 만들며, 그 거래의 round_id 가 테이블 세션 ID(tss_…)입니다. 이 API 는 세션 동안의 판별 손익을 모아 바이인 · 캐시아웃과 판별 손익이 맞는지를 보여 줍니다.
session_id 는 GET /transactions 의 round_id(값이 tss_ 로 시작), 또는 지갑 콜백 본문의 round_id 에서 얻습니다.
경로 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
session_id | string | 예 | 테이블 세션 ID(tss_…) |
요청 예시
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…'응답 예시
{
"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 }
}응답 필드
| 필드 | 타입 | 설명 |
|---|---|---|
session_id · external_id · nickname | 세션 · 플레이어 | |
table_id · table_name · table_names · game_code · currency | 테이블 정보 | |
status | string | opening(바이인 처리 중) · seated(앉아 있음) · kept(라이브 홀덤 — 자리를 비우고 칩을 맡겨 둔 상태) · closing(이석 처리 중) · closed(끝남) |
opened_at · closed_at | 시각 | 착석 · 이석 시각 (closed_at 은 끝나기 전 null) |
buy_in_total | int64 | 요청한 바이인 · 충전 합계. 지갑 차감이 실패한 요청 금액도 포함하므로 대사에는 reconcile.paid_in 을 쓰세요 |
cash_out | int64 | null | 캐시아웃 금액 (끝나기 전 null) |
chips | int64 | 지금 좌석 칩 (끝난 세션은 보통 0) |
rounds[] | array | 세션 동안 참여한 판(시작 순, 최대 5,000판): round_id · hand_no · status · started_at · settled_at · seat · debit · credit · rake · net · stack_after |
transactions[] | array | 이 세션의 지갑 거래 (생성 순) |
totals | object | rounds(판 수) · debit · credit · rake · net 합 |
reconcile | object | 세션 대사 결과 (아래) |
reconcile
| 필드 | 설명 |
|---|---|
paid_in | 실제로 지갑에서 들어온 돈 = 성공한 debit(바이인 · 충전) − 그 debit 의 성공한 rollback |
paid_out | 지갑으로 나간 돈 = 캐시아웃 credit 합 (실패가 아닌 것) |
unsettled | 아직 끝나지 않은(pending · uncertain) credit · rollback 수 |
expected_end | paid_in + Σcredit − Σdebit — 판별 손익으로 계산한 끝 금액 |
actual_end | 끝난 세션은 paid_out, 열린 세션은 지금 chips |
balanced | expected_end = actual_end 이고 unsettled = 0 이면 true |
closed | 캐시아웃까지 끝났는지 |
balanced: false 이면서 unsettled > 0 이면 캐시아웃 credit 이 아직 재시도 중이라는 뜻입니다. 잠시 뒤 다시 조회하세요.
오류
| HTTP | 코드 | 상황 |
|---|---|---|
| 404 | SESSION_NOT_FOUND | 형식이 틀린 ID, 없는 세션, 다른 오퍼레이터의 세션 |
| 500 | INTERNAL_ERROR | 일시적인 서버 오류 |
GET /operator/v1/transactions
플랫폼이 이 오퍼레이터 지갑으로 보낸 debit · credit · rollback 거래 원장입니다(balance 조회는 기록하지 않습니다). 오퍼레이터 지갑 원장과 tx_id 로 맞춰 보는 대사에 씁니다.
쿼리 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
from · to | 시각 | 아니오 | 플랫폼이 거래를 만든 시각(created_at) 기준 [from, to). 기본 최근 24시간 |
status | ok | failed | pending | uncertain | 아니오 | 이 상태만 |
type | debit | credit | rollback | 아니오 | 이 종류만 |
external_id | string | 아니오 | 이 플레이어만 |
limit | 정수 | 아니오 | 기본 100, 최대 500 |
cursor | string | 아니오 | 직전 응답의 next_cursor |
요청 예시
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…응답 예시
{
"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"
}응답 필드
| 필드 | 타입 | 설명 |
|---|---|---|
tx_id | string | 거래 ID(tx_…). 지갑 콜백 본문의 tx_id 와 같습니다 |
type | string | debit · credit · rollback |
ref_tx_id | string | null | rollback 이 취소하는 debit 의 tx_id. 그 밖에는 null |
external_id | string | 플레이어 |
amount | int64 | 금액 (통화 최소 단위, 0 이상) |
currency | string | 통화 |
reason | string | 사유 (buy_in · top_up · cash_out · bet · ante · double · split · settle · rollback 사유 등). 값 목록은 debit · credit · rollback 참고 |
round_id | string | null | 지갑 요청에 보낸 값 그대로 — 베팅형은 라운드(rnd_…), 바이인형은 테이블 세션(tss_…) |
game_code · table_id | string | null | 지갑 요청에 보낸 값 그대로 |
status | string | 아래 표 |
operator_tx_id | string | null | 지갑 응답에 오퍼레이터가 돌려준 operator_tx_id |
balance_after | int64 | null | 지갑 응답의 balance (응답에 없었으면 null) |
created_at | 시각 | 플랫폼이 거래를 만든 시각 (첫 시도 직전) |
completed_at | 시각 | null | 끝난 시각 (ok · failed · uncertain 이 된 때). 진행 중이면 null |
next_cursor | string | null | 다음 페이지 커서. 마지막 페이지면 null |
status
| 값 | 뜻 | 오퍼레이터 지갑에 반영돼 있어야 하는 상태 |
|---|---|---|
ok | 지갑이 OK 로 응답했습니다 | 반영됨 |
failed | debit 이 거절됐습니다 — 지갑이 OK 가 아닌 status 를 돌려줬거나, 알방식에서 알이 모자라 지갑을 부르지 않고 실패 처리했습니다 | 반영 안 됨 (호출 자체가 없었을 수 있음) |
uncertain | debit 응답을 받지 못했습니다(시간 초과 · 연결 오류 · UNKNOWN_ERROR). 플랫폼은 곧바로 이 debit 을 ref_tx_id 로 하는 rollback 을 보냅니다 | debit 과 그 rollback 을 합쳐 순변화 0 |
pending | 처리 중이거나, credit · rollback 이 OK 를 받을 때까지 재시도 중입니다 | 아직 확정 아님 — 나중에 다시 확인 |
- credit · rollback 은
OK를 받을 때까지 계속 재시도하므로 최종 상태는ok입니다. 오래pending으로 남는 credit · rollback 이 있으면 지갑 쪽 응답을 점검하세요. → 지갑 콜백 개요 - 너무 오래
pending에 머문 debit 은 플랫폼이 rollback 을 보내 정리합니다.
정렬과 페이지 넘기기
- 결과는
tx_id내림차순입니다. 새 거래 ID 는 시간순으로 커지지만, 정산 credit · rollback 처럼 다른 거래에서 결정적으로 만든 ID 는 시간순이 아니므로 엄밀한 최신순이 아닙니다. 시각 순서가 필요하면 받은 뒤created_at으로 정렬하세요. next_cursor는 그 페이지 마지막 행의tx_id입니다. 같은 필터 · 같은from·to로cursor에 넘기면 그보다 작은tx_id부터 이어집니다. 구간이 고정돼 있으면 페이지 사이 누락 · 중복이 없습니다.- 행 수가
limit보다 적으면next_cursor는null입니다(정확히limit이면 다음 호출이 빈 목록일 수 있습니다). to를 생략하면 "지금"이 호출마다 바뀝니다. 끝까지 받을 때는from·to를 고정하세요.
끝까지 받기 — 예시 코드
import { createHmac, randomBytes } from 'node:crypto';
const ORIGIN = '{API_BASE}'; // 환경별 주소 (온보딩 때 안내)
const OPERATOR = process.env.OPERATOR_CODE!; // 예: acme
const API_KEY = process.env.OPERATOR_API_KEY!;
const API_SECRET = process.env.OPERATOR_API_SECRET!;
// 서명한 GET — path 는 쿼리까지 인코딩된 그대로 서명하고 그대로 보낸다
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 은 본문 없음(빈 문자열)
.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' }); // ':' → %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;
}
// 예: 어제 하루(UTC). to 는 현재보다 충분히 과거로 고정한다
const rows = await fetchAllTransactions('2026-10-18T00:00:00Z', '2026-10-19T00:00:00Z');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 는 쿼리까지 인코딩된 문자열 — 서명한 그대로 보낸다."""
ts = str(int(time.time() * 1000))
nonce = secrets.token_hex(12)
msg = f"{ts}\n{nonce}\nGET\n{path}\n".encode() # GET 은 본문 없음
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")호출 수는 오퍼레이터당 초당 200건으로 제한됩니다. 대량으로 받을 때는 limit=500 으로 호출 수를 줄이세요.
오류
| HTTP | 코드 | 상황 |
|---|---|---|
| 500 | INTERNAL_ERROR | 일시적인 서버 오류 |
형식이 틀린 cursor 는 오류 없이 무시되고 첫 페이지부터 돌려줍니다.
GET /operator/v1/reports/ggr
정산된 판을 날짜 × 게임별로 묶은 GGR(총 게임 수익) 집계입니다.
쿼리 파라미터
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
from · to | 시각 | 아니오 | 판의 정산 시각 기준 [from, to). 기본 최근 24시간 |
game_code | string | 아니오 | 이 게임만 |
external_id | string | 아니오 | 이 플레이어의 베팅 · 당첨 · 레이크만. 없는 플레이어면 빈 결과 { "currency": "", "rows": [] } |
이 밖의 파라미터(묶음 기준 · 시간대 지정 등)는 지원하지 않으며 보내도 무시됩니다.
요청 예시
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…응답 예시
{
"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 }
]
}응답 필드
| 필드 | 타입 | 설명 |
|---|---|---|
currency | string | 금액의 통화 (아래 주의 참고) |
rows[].day | string | 날짜 YYYY-MM-DD — 정산 시각을 UTC 날짜로 끊습니다 |
rows[].game_code | string | 게임 코드 |
rows[].rounds | int64 | 판 수 |
rows[].players | int64 | 참여한 플레이어 수 (그 날 · 그 게임 안에서 중복 제외) |
rows[].bet · win | int64 | 뱅크형 게임의 베팅 합 · 지급 합. P2P 게임은 0 |
rows[].rake | int64 | P2P 게임에서 이 오퍼레이터 플레이어 몫으로 귀속된 레이크. 뱅크형은 0 |
rows[].ggr | int64 | bet − win + rake |
- P2P 게임(홀덤 등)의 GGR 은 레이크입니다. 플레이어끼리 주고받은 칩은 GGR 에 들어가지 않습니다.
- 뱅크형 게임(바카라 등)의 GGR 은
bet − win입니다. - 행은 날짜 → 게임 코드 순입니다. 무효 판은 GGR 금액에 영향이 없지만, 게임에 따라
rounds·players수에는 포함될 수 있습니다.
통화
모든 금액은 통화 최소 단위 정수입니다(예: PHP 는 센타보, 1250000 = ₱12,500.00). 자릿수는 GET /currencies 의 exponent 로 확인합니다.
응답의 currency 는 하나뿐이고 rows 에는 통화 칸이 없습니다. 내부적으로는 통화별로 따로 집계하므로, 두 가지 이상 통화의 테이블을 쓰는 오퍼레이터는 같은 날짜 · 게임 행이 통화별로 여러 번 나오지만 구분할 수 없습니다. 여러 통화를 쓴다면 통화별 수치는 GET /rounds(행마다 currency)로 직접 집계하세요.
오류
| HTTP | 코드 | 상황 |
|---|---|---|
| 500 | INTERNAL_ERROR | 일시적인 서버 오류 |
대사(reconciliation) 방법
플레이어 잔액의 원본은 오퍼레이터 지갑이므로, 플랫폼 원장(GET /transactions)과 오퍼레이터 지갑 원장이 거래 단위로 일치하는지 정기적으로 확인하는 것을 권장합니다. 두 원장의 공통 키는 tx_id 입니다.
준비 — 오퍼레이터 지갑 원장에 남길 것
지갑 콜백(debit · credit · rollback)을 처리할 때 최소한 다음을 저장하세요.
| 항목 | 이유 |
|---|---|
tx_id (고유 키) | 대사 키이자 멱등 처리 키 |
type · amount · currency · external_id | 금액 · 대상 비교 |
ref_tx_id (rollback) | 어떤 debit 을 취소했는지 |
round_id · reason | 원인 추적 (tss_… 면 GET /sessions 로 이어 볼 수 있음) |
| 처리 결과 (반영 · 거절 · 무시) | debit 을 거절했는지, rollback 대상이 없어 아무것도 안 했는지 |
응답한 operator_tx_id | 플랫폼 원장의 operator_tx_id 와 교차 확인 |
절차
- 구간을 정합니다. 예: 어제 하루
[2026-10-18T00:00:00Z, 2026-10-19T00:00:00Z).to는 최소 몇 분 이상 과거여야 진행 중인 거래가 끝난 상태로 나옵니다. 고정한from·to로 받으세요. - 플랫폼 원장을 끝까지 받습니다. 위 예시 코드처럼
next_cursor가null이 될 때까지 반복합니다. - 같은 구간의 오퍼레이터 원장을 준비합니다. 플랫폼의
created_at은 플랫폼이 거래를 만든 시각이고, 오퍼레이터가 요청을 받은 시각은 그보다 조금 늦거나(재시도 시) 많이 늦을 수 있습니다. 경계 누락을 막으려면 오퍼레이터 쪽은 구간 앞뒤로 여유(예: ±1시간)를 두고 뽑은 뒤, 플랫폼 행에 없는 것만 따로 확인합니다. tx_id로 맞춥니다. 플랫폼 행마다 아래 규칙으로 판정합니다.
플랫폼 type / status | 오퍼레이터 원장의 정상 상태 | 어긋나면 |
|---|---|---|
debit / ok | 같은 tx_id 로 같은 금액이 차감됨 | 차감 기록이 없거나 금액이 다르면 이상 — 플랫폼은 칩 · 베팅을 내줬습니다 |
debit / failed | 차감 안 됨 (거절 기록만 있거나, 알 부족으로 호출 자체가 없어 기록이 없음) | 차감돼 있으면 플레이어 돈이 빠졌는데 게임에는 반영되지 않은 것 — 환불 필요 |
debit / uncertain | 차감됐든 안 됐든, 같은 debit 을 ref_tx_id 로 하는 rollback 까지 합쳐 순변화 0 | 차감은 됐는데 rollback 이 반영되지 않았으면 이상 |
rollback / ok | 원 debit 을 차감했었다면 되돌림, 원 debit 을 받은 적이 없으면 아무것도 하지 않고 OK | 원 debit 이 차감된 채 남아 있으면 이상 |
credit / ok | 같은 tx_id 로 같은 금액이 입금됨 (금액 0 인 settle 도 기록) | 입금 기록이 없거나 금액이 다르면 이상 |
모든 종류 / pending | 아직 확정 아님 | 다음 대사 때 다시 확인. credit · rollback 은 OK 를 받을 때까지 재시도되므로 지갑 응답 상태를 점검 |
- 반대 방향도 확인합니다. 오퍼레이터 원장에는 있는데 플랫폼 원장(넓은 구간 포함)에 없는
tx_id는 다른 환경 · 다른 오퍼레이터 코드로 들어온 요청이거나 서명 검증을 거치지 않은 요청일 수 있으니 조사하세요. - 합계를 검산합니다(선택). 플레이어별로
Σ(ok debit) − Σ(ok rollback 중 원 debit 이 ok 였던 것) − Σ(ok credit)이 오퍼레이터 쪽 같은 계산과 같아야 합니다.
바이인형 게임 추가 확인
홀덤 등 바이인형 게임은 판별 지갑 거래가 없으므로, 플레이어가 "칩이 사라졌다"고 문의하면 테이블 세션 단위로 확인합니다.
- 그 플레이어의
buy_in·top_up·cash_out거래에서round_id(tss_…)를 얻습니다. - GET /sessions/{session_id} 의
reconcile.balanced를 봅니다.true면 바이인 + 판별 손익 = 캐시아웃이 맞습니다. - 판별 내역은
rounds[]의 각round_id로 라운드 상세를 조회합니다.
협의 사항
대사 주기, 불일치 발견 시 조정 절차 · 연락 창구는 계약 시 협의합니다.