Skip to content

딜러 ​

라이브 테이블을 직접 운영하는 오퍼레이터가 소속 딜러 계정 · 사원증 · 얼굴 사진을 관리하고, 딜러툴 단말 현황과 설치 파일 주소, 딜러 카드 입력 기록을 조회하는 API 입니다. 라이브 테이블을 운영하지 않는 오퍼레이터는 쓰지 않아도 됩니다.

메서드경로용도
GET/operator/v1/dealers소속 딜러 목록
POST/operator/v1/dealers딜러 계정 만들기
PATCH/operator/v1/dealers/{dealer_id}이름 · 역할 · PIN · 사용 여부 변경
POST/operator/v1/dealers/{dealer_id}/badge사원증 발급 · 재발급
DELETE/operator/v1/dealers/{dealer_id}/badge사원증 회수
PUT/operator/v1/dealers/{dealer_id}/photo얼굴 사진 올리기
DELETE/operator/v1/dealers/{dealer_id}/photo얼굴 사진 지우기
GET/operator/v1/dealer-devices딜러툴 단말 현황
GET/operator/v1/dealer-tool딜러툴 최신 버전 · 설치 파일 주소
GET/operator/v1/dealer-card-log딜러 카드 입력 기록 (독립운영만)
  • 딜러 계정은 이 오퍼레이터 소속으로만 만들고 고칩니다. 소속 딜러는 이 오퍼레이터의 전용 라이브 테이블만 다룹니다.
  • 다른 오퍼레이터 소속 딜러를 가리키면 존재하지 않는 딜러와 똑같이 404 DEALER_NOT_FOUND 입니다.
  • dealer_id 는 접두어 없는 UUID 문자열입니다(예: 0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6).
  • 단말 승인 · 차단 · 테이블 묶기 · 허용 단말 수는 본사가 정합니다. 이 API 로는 단말을 조회만 합니다.
  • 쓰기 요청의 응답에는 X-Audit-Id 헤더가 실립니다. 계정 변경 · 사진 변경은 변경 이력에서 되돌릴 수 있습니다.

GET /operator/v1/dealers ​

소속 딜러 목록입니다. 만든 순서로 정렬됩니다.

응답 예시 ​

json
{
  "dealers": [
    {
      "dealer_id": "0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6",
      "login": "maria01",
      "name": "Maria Santos",
      "role": "dealer",
      "status": "active",
      "rounds": 1842,
      "created_at": "2026-09-02T03:12:44.51Z",
      "photo_url": "{API_BASE}/dealer/v1/photos/0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6?v=mgx3k2a1",
      "badge_tail": "7QK2",
      "badge_issued_at": "2026-09-02T03:15:02.1Z",
      "badge_code": "DLR-8M3TZ0RW7QK2"
    }
  ]
}

응답 필드 ​

필드타입설명
dealer_idstring딜러 ID (UUID)
loginstring딜러툴 로그인 아이디
namestring표시 이름
rolestringdealer(딜러) · pitboss(피트보스 — 딜러툴 화면에서 카드를 직접 골라 넣는 수동 입력 등 관리 권한)
statusstringactive · disabled(사용 중지 — 딜러툴에 로그인할 수 없음)
roundsint64이 딜러가 진행한 판 수
created_at시각계정 생성 시각
photo_urlstring얼굴 사진 공개 주소. 없으면 ""
badge_tailstring | null사원증 코드 끝 4자리 (발급 전 · 회수 뒤 null)
badge_issued_at시각 | null사원증 발급 시각
badge_codestring사원증 코드 전체 (DLR- + 12자). 발급되지 않았으면 ""

사원증 코드 취급

badge_code 는 딜러툴에서 근무를 시작할 때 스캔하는 값입니다. 목록 응답에 전체 코드가 실리므로 관리자 권한이 있는 화면에서만 보여 주세요. 분실하면 회수하거나 재발급하세요.

photo_url 의 사진은 인증 없이 받을 수 있는 공개 주소이며, 사진을 바꾸면 v= 값이 바뀝니다.

POST /operator/v1/dealers ​

딜러 계정을 만듭니다.

요청 필드 ​

필드타입필수설명
loginstring예소문자 · 숫자로 시작하고 소문자 · 숫자 · _ · - 로 된 2 ~ 32자 (^[a-z0-9][a-z0-9_-]{1,31}$). 대문자는 소문자로, 앞뒤 공백은 지워서 저장합니다. 모든 오퍼레이터가 함께 쓰는 공간이라 다른 오퍼레이터가 쓰는 아이디는 쓸 수 없습니다
namestring예1 ~ 60바이트(UTF-8). 앞뒤 공백은 지웁니다
pinstring예딜러툴 로그인 PIN, 4 ~ 32자
rolestring아니오dealer(기본) · pitboss

요청 예시 ​

bash
curl -X POST {API_BASE}/operator/v1/dealers \
  -H 'Content-Type: application/json; charset=utf-8' \
  -H 'X-Operator: acme' -H 'X-Api-Key: ak_live_…' \
  -H 'X-Timestamp: 1760839200123' -H 'X-Nonce: 5d7f9a1c3e5b7d9f1a3c5e7b' -H 'X-Signature: 4c8a…' \
  -d '{"login":"maria01","name":"Maria Santos","pin":"482913","role":"dealer"}'

응답 예시 ​

201 Created

json
{ "dealer_id": "0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6" }

오류 ​

HTTP코드상황
400INVALID_REQUEST (message invalid json)본문이 JSON 이 아님
400INVALID_REQUEST (details.fields)login · name · pin · role 중 형식이 틀린 필드 (여러 개면 모두)
409DUPLICATE이미 있는 login
500INTERNAL_ERROR저장 실패

PATCH /operator/v1/dealers/{dealer_id} ​

이름 · 역할 · PIN · 사용 여부를 고칩니다. 보낸 필드만 바꿉니다.

요청 필드 ​

필드타입필수설명
namestring아니오1 ~ 60바이트
rolestring아니오dealer · pitboss
pinstring아니오새 PIN, 4 ~ 32자. "" 이면 바꾸지 않습니다
statusstring아니오active · disabled

사용 중지(disabled)하거나 역할을 바꾸면 그 딜러가 접속해 있던 딜러툴 연결이 끊기고 딜러툴은 로그인 화면으로 돌아갑니다. login 은 바꿀 수 없습니다.

요청 예시 ​

bash
curl -X PATCH {API_BASE}/operator/v1/dealers/0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6 \
  -H 'Content-Type: application/json; charset=utf-8' \
  -H 'X-Operator: acme' -H 'X-Api-Key: ak_live_…' \
  -H 'X-Timestamp: 1760839200123' -H 'X-Nonce: 1a3c5e7b9d1f3a5c7e9b1d3f' -H 'X-Signature: e09b…' \
  -d '{"status":"disabled"}'

응답 ​

204 No Content (본문 없음)

오류 ​

HTTP코드상황
404DEALER_NOT_FOUND형식이 틀린 ID, 없는 딜러, 다른 오퍼레이터 소속
400INVALID_REQUEST (message invalid json)본문이 JSON 이 아님
400INVALID_REQUEST (details.fields)name · role · status · pin 중 형식이 틀린 필드
500INTERNAL_ERROR저장 실패

POST /operator/v1/dealers/{dealer_id}/badge ​

사원증을 발급합니다. 이미 있으면 새 코드로 재발급하며 이전 코드는 즉시 쓸 수 없게 됩니다. 본문은 없습니다(빈 본문 또는 {} 로 서명).

응답 예시 ​

json
{ "code": "DLR-8M3TZ0RW7QK2", "tail": "7QK2", "login": "maria01", "name": "Maria Santos", "role": "dealer" }
필드설명
code사원증 코드 전체 — 바코드 · QR 로 인쇄해 딜러에게 지급합니다
tail코드 끝 4자리 (목록 badge_tail)
login · name · role사원증에 함께 인쇄할 딜러 정보

오류 ​

HTTP코드상황
404DEALER_NOT_FOUND없는 딜러 · 다른 오퍼레이터 소속
500INTERNAL_ERROR저장 실패

DELETE /operator/v1/dealers/{dealer_id}/badge ​

사원증을 회수합니다(분실 등). 다시 발급할 때까지 사원증으로 근무를 시작할 수 없습니다. 본문은 없습니다.

응답 ​

204 No Content

오류 ​

HTTP코드상황
404DEALER_NOT_FOUND없는 딜러 · 다른 오퍼레이터 소속
500INTERNAL_ERROR저장 실패

PUT /operator/v1/dealers/{dealer_id}/photo ​

딜러 얼굴 사진을 올립니다. 딜러툴 상단의 근무 딜러 카드와 관리 화면 목록에 쓰이며, 근무 중인 딜러툴에는 바로 반영됩니다.

요청 필드 ​

필드타입필수설명
imagestring예이미지 base64, 또는 data URL(data:image/jpeg;base64,…)

이미지 조건: JPEG 또는 PNG, 가로 · 세로 각각 2048px 이하. 정사각 이미지를 권장합니다.

실제 크기 한도는 요청 본문 64KB 입니다

Operator API 는 요청 본문을 최대 64KB(65,536바이트) 까지만 읽습니다. 사진은 base64 로 실리므로(원본의 약 4/3 배) 원본 이미지가 약 48KB 를 넘으면 본문이 잘려 서명 검증에 실패하고 401 INVALID_SIGNATURE 가 납니다. 올리기 전에 정사각으로 잘라 약 400×400px JPEG 등으로 줄여 48KB 이하로 맞추세요. (사진 자체의 검사 기준은 512KB 이지만, 본문 한도가 먼저 걸립니다.)

요청 예시 ​

bash
IMG=$(base64 -w0 maria.jpg)   # macOS: base64 -i maria.jpg
curl -X PUT {API_BASE}/operator/v1/dealers/0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6/photo \
  -H 'Content-Type: application/json; charset=utf-8' \
  -H 'X-Operator: acme' -H 'X-Api-Key: ak_live_…' \
  -H 'X-Timestamp: 1760839200123' -H 'X-Nonce: 9b1d3f5a7c9e1b3d5f7a9c1e' -H 'X-Signature: 77d1…' \
  -d "{\"image\":\"data:image/jpeg;base64,${IMG}\"}"

서명은 실제로 보내는 본문 바이트 그대로 계산합니다. → 인증 · 서명

응답 예시 ​

json
{ "photo_url": "{API_BASE}/dealer/v1/photos/0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6?v=mgx3q8z0" }

오류 ​

HTTP코드상황
401INVALID_SIGNATURE본문이 64KB 를 넘어 잘림 (위 경고) — 또는 일반 서명 오류
404DEALER_NOT_FOUND없는 딜러 · 다른 오퍼레이터 소속
400INVALID_REQUEST본문이 JSON 이 아님
400BAD_PHOTObase64 가 아님, 빈 이미지, JPEG · PNG 가 아님, 2048px 초과, 512KB 초과
500INTERNAL_ERROR저장 실패

DELETE /operator/v1/dealers/{dealer_id}/photo ​

얼굴 사진을 지웁니다. 딜러툴에는 기본 이미지가 보입니다. 본문은 없습니다.

응답 ​

204 No Content

오류 ​

HTTP코드상황
404DEALER_NOT_FOUND없는 딜러 · 다른 오퍼레이터 소속
500INTERNAL_ERROR삭제 실패

GET /operator/v1/dealer-devices ​

이 오퍼레이터에 묶인 딜러툴 단말(딜러 PC) 현황입니다. 원격 지원 접속 정보는 싣지 않습니다.

응답 예시 ​

json
{
  "active": 2,
  "limit": 4,
  "devices": [
    {
      "id": "0199e9c4-2f1a-7b3c-8d4e-5f6a7b8c9d0e",
      "name": "STUDIO-PC-01",
      "status": "active",
      "online": true,
      "logged_in": true,
      "dealer": "maria01",
      "on_duty": "Maria Santos",
      "operator": "acme",
      "operator_name": "Acme Casino",
      "app_version": "0.2.28",
      "os": "windows 11",
      "last_ip": "203.0.113.24",
      "last_seen_at": "2026-10-19T02:10:11.3Z",
      "created_at": "2026-09-01T08:00:00Z",
      "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
      "open_tables": ["tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d"],
      "setup_ready": true,
      "live": {
        "app_version": "0.2.28",
        "os": "windows 11",
        "scanner": "connected",
        "stream": { "state": "live", "latency_ms": 180, "bitrate_kbps": 2500, "width": 1920, "height": 1080, "fps": 30 },
        "cpu": 23.5,
        "mem": 41.2
      }
    }
  ]
}

응답 필드 ​

필드타입설명
activeint승인된(status: active) 단말 수
limitint | null본사가 정한 허용 단말 수. 제한이 없으면 null
devices[].idstring단말 ID (UUID)
devices[].namestring단말 이름
devices[].statusstringpending(승인 대기) · active(승인) · disabled(차단)
devices[].onlinebool지금 연결돼 있는지
devices[].logged_inbool딜러가 로그인해 있는지
devices[].dealer · on_dutystring근무 중인 딜러 아이디 · 이름 (로그인 중일 때만)
devices[].app_version · osstring딜러툴 버전 · 운영체제
devices[].last_ip · last_seen_at마지막 접속 IP · 시각
devices[].table_idstring묶인 테이블 (없으면 생략)
devices[].requested_table_idstring딜러툴이 묶어 달라고 요청한 테이블 (있을 때만)
devices[].open_tablesstring[]지금 딜러툴에서 열려 있는 테이블
devices[].setup_readybool연동 설정(게임 서버 · 영상 서버 · 테이블)이 모두 정해졌는지
devices[].liveobject실시간 상태(온라인일 때 마지막 보고) — scanner(connected · disconnected · virtual · none), stream(state: live · connecting · off · error, 지연 · 비트레이트 · 해상도 · fps), cpu · mem(%), update(자동 업데이트 진행 — 있을 때만)
devices[].last_statusobject오프라인일 때 참고할 마지막 상태 요약 (있을 때만)

이 밖에 operator · operator_name · game_server · media_server 같은 연동 설정 필드가 함께 실릴 수 있습니다. 값이 없는 필드는 생략됩니다.

오류 ​

HTTP코드상황
500INTERNAL_ERROR일시적인 서버 오류

GET /operator/v1/dealer-tool ​

딜러 PC 에 딜러툴을 설치할 최신 버전과 내려받기 주소입니다. 관리 화면에 링크를 보여 주고, 딜러 PC 브라우저에서 열어 설치하게 합니다. 설치한 딜러툴은 처음 실행할 때 운영사 코드(operator)를 입력하고, 본사 승인을 받은 뒤에 쓸 수 있습니다.

응답 예시 ​

json
{
  "version": "0.2.28",
  "notes": "영상 인코딩 개선",
  "pub_date": "2026-10-18T10:00:00Z",
  "files": [
    { "platform": "windows-x86_64", "alias": "windows", "file": "R-Live-Dealer_0.2.28_x64-setup.exe", "size": 9876543,
      "url": "{API_BASE}/dealer/v1/download/windows" }
  ],
  "operator": "acme"
}

응답 필드 ​

필드타입설명
versionstring게시된 최신 버전. 게시본이 없으면 ""
notes · pub_datestring버전 설명 · 게시 시각 (게시본이 없으면 생략)
files[]array플랫폼별 설치 파일. 게시본이 없으면 []
files[].platformstringwindows-x86_64 · darwin-aarch64(Apple 실리콘) · darwin-x86_64(Intel Mac)
files[].aliasstring짧은 이름 windows · mac · mac-intel
files[].file · size파일 이름 · 크기(바이트)
files[].urlstring고정 내려받기 주소 — 새 버전이 게시돼도 같은 주소가 최신 파일을 줍니다
operatorstring딜러툴 첫 화면에 넣을 운영사 코드 (= X-Operator)

내려받기 주소는 인증이 필요 없지만, 플랫폼이 허용한 스튜디오 네트워크에서만 열리도록 제한될 수 있습니다. 설치된 딜러툴은 새 버전이 게시되면 판이 끝난 뒤 스스로 업데이트합니다.

협의 사항

제공 플랫폼(Windows · macOS), 스튜디오 네트워크 허용 범위, 단말 승인 절차는 계약 시 협의합니다.

GET /operator/v1/dealer-card-log ​

자기 전용 테이블에 들어온 딜러 카드 입력(스캐너 · 수동 · 가상) 기록입니다. 딜러가 어떤 카드를 언제 어떤 방식으로 넣었고 서버가 받아들였는지 확인하는 감사용입니다.

독립운영 전용

독립운영(independent) 오퍼레이터만 쓸 수 있습니다. 알방식(prepaid) 오퍼레이터의 테이블은 본사가 운영하므로 403 CARD_LOG_FORBIDDEN 입니다. 공용 테이블 입력은 보이지 않습니다.

쿼리 파라미터 ​

파라미터타입필수설명
from · to시각아니오서버가 입력을 받은 시각(received_at) 기준 [from, to). 기본 최근 24시간. 형식은 공통 조회 파라미터와 같습니다
table_idstring아니오이 테이블만 (형식이 틀리면 빈 결과)
round_idstring아니오이 라운드만 (형식이 틀리면 빈 결과)
dealerstring아니오이 딜러 아이디(login)만
sourcestring아니오scanner · wedge · manual · virtual
resultstring아니오accepted · rejected · no_reply
groupround아니오round 이면 입력 대신 판별 묶음을 돌려줍니다
no_round1아니오판에 속하지 않은 입력만 (입력 목록에서만)
limit정수아니오기본 100, 최대 500
offset정수아니오건너뛸 행 수

응답 예시 — 입력 목록 ​

json
{
  "entries": [
    {
      "received_at": "2026-10-19T02:10:04.512Z",
      "id": "0199f3c1-0a1b-7c2d-8e3f-4a5b6c7d8e9f",
      "dealer": "maria01",
      "role": "dealer",
      "device_id": "0199e9c4-2f1a-7b3c-8d4e-5f6a7b8c9d0e",
      "device_name": "STUDIO-PC-01",
      "ip": "203.0.113.24",
      "source": "scanner",
      "card": "AS",
      "raw": "1S14",
      "scanner_id": "HID-0A3F",
      "scanned_at": "2026-10-19T02:10:04.43Z",
      "lag_ms": 82,
      "result": "accepted",
      "position": "seat_3",
      "reject_code": null,
      "reject_reason": null,
      "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
      "table_name": "Live Hold'em 1",
      "table_names": { "en": "Live Hold'em 1" },
      "game_code": "live_holdem",
      "round_id": "rnd_0199f3a2c4e57b1d9a2e4f6081c3d5e7",
      "round_no": 66,
      "round_status": "settled"
    }
  ],
  "has_more": false
}

입력 필드 ​

필드타입설명
received_at시각서버가 받은 시각 (UTC)
idstring입력 기록 ID
dealer · rolestring입력한 딜러 아이디 · 역할
device_id · device_namestring입력한 단말
ipstring | null단말 IP
sourcestringscanner · wedge(키보드 입력형 스캐너) · manual(피트보스 수동 입력) · virtual(가상 스캐너 — 테스트 환경)
cardstring | null카드 (AS 등)
rawstring | null스캐너가 읽은 원본 값
scanner_idstring | null스캐너 식별값
scanned_at시각 | null단말이 읽은 시각
lag_msint | null단말이 읽은 뒤 서버가 받기까지 걸린 시간(ms). 단말 시계 차이가 포함됩니다
resultstringaccepted(받아들임) · rejected(거절) · no_reply(응답 없음)
positionstring | null카드 자리 (예: seat_3)
reject_code · reject_reasonstring | null거절 코드 · 사유
hiddenbooltrue 면 진행 중인 판이라 card · raw 를 가렸습니다 (있을 때만)
table_id · table_name · table_names · game_code테이블
round_id · round_no · round_status라운드 (판에 속하지 않으면 null)

진행 중인 판(round_status: "open")의 입력은 카드 값(card · raw)을 null 로 가리고 hidden: true 를 붙입니다. 판이 끝나면 보입니다.

응답 예시 — 판별 묶음 (group=round) ​

json
{
  "rounds": [
    {
      "table_id": "tbl_0198c1d2e3f47a5b8c9d0e1f2a3b4c5d",
      "table_name": "Live Hold'em 1",
      "table_names": { "en": "Live Hold'em 1" },
      "game_code": "live_holdem",
      "round_id": "rnd_0199f3a2c4e57b1d9a2e4f6081c3d5e7",
      "round_no": 66,
      "round_status": "settled",
      "first_at": "2026-10-19T02:10:01.2Z",
      "last_at": "2026-10-19T02:11:01.9Z",
      "n": 19, "n_accepted": 19, "n_rejected": 0, "n_no_reply": 0,
      "n_scanned": 19, "n_manual": 0, "n_virtual": 0, "n_late": 0, "n_pitboss": 0,
      "max_lag_ms": 140,
      "dealers": ["maria01"],
      "devices": ["STUDIO-PC-01"]
    }
  ],
  "has_more": true
}
필드설명
first_at · last_at그 판의 첫 · 마지막 입력 시각
n · n_accepted · n_rejected · n_no_reply입력 수 · 결과별 수
n_scanned · n_manual · n_virtual입력 방식별 수 (n_scanned = scanner + wedge)
n_late스캔부터 서버 수신까지 5초를 넘긴 입력 수
n_pitboss피트보스가 넣은 입력 수
max_lag_ms가장 긴 지연(ms). 스캔 시각이 하나도 없으면 null
dealers · devices입력한 딜러 · 단말

판에 속하지 않은 입력(판 시작 전 거절 등)은 테이블별 한 행으로 묶이며 round_id 가 null 입니다. 판 행의 입력을 펼쳐 보려면 같은 조건에 round_id 를 붙여(판 없는 행은 table_id + no_round=1) 입력 목록을 다시 조회합니다. 묶음은 마지막 입력 최신순입니다.

라운드 하나의 입력은 라운드 상세의 card_log 로도 볼 수 있습니다.

페이지 넘기기 ​

offset 방식입니다. has_more: true 면 offset 을 limit 만큼 늘려 다시 조회합니다. 입력 목록은 received_at 최신순입니다.

오류 ​

HTTP코드상황
403CARD_LOG_FORBIDDEN알방식 오퍼레이터
500INTERNAL_ERROR일시적인 서버 오류

문서 v1.0.0 · Operator API v1