Dealers
These APIs let an operator that runs its own live tables manage its dealer accounts, dealer badges and face photos, and look up dealer tool device status, the installer download URL and the dealer card log. Operators that do not run live tables do not need them.
| Method | Path | Purpose |
|---|---|---|
| GET | /operator/v1/dealers | List your dealers |
| POST | /operator/v1/dealers | Create a dealer account |
| PATCH | /operator/v1/dealers/{dealer_id} | Change name, role, PIN or enabled status |
| POST | /operator/v1/dealers/{dealer_id}/badge | Issue or reissue a dealer badge |
| DELETE | /operator/v1/dealers/{dealer_id}/badge | Revoke a dealer badge |
| PUT | /operator/v1/dealers/{dealer_id}/photo | Upload a face photo |
| DELETE | /operator/v1/dealers/{dealer_id}/photo | Remove a face photo |
| GET | /operator/v1/dealer-devices | Dealer tool device status |
| GET | /operator/v1/dealer-tool | Latest dealer tool version and installer URLs |
| GET | /operator/v1/dealer-card-log | Dealer card log (independent mode only) |
- Dealer accounts are created and modified only under this operator. Your dealers handle only this operator's dedicated live tables.
- Referring to a dealer that belongs to another operator returns
404 DEALER_NOT_FOUND, exactly as for a dealer that does not exist. dealer_idis a UUID string without a prefix (e.g.0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6).- Device approval, blocking, table binding and the number of allowed devices are decided by the platform headquarters. This API only lets you view devices.
- Responses to write requests carry an
X-Audit-Idheader. Account changes and photo changes can be reverted from the audit log.
GET /operator/v1/dealers
Lists your dealers, sorted by creation order.
Response example
{
"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"
}
]
}Response fields
| Field | Type | Description |
|---|---|---|
dealer_id | string | Dealer ID (UUID) |
login | string | Dealer tool sign-in ID |
name | string | Display name |
role | string | dealer (dealer) or pitboss (pit boss — management privileges such as manual input, where cards are picked directly on the dealer tool screen) |
status | string | active or disabled (disabled — cannot sign in to the dealer tool) |
rounds | int64 | Number of rounds this dealer has run |
created_at | timestamp | Account creation time |
photo_url | string | Public URL of the face photo. "" if none |
badge_tail | string | null | Last 4 characters of the dealer badge code (null before issuance and after revocation) |
badge_issued_at | timestamp | null | Dealer badge issue time |
badge_code | string | Full dealer badge code (DLR- + 12 characters). "" if not issued |
Handling dealer badge codes
badge_code is the value scanned in the dealer tool to start a shift. Because the list response contains the full code, show it only on screens restricted to administrators. If a badge is lost, revoke or reissue it.
The photo at photo_url is a public URL that can be fetched without authentication; the v= value changes whenever the photo changes.
POST /operator/v1/dealers
Creates a dealer account.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
login | string | Yes | 2–32 characters, starting with a lowercase letter or digit and consisting of lowercase letters, digits, _ and - (^[a-z0-9][a-z0-9_-]{1,31}$). Uppercase letters are stored as lowercase, and leading/trailing whitespace is trimmed. This is a namespace shared by all operators, so an ID already used by another operator cannot be used |
name | string | Yes | 1–60 bytes (UTF-8). Leading/trailing whitespace is trimmed |
pin | string | Yes | Dealer tool sign-in PIN, 4–32 characters |
role | string | No | dealer (default) or pitboss |
Request example
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"}'Response example
201 Created
{ "dealer_id": "0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6" }Errors
| HTTP | Code | Situation |
|---|---|---|
| 400 | INVALID_REQUEST (message invalid json) | Body is not JSON |
| 400 | INVALID_REQUEST (details.fields) | Invalid format in login, name, pin or role (all invalid fields are listed) |
| 409 | DUPLICATE | login already exists |
| 500 | INTERNAL_ERROR | Failed to save |
PATCH /operator/v1/dealers/{dealer_id}
Changes the name, role, PIN or enabled status. Only the fields you send are changed.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | 1–60 bytes |
role | string | No | dealer or pitboss |
pin | string | No | New PIN, 4–32 characters. "" leaves it unchanged |
status | string | No | active or disabled |
Disabling a dealer (disabled) or changing their role disconnects any dealer tool session that dealer is signed in to, and the dealer tool returns to the sign-in screen. login cannot be changed.
Request example
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"}'Response
204 No Content (no body)
Errors
| HTTP | Code | Situation |
|---|---|---|
| 404 | DEALER_NOT_FOUND | Malformed ID, dealer does not exist, or dealer belongs to another operator |
| 400 | INVALID_REQUEST (message invalid json) | Body is not JSON |
| 400 | INVALID_REQUEST (details.fields) | Invalid format in name, role, status or pin |
| 500 | INTERNAL_ERROR | Failed to save |
POST /operator/v1/dealers/{dealer_id}/badge
Issues a dealer badge. If one already exists, it is reissued with a new code and the previous code stops working immediately. There is no body (sign an empty body or {}).
Response example
{ "code": "DLR-8M3TZ0RW7QK2", "tail": "7QK2", "login": "maria01", "name": "Maria Santos", "role": "dealer" }| Field | Description |
|---|---|
code | Full dealer badge code — print it as a barcode or QR code and give it to the dealer |
tail | Last 4 characters of the code (badge_tail in the list) |
login, name, role | Dealer details to print on the badge alongside the code |
Errors
| HTTP | Code | Situation |
|---|---|---|
| 404 | DEALER_NOT_FOUND | Dealer does not exist or belongs to another operator |
| 500 | INTERNAL_ERROR | Failed to save |
DELETE /operator/v1/dealers/{dealer_id}/badge
Revokes a dealer badge (e.g. when lost). Until a new badge is issued, the dealer cannot start a shift with a badge. There is no body.
Response
204 No Content
Errors
| HTTP | Code | Situation |
|---|---|---|
| 404 | DEALER_NOT_FOUND | Dealer does not exist or belongs to another operator |
| 500 | INTERNAL_ERROR | Failed to save |
PUT /operator/v1/dealers/{dealer_id}/photo
Uploads a dealer's face photo. It is used on the on-duty dealer card at the top of the dealer tool and in the list on the management screen, and is applied immediately to any dealer tool where the dealer is on duty.
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
image | string | Yes | Image as base64, or as a data URL (data:image/jpeg;base64,…) |
Image requirements: JPEG or PNG, no more than 2048px in width and in height. A square image is recommended.
The effective size limit is the 64KB request body
The Operator API reads at most 64KB (65,536 bytes) of a request body. Because the photo is sent as base64 (about 4/3 of the original size), an original image larger than about 48KB gets its body truncated, signature verification fails and you receive 401 INVALID_SIGNATURE. Before uploading, crop the image to a square and shrink it (for example to an approx. 400×400px JPEG) so it is 48KB or less. (The photo check itself allows up to 512KB, but the body limit applies first.)
Request example
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}\"}"Compute the signature over the exact body bytes you actually send. → Authentication and signing
Response example
{ "photo_url": "{API_BASE}/dealer/v1/photos/0199e8a1-6b2c-7d3e-9f40-a1b2c3d4e5f6?v=mgx3q8z0" }Errors
| HTTP | Code | Situation |
|---|---|---|
| 401 | INVALID_SIGNATURE | Body exceeded 64KB and was truncated (see the warning above) — or an ordinary signature error |
| 404 | DEALER_NOT_FOUND | Dealer does not exist or belongs to another operator |
| 400 | INVALID_REQUEST | Body is not JSON |
| 400 | BAD_PHOTO | Not base64, empty image, not JPEG or PNG, larger than 2048px, or larger than 512KB |
| 500 | INTERNAL_ERROR | Failed to save |
DELETE /operator/v1/dealers/{dealer_id}/photo
Removes the face photo. The dealer tool shows a default image instead. There is no body.
Response
204 No Content
Errors
| HTTP | Code | Situation |
|---|---|---|
| 404 | DEALER_NOT_FOUND | Dealer does not exist or belongs to another operator |
| 500 | INTERNAL_ERROR | Failed to delete |
GET /operator/v1/dealer-devices
Status of the dealer tool devices (dealer PCs) bound to this operator. Remote support connection details are not included.
Response example
{
"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
}
}
]
}Response fields
| Field | Type | Description |
|---|---|---|
active | int | Number of approved (status: active) devices |
limit | int | null | Number of allowed devices set by the platform headquarters. null if unlimited |
devices[].id | string | Device ID (UUID) |
devices[].name | string | Device name |
devices[].status | string | pending (awaiting approval), active (approved) or disabled (blocked) |
devices[].online | bool | Whether the device is currently connected |
devices[].logged_in | bool | Whether a dealer is signed in |
devices[].dealer, on_duty | string | Sign-in ID and name of the on-duty dealer (only while signed in) |
devices[].app_version, os | string | Dealer tool version and operating system |
devices[].last_ip, last_seen_at | Last connection IP and time | |
devices[].table_id | string | Bound table (omitted if none) |
devices[].requested_table_id | string | Table the dealer tool has requested to be bound to (only when present) |
devices[].open_tables | string[] | Tables currently open in the dealer tool |
devices[].setup_ready | bool | Whether all integration settings (game server, media server, table) are configured |
devices[].live | object | Real-time status (last report while online) — scanner (connected, disconnected, virtual, none), stream (state: live, connecting, off, error; plus latency, bitrate, resolution, fps), cpu and mem (%), update (automatic update progress — only when present) |
devices[].last_status | object | Summary of the last status for reference while offline (only when present) |
Integration settings fields such as operator, operator_name, game_server and media_server may also be included. Fields without a value are omitted.
Errors
| HTTP | Code | Situation |
|---|---|---|
| 500 | INTERNAL_ERROR | Temporary server error |
GET /operator/v1/dealer-tool
Returns the latest dealer tool version to install on dealer PCs and its download URLs. Show the link on your management screen and have it opened in the browser on the dealer PC to install. When the installed dealer tool is launched for the first time, enter the operator code (operator); the tool can be used after the platform headquarters approves it.
Response example
{
"version": "0.2.28",
"notes": "Improved video encoding",
"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"
}Response fields
| Field | Type | Description |
|---|---|---|
version | string | Latest published version. "" if nothing has been published |
notes, pub_date | string | Version notes and publish time (omitted if nothing has been published) |
files[] | array | Installer per platform. [] if nothing has been published |
files[].platform | string | windows-x86_64, darwin-aarch64 (Apple silicon) or darwin-x86_64 (Intel Mac) |
files[].alias | string | Short name: windows, mac or mac-intel |
files[].file, size | File name and size (bytes) | |
files[].url | string | Stable download URL — when a new version is published, the same URL serves the latest file |
operator | string | Operator code to enter on the dealer tool's first screen (= X-Operator) |
Download URLs require no authentication, but may be restricted so that they open only from studio networks allowed by the platform. The installed dealer tool updates itself after the current round ends when a new version is published.
To be agreed
Supported platforms (Windows, macOS), the allowed range of studio networks and the device approval procedure are agreed at contract time.
GET /operator/v1/dealer-card-log
The log of dealer card inputs (scanner, manual, virtual) received on your own dedicated tables. It is an audit record for checking which card a dealer entered, when, by which method, and whether the server accepted it.
Independent mode only
Available only to independent mode (independent) operators. Tables of prepaid mode (prepaid) operators are run by the platform headquarters, so they get 403 CARD_LOG_FORBIDDEN. Inputs on shared tables are not shown.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
from, to | timestamp | No | [from, to) based on the time the server received the input (received_at). Defaults to the last 24 hours. Same format as the common query parameters |
table_id | string | No | This table only (an empty result if malformed) |
round_id | string | No | This round only (an empty result if malformed) |
dealer | string | No | This dealer sign-in ID (login) only |
source | string | No | scanner, wedge, manual or virtual |
result | string | No | accepted, rejected or no_reply |
group | round | No | If round, returns per-round groups instead of inputs |
no_round | 1 | No | Only inputs that do not belong to a round (input list only) |
limit | integer | No | Default 100, maximum 500 |
offset | integer | No | Number of rows to skip |
Response example — input list
{
"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
}Input fields
| Field | Type | Description |
|---|---|---|
received_at | timestamp | Time the server received the input (UTC) |
id | string | Input record ID |
dealer, role | string | Sign-in ID and role of the dealer who entered the card |
device_id, device_name | string | Device the input came from |
ip | string | null | Device IP |
source | string | scanner, wedge (keyboard-emulating scanner), manual (manual input by a pit boss) or virtual (virtual scanner — test environments) |
card | string | null | Card (e.g. AS) |
raw | string | null | Raw value read by the scanner |
scanner_id | string | null | Scanner identifier |
scanned_at | timestamp | null | Time the device read the card |
lag_ms | int | null | Time (ms) from the device reading the card until the server received it. Includes any clock difference on the device |
result | string | accepted, rejected or no_reply (no response) |
position | string | null | Card position (e.g. seat_3) |
reject_code, reject_reason | string | null | Rejection code and reason |
hidden | bool | If true, card and raw are masked because the round is in progress (only when present) |
table_id, table_name, table_names, game_code | Table | |
round_id, round_no, round_status | Round (null if the input does not belong to a round) |
For inputs in a round that is in progress (round_status: "open"), the card values (card, raw) are masked as null and hidden: true is added. They become visible once the round ends.
Response example — per-round groups (group=round)
{
"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
}| Field | Description |
|---|---|
first_at, last_at | Time of the first and last input in the round |
n, n_accepted, n_rejected, n_no_reply | Number of inputs, and counts per result |
n_scanned, n_manual, n_virtual | Counts per input method (n_scanned = scanner + wedge) |
n_late | Number of inputs that took more than 5 seconds from scan to server receipt |
n_pitboss | Number of inputs entered by a pit boss |
max_lag_ms | Longest lag (ms). null if there is no scan time at all |
dealers, devices | Dealers and devices that entered cards |
Inputs that do not belong to a round (e.g. rejections before a round starts) are grouped into one row per table, with round_id set to null. To expand the inputs of a round row, query the input list again with the same conditions plus round_id (for a row without a round, table_id + no_round=1). Groups are ordered by most recent last input first.
The inputs of a single round are also available as card_log in round details.
Pagination
Offset-based. If has_more: true, increase offset by limit and query again. The input list is ordered by received_at, most recent first.
Errors
| HTTP | Code | Situation |
|---|---|---|
| 403 | CARD_LOG_FORBIDDEN | Prepaid mode operator |
| 500 | INTERNAL_ERROR | Temporary server error |