SBOBET API
API สำหรับจัดการ Member, เครดิต และรายงานของ SBOBET ผ่านบัญชี agent ทุกเส้นเป็น JSON ทั้งขาเข้าและขาออก
1แนบ API Key ทุกครั้ง
ส่ง header X-API-Key ในทุก request
(หรือ Authorization: Bearer <access token>)
ถ้าไม่แนบจะได้ 401 FSB-AUTH-001
2เรียก Directory ก่อน
เริ่มจาก POST /report/directory เพื่อดูรายชื่อบัญชีดาวน์ไลน์ —
ได้ customerId และ accountId
ไว้ใช้กับเส้นรายงานอื่นโดยไม่ต้องพิมพ์เอง
ตัวอย่างการเรียกใช้
curl -X POST {BASE_URL}/getcredit \
-H "X-API-Key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{"userName": "member001"}'
เลือกเส้นที่ต้องการจากเมนูด้านซ้าย — ใช้ช่องค้นหาด้านบนเพื่อกรอง
และลิงก์ของแต่ละหน้าแชร์ต่อได้ (URL มี #ชื่อเส้น ต่อท้าย)
POST Create Member
Description
สร้าง Member account ใหม่
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| userName | String | Optional | Username ที่ต้องการสร้าง — ไม่ส่งก็ได้: ระบบจะไล่เลขถัดไปให้อัตโนมัติ (code ของ agent เป็น prefix + เลขถัดจาก member ที่มีอยู่ เช่น zum4501000, zum4501001). ถ้าส่งมาเองต้องขึ้นต้นด้วย code ของ agent ตัวที่สร้าง ไม่งั้นพอร์ทัลปฏิเสธ (Invalid Username) |
| password | String | Optional | Password (default: "123123123Zz@@") |
| firstName | String | Optional | ชื่อ (default: "ABC") |
| lastName | String | Optional | นามสกุล (default: "Xenter") |
| useUsername | String | Required | บัญชี agent ที่ใช้สร้าง (ต้องเป็นบัญชีระดับ AG) |
| agentCustomerId | Number | Required | customerId ของบัญชี agent นั้น — ระบบตรวจกับ portal ว่าตรงกับ useUsername ก่อนสร้าง ถ้าไม่ตรงจะปฏิเสธทั้งคำขอ |
Request Example
// ไม่ต้องส่ง userName — ระบบไล่เลขถัดไปให้เอง (เช่น zum4501003) { "password": "Test@123456", "firstName": "John", "lastName": "Doe", "useUsername": "zum45agent", "agentCustomerId": 9000001 } // หรือกำหนดเอง (ต้องขึ้นต้นด้วย code ของ agent) { "userName": "zum4501099", "password": "Test@123456", "useUsername": "zum45agent", "agentCustomerId": 9000001 }
Response Example
{
"success": true,
"message": "สร้าง Member สำเร็จ: abc88zz001",
"userName": "abc88zz001",
"customerId": 9100012,
"agentCustomerId": 9000001,
"usedUsername": "agentdemo168",
"response": {
"__note__": "ค่าดิบที่ portal คืนกลับมา (ผ่านตรง ๆ) — โครงสร้างขึ้นกับ portal"
}
}
success, message, userName,
customerId เป็น envelope ของบริการนี้
ส่วน response คือค่าที่ portal คืนกลับมาแบบไม่ปรับรูป
(ล้มเหลวจะได้ success: false พร้อม failedAPI
และ upstreamStatus)
POST Create Bulk Members
Description
สร้าง Member accounts หลายๆ account พร้อมกัน (สูงสุด 100 accounts)
Request Example
{
"userNames": ["abc88zz001", "abc88zz002", "abc88zz003"],
"useUsername": "agentdemo168",
"agentCustomerId": 9000001,
"password": "123123123Zz@@",
"firstName": "ABC",
"lastName": "Xenter"
}
Response Example
{
"success": true,
"message": "สร้าง Member สำเร็จ 3/3 accounts",
"requested": 3,
"successCount": 3,
"failCount": 0,
"agentCustomerId": 9000001,
"usedUsername": "agentdemo168",
"createdAccounts": [
"abc88zz001",
"abc88zz002",
"... เหลืออีก 1 รายการ"
],
"results": [
{
"userName": "abc88zz001",
"customerId": 9100012,
"success": true
},
{
"userName": "abc88zz002",
"success": true
},
"... เหลืออีก 1 รายการ"
]
}
โครงสร้างจากผลการเรียกจริง
POST Client Login / Web Proxy
Description
ล็อกอินผู้เล่นแล้วเปิด url ที่ตอบกลับใน browser ของลูกค้า เพื่อตั้ง cookie sbo_session และเข้าหน้าเล่น หน้าเว็บ, CSS, JavaScript และ WebSocket ใช้ proxy ที่รวมอยู่ใน full-sbobet
เรียกจาก frontend หรือ backend ของเว็บคุณได้ทุก domain โดยส่ง username/password ไม่ต้องใช้ UI ของ home และไม่ต้องส่ง API key จากนั้นเปิด url ที่ตอบกลับมาใน browser ของลูกค้าเพื่อเข้าหน้าเล่น
Request Body
{"username":"member001","password":"CurrentPassword"}Response Example
{
"success": true,
"session": "<opaque-session-id>",
"url": "https://amusementgot.com/client/entry?ticket=<one-time-ticket>",
"play_url": "https://amusementgot.com/"
}url ใช้ครั้งเดียวภายใน 2 นาที: GET /client/entry?ticket=... ตั้ง cookie แล้ว redirect ไป play_url URL สุดท้ายเป็น https://amusementgot.com/ รองรับกรณี browser ไม่รับ third-party cookies ให้เปิดด้วย window.location.assign(data.url) หากเรียกผ่าน backend ให้ส่ง url กลับ frontend หรือ redirect browser ไป url นั้น
GET /client/status ตรวจสถานะ cookie ปัจจุบัน; POST /client/logout ยกเลิก session ของ browser นี้ การรับทุก domain ใช้กับเส้น login ส่วน status/admin API ใช้ origin/auth เดิม
เครื่องมือจัดการ proxy อยู่ที่ /client/proxy/health, /client/proxy/metrics, /client/proxy/sessions, /client/proxy/refresh, /client/proxy/import-session, /client/proxy/cf-cookies และต้องใช้ API key หรือสิทธิ์ admin
POST Client Change Password
Description
ล็อกอินเว็บผู้เล่นด้วย username/password แล้วเรียก POST /api/Password/UpdatePassword ในคำขอเดียว ใช้ได้กับหน้ารหัสผ่านหมดอายุ ไม่ต้องมี agent JWT, PIN หรือ customerId
แนบ X-API-Key หรือ Bearer token ที่มีสิทธิ์ operator ขึ้นไป ระบบใช้ session เฉพาะคำขอนี้ และไม่เก็บรหัสผ่านหรือ session ผู้เล่น
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | String | Required | Username ของผู้เล่น |
| password | String | Required | รหัสผ่านปัจจุบัน |
| newPassword | String | Required | รหัสผ่านใหม่ ระบบส่ง confirmNewPassword ให้ตรงกันอัตโนมัติ |
curl -X POST {BASE_URL}/client/changepassword \
-H "X-API-Key: <your-api-key>" \
-H "Content-Type: application/json" \
-d '{"username":"member001","password":"OldPass123@@","newPassword":"NewPass456@@"}'Response Example
{
"success": true,
"username": "member001",
"message": "เปลี่ยนรหัสผ่านผู้เล่นสำเร็จ กรุณาล็อกอินด้วยรหัสผ่านใหม่",
"requiresLogin": true,
"response": {"status": "Success", "message": ""}
}สำเร็จเมื่อ upstream ตอบ HTTP 200 และ status เป็น Success จากนั้นให้ผู้เล่นล็อกอินใหม่ด้วยรหัสใหม่ การเปลี่ยนรหัสไม่มี automatic retry
กรณีผิดพลาดมี step, upstreamStatus และ details ระบุขั้นตอนที่ล้มเหลว: 400 ข้อมูลไม่ครบหรือ upstream ปฏิเสธ, 429 ถูกจำกัดอัตรา, 503 upstream ผิดปกติหรือ timeout (กรณี timeout มี timeout: true) — ระบบนี้ไม่ตอบ 502/504 เพราะ Cloudflare จะแทน body ของสองสถานะนั้นด้วยหน้า HTML ของตัวเอง
ใช้ login rate limit และ outbound proxy ของระบบ โดเมนผู้เล่นกำหนดด้วย CLIENT_PORTAL_DOMAIN (ค่าเริ่มต้น amusementget.com) กำหนด CLIENT_OUTBOUND_PROXY เพื่อใช้ proxy แยกสำหรับ API ผู้เล่น กรณี upstream redirect ด้วย HTTP 302 ให้ตรวจว่า IP ของ proxy เข้าถึงเว็บผู้เล่นได้
POST Change Password
Description
เปลี่ยนรหัสผ่านของ Member
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| customerId | Number | Required | Customer ID ที่ต้องการเปลี่ยนรหัสผ่าน |
| password | String | Required | รหัสผ่านใหม่ |
| accountId | String | Optional | Account ID (ถ้าไม่ใส่จะใช้ customerId) |
Request Example
{
"customerId": 9100020,
"accountId": "abc88zz960",
"password": "@@123123Zz",
"useUsername": "bot0860"
}
Response Example
{
"success": true,
"message": "เปลี่ยนรหัสผ่านสำเร็จสำหรับ Customer ID: 9100016",
"customerId": 9100016,
"accountId": "xyz69aa001",
"usedUsername": "xyz69test",
"profileBefore": {
"accountId": "xyz69aa001",
"firstName": "",
"lastName": "",
"phone": "",
"mobile": "",
"isSuspended": true,
"isLocked": false
}
}
โครงสร้างจากผลการเรียกจริง
POST Suspend Account
Description
ล็อค/ปลดล็อค account
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| customerId | Number | Required | Customer ID ที่ต้องการ suspend |
| isSuspended | Boolean | Required | true = ล็อค, false = ปลดล็อค |
Request Example
{
"customerId": 9100017,
"isSuspended": true,
"useUsername": "bot0860"
}
Response Example
{
"accountId": "xyz5911",
"currentStatus": false,
"customerId": 9100009,
"message": "Customer ID 9100009 เปิดอยู่แล้ว",
"noChangeNeeded": true,
"success": true,
"usedUsername": "agentdemo168"
}
POST Suspend Bulk Accounts
Description
ล็อค/ปลดล็อคหลาย accounts พร้อมกัน (สูงสุด 50 accounts)
Request Example
{
"customers": [9100017, 9100018, 9100019],
"isSuspended": true,
"useUsername": "bot0860"
}
Response Example
{
"action": "unsuspend",
"failCount": 0,
"message": "เปิดบัญชี: สำเร็จ 0 accounts, ล้มเหลว 0 accounts, ไม่ต้องเปลี่ยน 2 accounts",
"noChangeCount": 2,
"requested": 2,
"results": [
{
"accountId": "xyz5911",
"customerId": 9100009,
"message": "เปิดอยู่แล้ว",
"noChangeNeeded": true,
"statusAfter": false,
"statusBefore": false,
"success": true
},
{
"accountId": "xyz5922",
"customerId": 9100011,
"message": "เปิดอยู่แล้ว",
"noChangeNeeded": true,
"statusAfter": false,
"statusBefore": false,
"success": true
}
],
"success": true,
"successCount": 0,
"usedUsername": "agentdemo168"
}
POST Topup Credit
Description
เติมเครดิตให้กับ Member พร้อมตรวจสอบ boardtotal ก่อนเติม
Request Body
รองรับหลายรูปแบบ:
- เติมให้ 1 คน
- เติมให้หลายคนจำนวนเท่ากัน
- เติมให้หลายคนจำนวนต่างกัน
| Parameter | Type | Required | Description |
|---|---|---|---|
| agentCustomerId | Number | Optional | Agent Customer ID สำหรับดึงข้อมูลเครดิตก่อน/หลังเติม |
| accountId | String | Optional | accountId ของ member (เช่น zum4501000) — ถ้าส่งมา ระบบอ่านเครดิตปัจจุบันและยอดหลังทำรายการรายคนด้วย GET /CustomerBalanceInfoByAccountId (payload คงที่ ~0.5KB ไม่โตตามจำนวนยูสใต้ agent) และไม่ต้องส่ง agentCustomerId; ไม่ส่ง = อ่านจาก /wallet/creditbalance/children ทั้งสาย (แบบเดิม) — accountId ผิด → 404 ปฏิเสธทันที, ข้ามสาย → 403; ต้องคู่กับ customerId คนเดียวกัน เพราะการเขียนใช้ customerId |
| asyncVerify | Boolean | Optional | ตอบทันทีหลังเขียนเครดิตสำเร็จ แล้วตรวจยอดหลังทำรายการเบื้องหลัง (เร็วขึ้น ~1 round-trip) — verify.status จะเป็น "pending" และถ้ายอดไม่ตรงจะเข้า /reviews ตามปกติ (default: false) |
| boardtotal | Number | Optional | ตรวจสอบ boardtotal (ยอดกระดาน agent) ก่อน/หลังเติม — ส่งมาเพื่อยืนยันยอด (ถ้าไม่ตรงจะไม่เติม). ไม่ส่ง = ข้ามการตรวจยอดกระดานทั้งหมด (เติมตามปกติ, verify.status = "skipped") เหมาะกับเติม MA→AG ที่ไม่มี/อ่านยอดกระดานไม่ได้ |
Request Examples
แนะนำ: ส่ง accountId คู่ customerId — อ่านเครดิตรายคน เหมาะกับ agent ที่มียูสเยอะ
{
"customerId": 37475555,
"accountId": "zum4501000", // ส่งคู่กับ customerId → อ่านเครดิตรายคน ไม่ต้องใช้ agentCustomerId
"amount": 500,
"boardtotal": 97700,
"useUsername": "zum45agent"
}
หลายคนแบบรายคน (accountId ต่อ item)
{
"customers": [
{ "customerId": 37475555, "accountId": "zum4501000", "amount": 500 },
{ "customerId": 37475556, "accountId": "zum4501001", "amount": 200 }
],
"useUsername": "zum45agent"
}
รูปแบบเดิม: เติมผ่าน customers array ด้วย agentCustomerId (อ่านจาก children ทั้งสาย)
{
"agentCustomerId": 9000001,
"customers": [
// { "customerId": 9100015, "amount": 160 },
{
"customerId": 9100012,
"amount": 100,
"boardtotal": 46887
}
],
"useUsername": "abc88zzq"
}
รูปแบบที่ 1: เติมให้ 1 คน
{
"customerId": 9100012,
"amount": 100,
"boardtotal": 1000,
"accountId": "bot0860",
"agentCustomerId": 9000001,
"useUsername": "abc88zzq"
}
รูปแบบที่ 2: เติมให้หลายคนจำนวนเท่ากัน
{
"customerId": "9100012,9100013,9100014",
"amount": 100,
"agentCustomerId": 9000001
}
รูปแบบที่ 3: เติมให้หลายคนจำนวนต่างกัน
{
"customers": [
{
"customerId": 9100012,
"amount": 130,
"boardtotal": 1000,
"accountId": "bot0860",
"agentCustomerId": 9000001
},
{
"customerId": 9100013,
"amount": 105
}
],
"agentCustomerId": 9000001,
"useUsername": "abc88zzq"
}
Response Example
{
"success": true,
"verify": {
"status": "ok"
},
"message": "เติมเครดิต: สำเร็จ 2 accounts, ล้มเหลว 0 accounts, รวม 235 บาท",
"usedUsername": "abc88zzq",
"summary": {
"totalAccounts": 2,
"successCount": 2,
"failCount": 0,
"skipCount": 0,
"creditNoChangeCount": 0,
"requestedAmount": 235,
"actualUsedAmount": 235
},
"boardtotal": {
"boardtotal_before": 10000,
"boardtotal_after": 9765,
"used": 235,
"change": "10000 → 9765 (ใช้ไป 235 บาท)"
},
"results": [
{
"customerId": 9100012,
"amount": 130,
"success": true,
"creditBefore": {
"givenCredit": 100,
"totalBalance": 50,
"betCredit": 150
},
"creditAfter": {
"givenCredit": 230,
"totalBalance": 50,
"betCredit": 280
},
"creditChange": {
"givenCredit": 130,
"totalBalance": 0,
"betCredit": 130
}
}
]
}
หมายเหตุ
- หากเครดิตปัจจุบันเท่ากับจำนวนที่ต้องการเติมอยู่แล้ว (CreditNoChange) จะนับเป็น skip
- ระบบจะแสดงข้อมูลเครดิตก่อนและหลังเติมเมื่อระบุ agentCustomerId
- boardtotal คือยอดเครดิตคงเหลือของ Agent
-
การเทียบยอดหลังเติม (verify):
ระบบอ่านยอดกระดานก่อนและหลังเติม
แล้วเทียบว่ายอดที่ขยับเท่ากับยอดที่เติมพอดี ผลอยู่ในฟิลด์
verify.status—"ok"ยอดตรง,"unverified"อ่านยอดไม่ครบจึงเทียบไม่ได้,"mismatch"ยอดไม่ตรง -
เมื่อ
verify.status = "mismatch"ระบบตอบsuccess: falseพร้อมexpected,actualและreviewId— รายการถูกบันทึกเป็นรายการค้างตรวจสอบ รอพนักงานตรวจที่GET /reviews -
หากส่ง
boardtotalรายตัวแล้วยอดไม่ตรงก่อนเติม รายการนั้นจะถูกข้าม (ยังไม่มีเงินเคลื่อนไหว) และถูกบันทึกเป็นรายการค้างตรวจสอบพร้อมreviewIdเช่นกัน
POST Get Credit
Description
ดึงข้อมูลเครดิตของลูกค้าทั้งหมดภายใต้ Agent หรือเฉพาะลูกค้าที่ระบุ
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| agentCustomerId | Number | Required | Agent Customer ID |
| customerId | Number | Optional | Customer ID ที่ต้องการดูเฉพาะเจาะจง |
| useUsername | String | Optional | ระบุ username ที่ต้องการใช้ JWT |
Request Example
{
"agentCustomerId": 9000001,
"customerId": 9100012,
"useUsername": "abc88zzq"
}
Response Example
{
"success": true,
"agentCustomerId": 9000001,
"customerId": 9100012,
"usedUsername": "abc88zzq",
"data": {
"customerId": 9100012,
"credit": {
"givenCredit": 1000
},
"balance": {
"totalBalance": 500
},
"betCredit": 1500,
"customerInfo": {
"accountId": "abc88zz001",
"isSuspended": false
}
},
"message": "ดึงข้อมูลเครดิตสำเร็จสำหรับ Customer ID: 9100012"
}
POST Get Credit Agent
Description
ดึงยอดเครดิตของ ตัว Agent เอง จากหน้า Dashboard ของพอร์ทัลโดยตรง —
availableCredit คือเครดิตที่ Agent เหลือไว้แจกจ่ายให้สายงาน
ต่างจาก /getcredit ที่ดูเครดิตของลูกค้าในสายงาน
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| useUsername | String | Optional | ระบุ username ที่ต้องการใช้ JWT — ไม่ระบุจะใช้บัญชีของ session นั้น |
Request Example
{
"useUsername": "abc88zzq"
}
Response Example
{
"success": true,
"accountId": "abc88zzq",
"usedUsername": "abc88zzq",
"availableCredit": 125000,
"message": "ดึงเครดิตเอเย่นต์สำเร็จสำหรับ abc88zzq"
}
POST Transfer (Withdraw)
Description
ถอนเงินจาก Member โดยระบบจะ:
- อ่านยอดกระดานและ balance ของ agent ก่อนถอนทุกครั้ง (ปิดได้ด้วย skipCashBalanceCheck สำหรับกระดานที่ไม่มี Cash Balance)
-
ตรวจสอบ bet credit ว่าเพียงพอหรือไม่ โดย
betCredit = givenCredit + totalBalance(totalBalanceคือยอดได้/เสียของลูกค้า: บวก = ลูกค้าได้, ลบ = ลูกค้าเสีย) -
ตัดจาก Credit (
givenCredit) ก่อน หากยังไม่พอ จึงตัดส่วนที่เหลือจาก Balance (Transfer). ถ้า totalBalance ติดลบ (ลูกค้าเสีย) จะตัดจาก givenCredit อย่างเดียว ไม่แตะ Balance -
อ่านยอดลูกค้าซ้ำหลังถอน
แล้วเทียบว่าลดลงเท่ายอดที่ถอนพอดี — ผลอยู่ในฟิลด์
verify -
ยอดไม่ตรง (ก่อนหรือหลัง) →
บันทึกเป็นรายการค้างตรวจสอบ ดูได้ที่
GET /reviews
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| customerId | Number | Required | Customer ID ที่ต้องการถอนเงิน |
| amount | Number | Required | จำนวนเงินที่ต้องการถอน (ส่งเป็นค่าบวก) |
| boardtotal | Number | Recommended | ควรส่งมาเสมอ — ยอดกระดาน (agent board total) ที่ปลายทางคาดไว้ ส่งมาเพื่อ cross-check กับยอดจริงให้ตรงกัน ถ้าไม่ตรงจะไม่ถอน และบันทึกเป็นรายการค้างตรวจสอบ (กันการทำรายการบนยอดที่คลาดเคลื่อน). ถ้าไม่ส่ง ระบบจะข้ามการ cross-check ยอดกระดานนี้ |
| cashbalance | Number | Optional |
ไม่จำเป็นต้องส่ง — ระบบอ่าน totalBalance (net win/loss) สดเอง
และ verify ก่อน/หลังถอนให้อยู่แล้ว.
ส่งมาได้เป็น guard เสริม: ถ้าไม่ตรงกับค่าจริงจะไม่ถอน และบันทึกเป็นรายการค้างตรวจสอบ.
ค่าจริงเทียบกับ totalBalance ไม่ใช่ฟิลด์ cashBalance
(พอร์ทัลรายงาน cashBalance = 0 ที่ระดับ agent-aggregate ยอดจริงอยู่ที่ totalBalance).
หมายเหตุ: พอร์ทัล SPA ( /wallet/transfer/partialnew) ไม่รับ field นี้ในตัว transfer —
การตรวจยอดทำที่ฝั่งเรา ไม่ได้ส่งต่อไปให้ค่าย
|
| skipCashBalanceCheck | Boolean | Optional | สำหรับกระดานที่ไม่มี Cash Balance เท่านั้น — ปกติระบบอ่าน cash balance ของ agent ก่อนถอนทุกครั้ง ตั้งเป็น true เพื่อข้ามการอ่านนี้ (default: false) |
| skipBetCreditCheck | Boolean | Optional | ข้ามการเช็ค bet credit (default: false) |
| accountId | String | Optional | accountId ของ member (เช่น zum4501000) — ถ้าส่งมา ระบบอ่านเครดิตปัจจุบันและยอดหลังทำรายการรายคนด้วย GET /CustomerBalanceInfoByAccountId (payload คงที่ ~0.5KB ไม่โตตามจำนวนยูสใต้ agent) และไม่ต้องส่ง agentCustomerId; ไม่ส่ง = อ่านจาก /wallet/creditbalance/children ทั้งสาย (แบบเดิม) — accountId ผิด → 404 ปฏิเสธทันที, ข้ามสาย → 403; ต้องคู่กับ customerId คนเดียวกัน เพราะการเขียนใช้ customerId |
| asyncVerify | Boolean | Optional | ตอบทันทีหลังเขียนเครดิตสำเร็จ แล้วตรวจยอดหลังทำรายการเบื้องหลัง (เร็วขึ้น ~1 round-trip) — verify.status จะเป็น "pending" และถ้ายอดไม่ตรงจะเข้า /reviews ตามปกติ (default: false) |
| agentCustomerId | Number | Recommended | Agent Customer ID สำหรับเช็ค bet credit (แนะนำให้ใส่เสมอ) |
| useUsername | String | Optional | ระบุ username ที่ต้องการใช้ JWT |
Request Examples
แนะนำ: ส่ง accountId คู่ customerId — อ่านยอดรายคน
{
"customerId": 37475555,
"accountId": "zum4501000", // ส่งคู่กับ customerId → อ่านยอดรายคน ไม่ต้องใช้ agentCustomerId
"amount": 100,
"boardtotal": 97700, // ควรส่ง — cross-check ยอดกระดานให้ตรง
"useUsername": "zum45agent"
}
รูปแบบเดิม: ด้วย agentCustomerId (อ่านจาก children ทั้งสาย)
{
"agentCustomerId": 9000001,
"customerId": 9100012,
"amount": 1,
"boardtotal": 46887, // ควรส่ง — cross-check ยอดกระดานให้ตรง
"useUsername": "abc88zzq" // optional
// cashbalance ไม่ต้องส่ง — ระบบอ่าน totalBalance สดและ verify เอง
}
Response Example
{
"success": true,
"verify": {
"status": "ok"
},
"message": "Transfer สำเร็จสำหรับ Customer ID 9100020",
"customerId": 9100020,
"amount": 10,
"usedUsername": "bot0860",
"breakdown": {
"totalWithdrawn": 10,
"fromCredit": 10,
"fromBalance": 0
},
"balanceBefore": {
"credit": 15,
"totalBalance": 100,
"betCredit": 115,
"boardtotal": 909015.0,
"cashBalance": -150010.35 // net balance ของ agent (อ่านจาก totalBalance) ลบ = ฝั่ง agent ติดลบ
},
"balanceAfter": {
"credit": 5,
"totalBalance": 100,
"betCredit": 105,
"boardtotal": 909015.0,
"cashBalance": -150000.35
}
}
Error Cases
- บัญชีถูกล็อคอยู่: ไม่สามารถทำ transfer ได้
-
boardtotal/cashbalance ไม่ตรง: จะไม่ดำเนินการ
transfer และบันทึกเป็นรายการค้างตรวจสอบ (ตอบ
reviewIdมาด้วย) -
อ่าน cash balance ก่อนถอนไม่สำเร็จ:
จะไม่ดำเนินการ transfer — ถ้ากระดานไม่มี Cash Balance ให้ส่ง
skipCashBalanceCheck: true -
ยอดหลังถอนไม่ตรง: เงินขยับไปแล้ว ระบบตอบ
HTTP 200 แต่
success: falseพร้อมverify.status: "mismatch"และreviewId— รอพนักงานตรวจสอบ อย่า retry ทันที - เครดิตไม่พอ: เมื่อ bet credit น้อยกว่าจำนวนที่ต้องการถอน
- ไม่พบลูกค้า: เมื่อ customerId ไม่อยู่ภายใต้ agent ที่ระบุ
หมายเหตุสำคัญ
- ระบบจะตัดจาก Credit ก่อนเสมอ หากไม่พอจึงจะตัดจาก Balance
- ระบบจะ suspend account อัตโนมัติก่อนทำ transfer และ unsuspend หลังเสร็จ
- หากไม่ระบุ agentCustomerId อาจทำให้เช็ค bet credit ไม่ถูกต้อง
- Bet Credit = Given Credit + Total Balance
- หากต้องการข้ามการเช็ค bet credit ให้ตั้ง skipBetCreditCheck = true
GET Review Queue
Description
รายการฝาก/ถอนที่การเทียบยอดก่อนหรือหลังทำรายการ ไม่ตรง — ระบบบันทึกไว้เป็นรายการค้าง รอพนักงานเข้ามาตรวจสอบและปิดรายการ ตาม ขั้นตอนการเทียบยอดกระดาน/Cash Balance
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | String | Optional |
pending (default) | resolved |
all
|
Response Example
{
"success": true,
"count": 1,
"reviews": [
{
"id": "665f1a2b3c4d5e6f7a8b9c0d",
"at": "2026-08-13T12:00:00Z",
"kind": "withdraw",
"stage": "after",
"field": "credit",
"customerId": 9100012,
"amount": 100,
"expected": 450,
"actual": 460,
"useUsername": "abc88zzq",
"note": "ยอดลูกค้าหลังถอนไม่ตรงกับที่คาด รอพนักงานตรวจสอบ",
"status": "pending"
}
]
}
หมายเหตุ
-
kind:deposit(เติม) |withdraw(ถอน) -
stage:before= ไม่ตรงก่อนทำรายการ (ยังไม่มีเงินเคลื่อนไหว) |after= ไม่ตรงหลังทำรายการ (เงินขยับไปแล้ว ต้องตรวจสอบก่อนทำรายการซ้ำ) -
field: ยอดที่ไม่ตรง —boardtotal|cashbalance|credit - ต้องตั้งค่า MongoDB จึงจะเก็บคิวนี้ได้
POST Resolve Review
Description
ปิดรายการค้างตรวจสอบหลังพนักงานตรวจแล้ว รายการที่ถูกปิดแล้วปิดซ้ำไม่ได้ (ได้ 404) เพื่อไม่ให้สองคนเข้าใจว่าตัวเองเป็นคนจัดการรายการเดียวกัน ต้องใช้สิทธิ์ระดับ operator ขึ้นไป
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | String | Required | id ของรายการค้างตรวจสอบ (จาก GET /reviews) |
| resolution | String | Optional | บันทึกผลการตรวจสอบ |
Request Example
{
"id": "665f1a2b3c4d5e6f7a8b9c0d",
"resolution": "ตรวจแล้ว ยอดตรงหลังรอบตัดยอด"
}
Response Example
{
"success": true,
"id": "665f1a2b3c4d5e6f7a8b9c0d"
}
GET Agent List
Description
รายชื่อ ทุกบัญชีในระบบของเรา แบบแบน (หนึ่งแถวต่อหนึ่ง
บัญชี) — ไม่กรองสถานะ ไม่ว่าจะเปิด/ปิด (disabled), มี
token หรือหมดอายุ ก็แสดงครบทุกตัว พร้อมระดับ
(level/levelName) และสถานะ session
(hasToken, expired). บัญชีที่อยู่ใน agent
pool จะแนบข้อมูล pool เพิ่ม (inPool,
agentCustomerId, useUsername,
memberCount, active) — เลข member มาจากการ
scan/นับล่าสุด ไม่ยิงพอร์ทัลสด ถ้าต้องการเลขล่าสุดให้เรียก
POST /pool/{prefix}/refresh-count ก่อน
Query Parameters
ไม่มี — ส่งเฉพาะ header Authorization: Bearer <token>. member count ของ agent ใน pool ถูกนับสดจากพอร์ทัลทุกครั้งที่เรียก (ยิงพอร์ทัล 1 ครั้งต่อ agent — อย่าเรียกถี่เกินไป).
Response Example
{
"success": true,
"total": 333,
"agents": [
{
"accountId": "bot0860",
"accountCode": "zds0860",
"customerId": 3021111,
"parentCode": "zds08",
"parentAccountId": "onlinezds08",
"parentCustomerId": 19700001,
"parentName": "ZDS08 (MA)",
"level": 2,
"levelName": "AG",
"disabled": true,
"hasToken": true,
"expired": true,
"inPool": false,
"memberCount": 0,
"usable": false,
"active": false
},
{
"accountId": "zum4501",
"accountCode": "zum4501",
"level": 2,
"levelName": "AG",
"disabled": false,
"hasToken": true,
"expired": false,
"inPool": true,
"prefix": "zum45",
"agentCustomerId": 12345678,
"useUsername": "zum45agent",
"memberCount": 312,
"state": "active",
"usable": true,
"active": true,
"countedAt": "2026-08-17T14:15:58Z"
}
]
}
หมายเหตุ
accountId: username ที่เก็บในระบบเรา (key ของ store)accountCode: agent code ฝั่งพอร์ทัล (จาก claim ของ token เช่นzds0860)customerId: customerId ของบัญชีตัวเองฝั่งพอร์ทัล (ทุกระดับ รวม MA/SMA)parentCode/parentAccountId/parentCustomerId/parentName: MA/สายบนที่ AG นี้อยู่ใต้ — code, login (accountId), customerId ฝั่งพอร์ทัล และชื่อของ MA.parentAccountIdใช้เป็นuseUsernameได้เลย เวลาจะเติมเครดิต/โอนให้ agent ตัวนี้ (ต้องยิงด้วย session ของตัวแม่). มาจาก org chart ที่ crawl มา; agent ที่ pool สร้างใหม่ fallback ใช้ MA ของ pool. ว่างถ้ายังไม่มีข้อมูลสายlevel/levelName: ระดับบัญชี —2=AG(Agent),3=MA(Master Agent),4=SMA(Super Master);0/""= ยังไม่ทราบdisabled: ถูกปิดใช้งานในระบบเรา (ยังแสดงในลิสต์นี้เสมอ ไม่ถูกกรองออก)hasToken/expired: สถานะ session — มี token ไหม และหมดอายุหรือยังinPool: บัญชีนี้อยู่ใน agent pool ไหม; ถ้าtrueจะมีprefix,agentCustomerId,useUsername,memberCount,state,activeแนบมาด้วยmemberCount: จำนวนยูสเซอร์ภายใต้ agent — นับสดจากพอร์ทัลทุกครั้งที่เรียก (countedAt= เวลาที่นับ); 0 ถ้าไม่อยู่ใน poolactive: เป็น agent ตัวที่ pool ใช้สร้างยูสใหม่อยู่ตอนนี้- เส้นนี้ดึงจาก store ของเรา — แสดงทุกบัญชีเสมอ ไม่กรองตามสถานะเปิด/ปิด
POST Agent Log
Description
ค้นหา action log ของเอเย่นต์จากพอร์ทัล (proxy ไปที่
agentlog/filter) — ประวัติการปรับเครดิต, โอนเงิน,
table limit, สถานะบัญชี, คอมมิชชั่น, ธนาคาร, ฝาก-ถอน และโปรโมชั่น
ตามช่วงเวลาและผู้ทำรายการ body ส่งต่อไปพอร์ทัลตามเดิมทุก field
ยกเว้น useUsername
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| useUsername | String | Required | บัญชีเอเย่นต์ที่ใช้ session (เช่น abc88) |
| fromDate | Number | Required | เริ่มช่วงเวลา (unix milliseconds) |
| toDate | Number | Required | สิ้นสุดช่วงเวลา (unix milliseconds) |
| createdBy | String[] | Optional | กรองผู้ทำรายการด้วย id ของบัญชีในพอร์ทัล เช่น ["online@abc88"] (ไม่ใช่ชื่อที่ใช้ล็อกอิน) — ไม่ส่งหรือส่ง [] = ทุกคนในบัญชี; id ที่ใช้ได้ดูจาก createdBy ในคำตอบ |
| actionLogTabs | Number[] | Optional | ประเภท log ที่ต้องการ (default [1] = Credit) |
| targetAccountId | String | Optional | กรองเฉพาะบัญชีปลายทาง (null = ทั้งหมด) |
Request Example
{
"useUsername": "abc88",
"fromDate": 1786507200000,
"toDate": 1787025600000,
"createdBy": ["online@abc88"],
"actionLogTabs": [1],
"targetAccountId": null
}
Response Example
{
"success": true,
"createdBy": ["online@abc88", "abc88"],
"logs": {
"creditAgentActionLogs": [
{
"fromValue": "1,200,000.00",
"toValue": "1,500,000.00",
"createOn": 1786536220563,
"accountId": "abc8801",
"createdBy": "online@abc88",
"description": "Credit",
"remark": "Successful;;",
"ip": "203.0.113.25"
}
],
"transferAgentActionLogs": [],
"tableLimitAgentActionLogs": [],
"accountStatusAgentActionLogs": [],
"commissionAgentActionLogs": [],
"bankAgentActionLogs": [],
"depositWithdrawalAgentActionLogs": [],
"promotionAgentActionLogs": []
}
}
หมายเหตุ
actionLogTabsเลือกได้หลายค่าพร้อมกัน — แต่ละ tab เติมข้อมูลลง array ของประเภทนั้นใน response (tab ที่ไม่ได้เลือกจะเป็น array ว่าง)createOnเป็น unix millisecondsfromValue/toValueเป็นสตริงมี comma ตามที่พอร์ทัลส่งมา- ต้องมี session ของบัญชีใน
useUsernameอยู่แล้ว (login ผ่านระบบก่อน)
POST Backup Domains
Description
ดึงรายชื่อโดเมนสำรอง (backup domains) ของฝั่งพอร์ทัลเอเย่นต์
(proxy ไปที่ GET domains/backup)
ใช้เช็คว่ามีโดเมนไหนใช้เข้าระบบแทนได้เมื่อโดเมนหลักถูกบล็อก
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| useUsername | String | Required | บัญชีเอเย่นต์ที่ใช้ session (เช่น abc88) |
Request Example
{
"useUsername": "abc88"
}
Response Example
{
"success": true,
"domains": [
"example-backup1.example.com",
"example-backup2.example.com"
]
}
หมายเหตุ
domainsส่งต่อโครงตามที่พอร์ทัลตอบมาตรง ๆ — รูปแบบอาจเปลี่ยนตามฝั่งพอร์ทัล- ต้องมี session ของบัญชีใน
useUsernameอยู่แล้ว (login ผ่านระบบก่อน)
GET Analytics
Description
แปดเส้นที่หน้า Control Room ใช้ —
อ่านจาก MongoDB ที่ตัวเก็บข้อมูลเก็บไว้ ไม่ยิงพอร์ทัลเลย
จึงเรียกถี่ได้และไม่กินโควตา switch_account
ทุกเส้นเป็น GET รับพารามิเตอร์ทาง query string
ไม่มี body และเปิดให้ตั้งแต่ระดับ viewer ขึ้นไป
วันที่ทุกตัวเป็น YYYY-MM-DD ตามวันแพ้/ชนะของพอร์ทัล
(แบบเดียวกับ /history/*) ไม่ใช่วันตามปฏิทินสากล
ถ้ายังไม่ได้ตั้ง MONGO_URI ทุกเส้นตอบ
501 พร้อมข้อความว่าการเก็บประวัติยังไม่ได้เปิดใช้งาน
Endpoints
| Path | Query | ตอบอะไร |
|---|---|---|
/analytics/today |
date (ไม่ใส่ = วันนี้) |
วันนั้นรายชั่วโมง เทียบกับชั่วโมงเดียวกันเมื่อวาน + ผู้เล่นวันนี้ กำลังเล่นตอนนี้ บิลค้าง และบิล/นาที |
/analytics/series |
from to (≤400 วัน, ไม่ใส่ = 30 วันล่าสุด) · by=day|hour · group=none|product |
turnover / win-loss / จำนวนบิล เป็นราย bucket · by=hour ใช้ได้กับวันเดียว (from เท่ากับ to) เท่านั้น |
/analytics/top |
from to (≤92 วัน, ไม่ใส่ = 7 วันล่าสุด) · metric=turnover|winloss · kind=player|agent · limit 1–100 (ค่าเริ่ม 20) |
อันดับผู้เล่นหรือสาย พร้อม avgStake สินค้าหลัก IP ล่าสุด (เฉพาะ player) และ spark 14 วัน |
/analytics/heatmap |
from to (≤92 วัน, ไม่ใส่ = 28 วันล่าสุด) |
จำนวนบิลแยกตามวันในสัปดาห์ × ชั่วโมง — ใช้ดูว่าคึกคักช่วงไหน |
/analytics/behaviour |
from to (≤92 วัน, ไม่ใส่ = 7 วันล่าสุด) |
สัดส่วน live/pre-match, บิลชุด vs เดี่ยว, ลีกและประเภทบิลยอดนิยม, ผู้เล่นใหม่/กลับมา/หายไป |
/analytics/players |
window=7d|30d (ค่าเริ่ม 7d) |
ตารางความเสี่ยงรายผู้เล่น พร้อมธง highStake sharedIP winStreak inactive |
/analytics/exposure |
matchLimit · playerLimit — ทั้งคู่ 1–100 (ค่าเริ่ม 20) |
บิลที่ยังไม่เคลียร์ทั้งระบบ ณ ตอนนี้ — ยอดเสี่ยงรวม จำนวนใบ แมตช์ที่เงินค้างมากสุด และผู้เล่นที่ถือมากสุด |
/analytics/coverage |
days 1–400 (ค่าเริ่ม 30) |
ความครบของข้อมูลรายวันจาก coverage_daily ที่รอบกลางคืนคำนวณไว้ |
Response Example
GET /analytics/today
{
"date": "2026-08-29",
"hourly": [
{ "hour": 13, "turnover": 1840500, "winloss": -24100, "bets": 912 }
],
"yesterdayHourly": [
{ "hour": 13, "turnover": 1712300, "winloss": 18700, "bets": 874 }
],
"playersToday": 1284,
"playingNow": 63,
"exposureStake": 4180000,
"exposureTickets": 2140,
"betsToday": 10422,
"betsPerMinute": 11.6,
"asOf": "2026-08-29T13:42:05+07:00"
}
playingNow นับ betAccountId
ที่ไม่ซ้ำกันในบิลค้างซึ่งเห็นล่าสุดภายใน 15 นาที
ไม่ใช่จำนวนคนที่เปิดหน้าเว็บอยู่
ข้อควรรู้
-
/analytics/coverageอ่านค่าที่คำนวณไว้อย่างเดียว วันที่รอบกลางคืนยังไม่ได้คำนวณจะตอบcoverage: null— null แปลว่ายังไม่ได้ตรวจ ไม่ใช่เก็บไม่ได้เลย ต้องแยกสองอย่างนี้ในการแสดงผล -
ตัวเลขทั้งหมดครบเท่าที่ตัวเก็บข้อมูลเก็บมาได้ ถ้า coverage
ของช่วงนั้นต่ำ ตัวเลขก็ต่ำตาม — ตรวจ
/analytics/coverageก่อนสรุปว่ายอดตก -
ช่วงวันที่ที่กว้างเกินเพดานของแต่ละเส้นตอบ
FSB-INPUT-004(400) ไม่ใช่ตัดช่วงให้เงียบๆ -
รอบคำนวณรายวัน (
analytics_daily,coverage_daily) ทำงานหลัง 06:00 น. เวลาไทย จึงยังไม่มีตัวเลขของ "เมื่อวาน" ก่อนเวลานั้น
รหัสข้อผิดพลาด
หน้าตาของคำตอบเมื่อล้มเหลว
{
"success": false,
"code": "FSB-CREDIT-003",
"error": "boardtotal ไม่ตรง: ระบบ 250000.50 แต่ใน agent 249500.50",
"fix": "อ่านยอดล่าสุดด้วย /getcredit แล้วส่งมาใหม่ ..."
}
- พอร์ทัลล้มเหลว = 503 เสมอ ไม่ใช่ 502/504 — API นี้อยู่หลัง Cloudflare ซึ่งจะแทน body ของ 502/504 ด้วยหน้า HTML ของตัวเอง ผู้เรียกจะเห็นแค่ "Bad gateway" โดยไม่มีเหตุผล จึงตอบเป็น 503 พร้อม JSON แทน (
code,upstreamStatus,details) - retryable: true + header
Retry-Afterมีเฉพาะเมื่อความล้มเหลวนั้นยังไม่ได้เปลี่ยนอะไรบนพอร์ทัล (งานอ่าน หรือขั้นเตรียม session ก่อนเขียน) — ลองใหม่ได้ทันทีอย่างปลอดภัย ถ้าไม่มี field นี้ อย่า retry อัตโนมัติ โดยเฉพาะคำสั่งเติม/โอน เพราะผลอาจเกิดไปแล้ว ให้ตรวจยอดก่อน - 503 +
sessionRecovering: true— session ของบัญชีพอร์ทัลหมดอายุ และพอร์ทัลยังไม่รับการล็อกอินจากระบบ (เช่นกำลังจำกัด IP ของระบบ) ยังไม่ได้ทำรายการใด ๆ ระบบจะล็อกอินให้เองเมื่อพอร์ทัลรับ ให้ลองใหม่ตามRetry-After— ผู้เรียกไม่ต้องล็อกอินเอง (เดิมกรณีนี้ตอบ 401 "กรุณาล็อกอินใหม่" ซึ่งผิด) ส่วน 401refreshable: falseเหลือเฉพาะกรณีที่ระบบแก้เองไม่ได้จริง: ไม่ได้เก็บรหัสไว้ หรือรหัสที่เก็บไว้ถูกพอร์ทัลปฏิเสธ - 429 จากพอร์ทัล (
rateLimited: true) มาพร้อม headerRetry-AfterและretryAfterSecondsเสมอ — ให้รอตามนั้น พอร์ทัลจำกัดตาม IP ขาออกของระบบนี้ ไม่ใช่ตามบัญชี การยิงซ้ำเร็วกว่านั้นไม่ช่วยให้ผ่าน ถ้ามีheldBack: trueแปลว่าคำขอนี้ ยังไม่ได้ถูกส่งไปพอร์ทัล: พอร์ทัลเพิ่งปฏิเสธ ระบบจึงพักการเรียกทั่วไปไว้ก่อน (เติม/โอนเครดิตไม่ถูกพัก) - error เขียนให้คนอ่าน มีรายละเอียดของคำขอนั้นๆ และอาจถูกแก้คำ
- code ไม่เปลี่ยน ใช้แยกกรณีในโค้ดและใช้ค้นหาต้นตอ
- fix คือสิ่งที่ควรทำต่อ
คำขอไม่ถูกต้อง
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-INPUT-001 | 400 | body ไม่ใช่ JSON ที่อ่านได้ | ตรวจว่าส่ง Content-Type: application/json และ body เป็น JSON ที่ถูกต้อง |
FSB-INPUT-002 | 400 | ไม่ได้ส่งค่าที่จำเป็น | ดูชื่อฟิลด์ที่ขาดในข้อความ แล้วส่งมาให้ครบ |
FSB-INPUT-003 | 400 | ค่าที่ส่งมาผิดรูปแบบหรืออยู่นอกช่วงที่รับได้ | ดูชนิดและช่วงค่าที่รับได้ในเอกสารของเส้นนั้น |
FSB-INPUT-004 | 400 | ช่วงวันที่ผิดรูปแบบ กว้างเกินไป หรือ from อยู่หลัง to | ใช้รูปแบบ YYYY-MM-DD และให้ from ไม่เกิน to |
การเข้าใช้ระบบนี้
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-AUTH-001 | 401 | ไม่ได้แนบ API key หรือ session ที่ใช้ได้ | แนบ X-API-Key หรือ Authorization: Bearer <access token> |
FSB-AUTH-002 | 401 | ชื่อผู้ใช้หรือรหัสผ่านของระบบนี้ไม่ถูกต้อง | ตรวจชื่อผู้ใช้และรหัสผ่าน |
FSB-AUTH-003 | 401 | session หมดอายุหรือถูกยกเลิกแล้ว | เรียก /auth/refresh ด้วย refresh token หรือเข้าสู่ระบบใหม่ |
FSB-AUTH-004 | 403 | สิทธิ์ไม่พอสำหรับงานนี้ | ใช้ token ที่มีสิทธิ์ระดับที่ระบุในข้อความ หรือขอให้แอดมินเพิ่มสิทธิ์ |
FSB-AUTH-005 | 401 | บัญชีนี้เปิดยืนยันตัวตนสองชั้นไว้ ต้องส่งรหัส 6 หลักมาด้วย | ส่ง code จากแอป authenticator มาในคำขอเดียวกัน |
FSB-AUTH-006 | 401 | รหัสยืนยันสองชั้นไม่ถูกต้อง | ขอรหัสใหม่จากแอป และตรวจว่าเวลาบนเครื่องตรงกับเวลาจริง |
FSB-AUTH-007 | 401 | รหัสถูกต้องแต่หมดอายุไปแล้ว | ใช้รหัสปัจจุบันจากแอป อย่ากรอกทิ้งไว้นานก่อนกดยืนยัน |
บัญชีพอร์ทัลและ Token
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-TOKEN-001 | 400 | ยังไม่มี JWT ของบัญชีพอร์ทัลที่จะใช้ | เรียก POST /login เพื่อล็อกอินบัญชีพอร์ทัลก่อน |
FSB-TOKEN-002 | 401 | JWT ของบัญชีพอร์ทัลหมดอายุและต่ออายุอัตโนมัติไม่ได้ | ล็อกอินบัญชีพอร์ทัลใหม่ หรือเก็บรหัสไว้เพื่อให้ระบบต่อให้เอง |
FSB-TOKEN-003 | 403 | บัญชีพอร์ทัลนี้ถูกปิดใช้งานไว้ในระบบ | เปิดใช้งานที่หน้าสถานะล็อกอิน หรือเลือกบัญชีอื่น |
FSB-TOKEN-004 | 403 | บัญชีที่ล็อกอินอยู่ไม่มีสิทธิ์สลับไปยังบัญชีที่ขอ | เลือกบัญชีจากรายการที่สลับได้ หรือให้ผู้ดูแลพอร์ทัลเปิดสิทธิ์ให้ |
FSB-TOKEN-005 | 400 | บัญชีที่ขอสลับไปไม่ใช่บัญชีเอเย่นต์ | สมาชิกไม่ใช่บัญชีที่สลับเข้าไปได้ ให้ระบุด้วย customerId แทน |
FSB-TOKEN-006 | 409 | พอร์ทัลให้สลับบัญชีได้ครั้งเดียวต่อการล็อกอิน และครั้งนี้ใช้ไปแล้ว | เก็บรหัสพอร์ทัลไว้เพื่อให้ระบบล็อกอินใหม่ให้เอง หรือล็อกอินใหม่เอง |
เครดิต
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-CREDIT-001 | 503 | อ่านเครดิตปัจจุบันไม่สำเร็จ จึงไม่ทำรายการต่อ | ลองใหม่อีกครั้ง — ระบบยกเลิกไว้แทนที่จะเดา เพราะเดาผิดคือเขียนทับเครดิตลูกค้า |
FSB-CREDIT-002 | 400 | ไม่พบลูกค้ารายนี้ในสายงานของ agent ที่ระบุ | ตรวจ agentCustomerId ว่าเป็นต้นสายของลูกค้าคนนี้จริง |
FSB-CREDIT-003 | 409 | boardtotal ที่ส่งมาไม่ตรงกับยอดจริงที่พอร์ทัล | อ่านยอดล่าสุดด้วย /getcredit แล้วส่งมาใหม่ หรือไม่ต้องส่ง boardtotal ถ้าไม่ต้องการให้ตรวจ |
FSB-CREDIT-004 | 409 | cashbalance ที่ส่งมาไม่ตรงกับยอดจริงที่พอร์ทัล | อ่านยอดล่าสุดแล้วส่งมาใหม่ หรือไม่ต้องส่งถ้าไม่ต้องการให้ตรวจ |
FSB-CREDIT-005 | 400 | เครดิตคงเหลือไม่พอสำหรับจำนวนที่ขอดึงออก | ลดจำนวนลง หรือถ้าติดที่เครดิตที่ผูกกับบิลค้าง ให้ส่ง skipBetCreditCheck: true เมื่อยอมรับผลได้ |
FSB-CREDIT-006 | 400 | amount ต้องมากกว่า 0 | เส้นเติมและเส้นดึงออกใช้จำนวนบวกทั้งคู่ ทิศทางกำหนดด้วยเส้นที่เรียก ไม่ใช่ด้วยเครื่องหมาย |
สมาชิก
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-MEMBER-001 | 404 | ไม่พบสมาชิกรายนี้ | ตรวจ customerId — ใช้ /getcredit หรือ /report/directory เพื่อดูรายชื่อที่มีจริง |
FSB-MEMBER-002 | 409 | ชื่อผู้ใช้นี้ถูกใช้ไปแล้ว | เปลี่ยนชื่อผู้ใช้ หรือให้ระบบสุ่มต่อท้ายให้ |
รายงาน
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-REPORT-001 | 400 | ช่วงวันที่กว้างเกินกว่าที่พอร์ทัลตอบได้ | แบ่งเป็นช่วงสั้นลง หรือใช้เส้น /history/* ที่อ่านจากข้อมูลที่เก็บไว้ |
FSB-REPORT-002 | 404 | ยังไม่เคยเก็บข้อมูลของช่วงวันที่นี้ไว้ | ใช้ /history/coverage ดูว่ามีวันไหนบ้าง หรือเปิดดูรายงานวันนั้นหนึ่งครั้งเพื่อให้ระบบเก็บ |
Gateway
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-GW-001 | 400 | path ที่ส่งมาไม่ใช่ path เปล่า — เป็น URL เต็ม ชื่อโฮสต์ หรือมี ".." | ส่งเฉพาะ path เช่น "/report/winloss" |
FSB-GW-002 | 403 | เส้นนี้ไม่อนุญาตให้เรียกผ่าน gateway | ใช้เส้นเฉพาะของระบบที่ทำงานเดียวกัน ดูชื่อเส้นในข้อความ |
FSB-GW-003 | 400 | method ที่ส่งมาไม่รองรับ | ใช้ GET, POST, PUT, PATCH หรือ DELETE |
พอร์ทัลปลายทาง
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-PORTAL-001 | 503 | พอร์ทัลตอบกลับเป็นข้อผิดพลาด | ดู details และ upstreamStatus ในคำตอบ — เป็นข้อความจากพอร์ทัลโดยตรง |
FSB-PORTAL-002 | 503 | พอร์ทัลไม่ตอบภายในเวลาที่กำหนด | ลองใหม่อีกครั้ง ถ้าเกิดต่อเนื่องแปลว่าพอร์ทัลกำลังมีปัญหา |
FSB-PORTAL-003 | 429 | พอร์ทัลปฏิเสธเพราะเรียกถี่เกินไป และลองซ้ำให้แล้วยังไม่ผ่าน | ลดความถี่ในการเรียกลง |
การจำกัดอัตราและ IP
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-LIMIT-001 | 429 | เรียกถี่เกินขีดจำกัดของระบบนี้ | รอสักครู่แล้วลองใหม่ ดู Retry-After ในหัวข้อคำตอบ |
FSB-LIMIT-002 | 403 | ที่อยู่ IP นี้ไม่อยู่ในรายการที่อนุญาต | เรียกจาก IP ที่อยู่ใน allow list หรือให้แอดมินเพิ่ม IP นี้เข้าไป |
FSB-LIMIT-003 | 403 | ที่อยู่ IP นี้ถูกบล็อกไว้ | ติดต่อแอดมินเพื่อปลดบล็อก |
ระบบ
| Code | HTTP | หมายถึง | แก้ยังไง |
|---|---|---|---|
FSB-SYS-001 | 501 | ฟีเจอร์นี้ต้องใช้ MongoDB แต่ยังไม่ได้ตั้งค่าไว้ | ตั้งค่า MONGO_URI แล้วเริ่มเซิร์ฟเวอร์ใหม่ |
FSB-SYS-002 | 500 | ข้อผิดพลาดภายในระบบ | แจ้งรหัสนี้พร้อมเวลาที่เกิดให้ผู้ดูแล — รายละเอียดอยู่ในบันทึกของเซิร์ฟเวอร์ |
POST Win/Loss Report
Description
รายงานแพ้/ชนะ แยกตามดาวน์ไลน์และผลิตภัณฑ์
คืนยอดรวมของทุกคอลัมน์มาให้ในฟิลด์ totals
⚠️ เรื่องวันที่ — สำคัญที่สุด
-
ส่งวันที่เป็น "YYYY-MM-DD" ธรรมดา เช่น
"2026-07-29"เท่านั้น - เซิร์ฟเวอร์แปลงเป็น epoch ให้เอง โดยใช้ เที่ยงคืน UTC-4 ซึ่งเป็นเส้นแบ่งวัน (win/lost date) ของ upstream
- ห้ามคำนวณ epoch เอง — ถ้าใช้ UTC midnight หรือ GMT+7 ยอดจะเหลื่อมไปทั้งวัน
-
เว้น
toไว้ = รายงานวันเดียว, เว้นทั้งคู่ = วันนี้
Request Body
{
"customerId": 9000001,
"requesterId": 9000001,
"from": "2026-07-01",
"to": "2026-07-29",
"filters": [1, 3, 4, 2, 5, 6],
"simple": false,
"useUsername": "optional"
}
-
requesterId— เว้นไว้ได้ ค่าเริ่มต้นเท่ากับcustomerId. เวลา drill-down เข้าดาวน์ไลน์ ให้ เปลี่ยนแค่ customerId แล้วคง requesterId เป็นบัญชีที่ล็อกอิน -
filters— 1 = กีฬา, 2 = คาสิโน, 3 = เกม (4-6 upstream รับแต่ไม่เคยคืนข้อมูล) เว้นไว้ = ครบทุกตัว -
simple: true— ยุบชั้น Agent/MA/SMA เหลือชุดตัวเลขเดียว พร้อมฟิลด์payout
Response Example
{
"success": true,
"customerId": 9000001,
"from": "2026-07-29",
"to": "2026-07-29",
"mode": "detailed",
"count": 27,
"data": [
{
"displayName": "Efootball",
"filter": 1,
"customerId": 9100008,
"accountId": "abc78",
"turnover": 69540,
"playerWinloss": -69540,
"maTotal": 521.55,
"smaTotal": 55214.76,
"grossCommission": 521.55,
"companyWinloss": 13803.69
}
],
"totals": { "turnover": 750000, "companyWinloss": 18420.55 }
}
POST Dashboard
Description
รวม 9 เส้น dashboard ของ upstream ให้เหลือ request เดียว ยิงขนานฝั่งเซิร์ฟเวอร์ — ออกแบบมาให้เรียกซ้ำเป็นรอบ (realtime)
Request Body
{
"accountId": "abc",
"useUsername": "optional"
}
accountId คือ username ที่ได้จาก
/report/directory (เช่น abc, xyz, qrs)
Response Example
{
"success": true,
"fetchedAt": "2026-07-29T05:20:11Z",
"data": {
"totalOutstanding": 356175.68,
"remainingCredit": 0,
"usedCredit": 50000000,
"players": { "online": 73, "open": 35152 },
"monthToDate": {
"previous": 610000.15,
"current": 830000.60,
"trend": { "value": 1.0648, "display": true }
},
"inactivePlayers": [ { "accountId": "abc8810069", "betCredit": 0.43 } ]
},
"errors": null
}
ถ้ามีบางเส้นล้ม จะไม่ทำให้ทั้ง request พัง — คีย์นั้นจะหายไปจาก
data แล้วไปโผล่ใน errors แทน
ให้ tile ที่เหลือยังแสดงผลได้ (จะได้ 503
ก็ต่อเมื่อล้มทั้งหมด ซึ่งมักแปลว่า token หมดอายุ)
POST Metrics
Description
time series แพ้/ชนะรายวัน แยกตามกลุ่มผลิตภัณฑ์ ใช้วาดกราฟ
Request Body
{
"accountId": "abc",
"filters": [1, 2, 3]
}
คีย์ของ data เป็นชื่อกลุ่มภาษาไทย (กีฬา / คาสิโน /
เกม) แต่ละค่ามี current และ
lastYear เป็น array ของ
{ labels: [epoch, epoch], value }
Response Example
{
"accountId": "xyz59",
"data": {
"กีฬา": [
{
"productType": 1,
"current": [
{
"labels": [
1785988800000,
1785988800000
],
"value": 0
},
{
"labels": [
1786075200000,
1786075200000
],
"value": 0
},
"... เหลืออีก 5 รายการ"
],
"lastYear": [
{
"labels": [
1754452800000,
1754452800000
],
"value": 0
},
{
"labels": [
1754539200000,
1754539200000
],
"value": 0
},
"... เหลืออีก 5 รายการ"
]
},
{
"productType": 2,
"current": [
{
"labels": [
1785988800000,
1785988800000
],
"value": 0
},
{
"labels": [
1786075200000,
1786075200000
],
"value": 0
},
"... เหลืออีก 5 รายการ"
],
"lastYear": [
{
"labels": [
1754452800000,
1754452800000
],
"value": 0
},
{
"labels": [
1754539200000,
1754539200000
],
"value": 0
},
"... เหลืออีก 5 รายการ"
]
},
"... เหลืออีก 1 รายการ"
],
"คาสิโน": [
{
"productType": 1,
"current": [
{
"labels": [
1782100800000,
1782619200000
],
"value": 0
},
{
"labels": [
1782705600000,
1783224000000
],
"value": 0
},
"... เหลืออีก 6 รายการ"
],
"lastYear": [
{
"labels": [
1750564800000,
1751083200000
],
"value": 0
},
{
"labels": [
1751169600000,
1751688000000
],
"value": 0
},
"... เหลืออีก 6 รายการ"
]
},
{
"productType": 2,
"current": [
{
"labels": [
1782100800000,
1782619200000
],
"value": 0
},
{
"labels": [
1782705600000,
1783224000000
],
"value": 0
},
"... เหลืออีก 6 รายการ"
],
"lastYear": [
{
"labels": [
1750564800000,
1751083200000
],
"value": 0
},
{
"labels": [
1751169600000,
1751688000000
],
"value": 0
},
"... เหลืออีก 6 รายการ"
]
},
"... เหลืออีก 1 รายการ"
],
"เกม": [
{
"productType": 1,
"current": [
{
"labels": [
1772337600000,
1774929600000
],
"value": 0
},
{
"labels": [
1775016000000,
1777521600000
],
"value": 0
},
"... เหลืออีก 4 รายการ"
],
"lastYear": [
{
"labels": [
1740801600000,
1743393600000
],
"value": 0
},
{
"labels": [
1743480000000,
1745985600000
],
"value": 0
},
"... เหลืออีก 4 รายการ"
]
},
{
"productType": 2,
"current": [
{
"labels": [
... (ตัดให้สั้น)
POST Statement
Description
งบรายวันแยกตามผลิตภัณฑ์ พร้อมรายการโอนเครดิตในช่วงเดียวกัน ดึงมาคู่กันเพราะพอร์ทัลใช้คู่กันเสมอ
Request Body
{
"customerId": 9000001,
"from": "2026-07-22",
"to": "2026-07-29"
}
Response Example
{
"data": {
"statement": [
{
"date": 1784779200000,
"type": 99,
"product": 1,
"betting": 273139.75,
"commission": -18654.55,
"cashBalance": 254485.2,
"totalBalance": -152000.69
}
],
"transfers": [
{
"winLostDate": 1784779200000,
"transDesc": "To Customer:abc88(...)",
"amount": -0.74,
"transDate": 1784810147857
}
]
}
}
แถว type: 100 เป็นแถวยอดยกมา ซึ่ง
product, betting,
commission จะเป็น null
POST Calendar
Description
ข้อมูลประกอบตัวเลือกวันที่ — วันไหนมีบิล และวันไหน upstream ปิดยอดแล้ว คืนมาเป็น "YYYY-MM-DD" ทั้งหมด ไม่ต้องแปลง epoch เอง
Request Body
{
"customerId": 9000001,
"from": "2026-07-01",
"to": "2026-07-29",
"useUsername": "optional"
}
customerId จำเป็น — ส่งเป็น request body ตรง ๆ
ไม่ต้องห่อด้วย data (บล็อก data
ด้านล่างเป็น รูปแบบ response ที่ได้กลับมา)
Response Example
{
"data": {
"datesWithBet": ["2026-07-28", "2026-07-29"],
"settledDays": {
"2026-07-27": 1,
"2026-07-28": 1,
"2026-07-29": 0
}
}
}
settledDays: 1 = ปิดยอดแล้ว,
0 = ยังไม่ปิด — วันที่ยังไม่ปิด
ตัวเลขในรายงานยังเปลี่ยนได้ ควรเตือนผู้ใช้ก่อนเอายอดไปใช้จริง
POST Settled Bets
Description
บิลรายใบที่เคลียร์แล้ว แยกตามผลิตภัณฑ์ (sports, eSports, games,
liveCasino, raceTote) พร้อมจำนวนบิลต่อผลิตภัณฑ์ในฟิลด์
counts
Request Body
{
"customerId": 9100002,
"from": "2026-07-28",
"language": "1"
}
โครงสร้างบิลลึกและต่างกันตามผลิตภัณฑ์ จึงส่งผ่านตามที่ upstream คืนมาโดยไม่ปรับรูป
Response Example
{
"count": 0,
"counts": {
"eSports": 0,
"games": 0,
"liveCasino": 0,
"raceTote": 0,
"sports": 0
},
"customerId": 9100010,
"data": {
"eSports": [],
"games": [],
"liveCasino": [],
"raceTote": [],
"sports": []
},
"from": "2026-08-01",
"groups": null,
"rows": null,
"success": true,
"to": "2026-08-13",
"totals": {},
"usedUsername": "agentdemo168"
}
POST Running Bets
Description
บิลของลูกค้าที่ ยังไม่จบ / รอผล (running) ต่อ
customerId รายคน — เรียกพอร์ทัล
/betlist/Running ไม่มีช่วงวันที่ (บิลที่ยังไม่ settle
ไม่มีวันจบ) ต่างจาก
/report/outstanding
ที่เป็นยอด exposure รวมทั้งเอเย่นต์ ไม่แยกรายลูกค้า
ต้องเรียกด้วยเอเย่นต์ที่เป็น เจ้าของสมาชิกคนนั้น
(ตัวเดียวกับตอนสมัคร / ค่า usedUsername ที่ได้จาก
/create) มิฉะนั้นพอร์ทัลตอบ 403
Request Body
{
"customerId": 37485351,
"filters": [1],
"language": "1",
"useUsername": "zum45agent"
}
customerId จำเป็น · filters ไม่ใส่ =
ค่าเริ่มต้นชุดเดียวกับ Settled Bets · accountId /
useUsername ใช้เลือก/สลับบัญชีเหมือนเส้นรายงานอื่น
Response Example
{
"success": true,
"customerId": 37485351,
"count": 0,
"counts": {
"eSports": 0,
"games": 0,
"liveCasino": 0,
"raceTote": 0,
"sports": 0
},
"totals": {},
"rows": null,
"groups": null,
"data": {
"eSports": [],
"games": [],
"liveCasino": [],
"raceTote": [],
"sports": []
},
"usedUsername": "zum45agent"
}
POST Breakdown
Description
รวม billing breakdown และ cancelled-campaign breakdown ไว้ในคำขอเดียว
Request Body
{
"customerId": 9000001,
"from": "2026-07-01",
"to": "2026-07-29",
"useUsername": "optional"
}
customerId จำเป็น
หมายเหตุ
ทั้งสองเส้นคืน array ว่างในทุก response ที่เก็บได้จริง จึงส่งผ่านแบบไม่กำหนดชนิดข้อมูล แทนที่จะเดาโครงสร้างขึ้นมาเอง
Response Example
{
"customerId": 9100010,
"data": {
"billing": [],
"cancelled": []
},
"errors": null,
"from": "2026-08-01",
"success": true,
"to": "2026-08-13",
"usedUsername": "agentdemo168"
}
POST Outstanding Bets
Description
ความเสี่ยงที่ยังไม่เคลียร์ แยกรายแมตช์ — มีเงินวางอยู่ข้างไหนเท่าไหร่ ทั้งเต็มเวลาและครึ่งแรก เรียงจากยอดค้างมากสุดลงมาให้แล้ว เพราะเป็นหน้าจอความเสี่ยง แมตช์ที่เงินเยอะสุดต้องเห็นก่อน
Request Body
{
"monitored": false,
"language": "1"
}
ทุกฟิลด์เว้นได้หมด — monitored: true
จะกรองเหลือเฉพาะแมตช์ที่ตั้งเฝ้าไว้
Response Example
{
"count": 112,
"liveCount": 78,
"totalExposure": 175286.18,
"data": [
{
"leagueName": "Sydney Super Cup",
"sportName": "Football",
"homeTeamName": "Sydney FC",
"awayTeamName": "Tottenham Hotspur",
"isLive": false,
"liveHomeScore": 0,
"showTime": 1785318300000,
"fullTime": {
"home": 4.8, "away": 0,
"hdpTotal": 4.8, "hdpTotalCount": 1,
"over": 1205, "under": 0,
"ouTotal": 1205, "ouTotalCount": 2
},
"halfTime": { "hdpTotal": 0, "ouTotal": 0 }
}
]
}
totalExposure =
fullTime.hdpTotal + fullTime.ouTotal + halfTime.hdpTotal +
halfTime.ouTotal รวมทุกแมตช์ —
คำนวณมาให้แล้วไม่ต้องบวกเอง
POST Credit Downline
Description
ฐานะเครดิตของตัวเองพร้อมของดาวน์ไลน์ทุกคน รวม 3 เส้นเป็นคำขอเดียว
(creditbalance/self,
creditbalance/children, BalanceInfo)
Request Body
{
"customerId": 9000001,
"accountStatus": 0
}
accountStatus — 0 = ทุกสถานะ,
1 = เฉพาะที่ใช้งานอยู่ ค่าเริ่มต้นคือ 0
(ส่ง 0 มาเองก็ยังเป็น 0 ไม่ถูกแทนที่)
Response Example
{
"data": {
"self": {
"customerId": 9000001,
"customerInfo": { "accountId": "abc", "accountStatus": 1 },
"credit": { "givenCredit": 50000000, "usedCredit": 50000000 },
"balance": { "totalBalance": -441911.43 },
"outstanding": { "totalOutstanding": 299643.86 },
"betCredit": 4723897218.11
},
"children": [ "... 34 รายการ รูปแบบเดียวกับ self" ]
}
}
POST IP Groups
Description
ยอดเดิมพันรวมตาม IP ต้นทาง — ใช้จับกรณีหลายสมาชิกยิงจาก IP เดียวกัน ซึ่งเป็นเหตุผลที่รายงานนี้มีอยู่ เรียงตามจำนวนสมาชิกมากสุดก่อน แล้วค่อยตามยอดเดิมพัน
Request Body
{
"filter": 1
}
filter เป็น ตัวเลขเดี่ยว
(1 = กีฬา, 2 = คาสิโน, 3 = เกม) ไม่ใช่ array แบบเส้นอื่น
เพราะ upstream รับ filters แค่ค่าเดียวที่เส้นนี้
ค่าเริ่มต้นคือ 1
Response Example
{
"count": 421,
"sharedIPs": 1,
"totalStake": 500000,
"data": [
{
"ip": "203.0.113.58",
"memberCount": 2,
"betCount": 6,
"memberTotal": 7500,
"agentTotal": 0,
"maTotal": 0,
"smaTotal": 6000
}
]
}
sharedIPs = จำนวน IP ที่มี
memberCount > 1 — คือตัวเลขที่ควรดูก่อน
ส่วน IP ที่มีสมาชิกคนเดียวเป็นเรื่องปกติ
POST Directory
Description
รายชื่อบัญชีที่ใช้ทำรายงานได้ — ดาวน์ไลน์ชั้นแรก และบัญชี agent ที่สลับไปได้
Request Body
{
"useUsername": "optional"
}
ไม่มีพารามิเตอร์ที่จำเป็น — ทำงานบนโทเคนของล็อกอินเอง
useUsername ใส่เมื่อต้องการระบุบัญชีที่ใช้โทเคน
(บล็อก data ด้านล่างเป็น รูปแบบ response)
Response Example
{
"data": {
"linkedAgents": [
{ "customerId": 9000001, "username": "abc", "isLocked": false }
],
"children": [
{ "customerId": 9100005, "accountId": "abc08", "firstName": "P.BOY" }
]
}
}
เรียกเส้นนี้ก่อนเสมอ — username
ใช้เป็น accountId ของ dashboard/metrics ได้ตรงๆ
และ customerId ใช้เป็น requesterId
ของรายงานได้ตรงๆ จึงไม่ต้องให้ผู้ใช้พิมพ์เอง