POST Login

/login

Description

ใช้สำหรับ login เข้าระบบและรับ JWT Token สำหรับใช้งาน API อื่นๆ

Request Body

Parameter Type Required Description
username String Required Username สำหรับ login
password String Required Password สำหรับ login
pin String Required PIN 6 หลัก

Request Example

{
  "username": "bot0860",
  "password": "aa112233++",
  "pin": "123456"
}

Response Example

{
  "jwt": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "username": "bot0860",
  "message": "Login สำเร็จสำหรับ bot0860"
}

POST Login Multiple Accounts

/login/multiple

Description

Login หลาย accounts พร้อมกันและบันทึก JWT Token ของแต่ละ account

Request Body

ส่งเป็น object ที่มี key เป็นชื่อที่ต้องการ และ value เป็นข้อมูล login

Request Example

{
  "a": {
    "username": "zds88zzq",
    "password": "aa112233++",
    "pin": "445566"
  },
  "b": {
    "username": "zds88zzr",
    "password": "bb112233++",
    "pin": "556677"
  }
}

GET JWT List

/jwt/list

Description

แสดงรายการ JWT Token ทั้งหมดที่มีในระบบ

Response Example

{
  "success": true,
  "count": 2,
  "jwtTokens": [
    {
      "username": "bot0860",
      "hasToken": true,
      "tokenPreview": "***...last20chars",
      "timestamp": "2024-06-06T10:30:00.000Z"
    }
  ],
  "message": "พบ JWT Token 2 รายการ"
}

POST JWT Refresh

/jwt/refresh

Description

ต่ออายุ access token ทันทีผ่าน OIDC refresh grant โดยไม่ต้องกรอกรหัสผ่านใหม่

ปกติระบบต่ออายุให้อัตโนมัติอยู่แล้วเมื่อ token ใกล้หมด เส้นนี้มีไว้สำหรับกรณีที่ automation ครอบคลุมไม่ได้ — อยากได้เวลาเต็มชั่วโมงก่อนเริ่มงานยาว หรืออยากรู้เดี๋ยวนี้ว่า refresh token ยังใช้ได้ไหม แทนที่จะไปรู้ตอนรายงานล้ม

Request Body

{ "username": "sbobetonline168" }

เว้น body ว่างได้ = ต่ออายุ token ตัวแรกที่เจอในระบบ

Response Example

{
  "success": true,
  "usedUsername": "sbobetonline168",
  "expiresAt": "2026-07-29T12:56:35Z",
  "message": "ต่ออายุ token ของ sbobetonline168 เรียบร้อย"
}

ได้ 401 พร้อม refreshable: false เมื่อ refresh token ใช้ไม่ได้แล้ว — กรณีนั้นต้องล็อกอินใหม่เท่านั้น

DELETE JWT Clear

/jwt/clear

Description

ลบ JWT Token ออกจากระบบ

Request Body (Optional)

Parameter Type Required Description
username String Optional ระบุเพื่อลบเฉพาะ JWT ของ username นั้น (ถ้าไม่ระบุจะลบทั้งหมด)

POST Create Member

/create

Description

สร้าง Member account ใหม่

Request Body

Parameter Type Required Description
userName String Required Username ที่ต้องการสร้าง
password String Optional Password (default: "123123123Zz@@")
firstName String Optional ชื่อ (default: "ABC")
lastName String Optional นามสกุล (default: "Xenter")
useUsername String Optional ระบุ username ที่ต้องการใช้ JWT

Request Example

{
  "userName": "testuser001",
  "password": "Test@123456",
  "firstName": "John",
  "lastName": "Doe",
  "useUsername": "bot0860"
}

POST Create Bulk Members

/create/bulk

Description

สร้าง Member accounts หลายๆ account พร้อมกัน (สูงสุด 100 accounts)

Request Example

{
  "userNames": ["zds88zz001", "zds88zz002", "zds88zz003"],
  "password": "123123123Zz@@",
  "firstName": "ABC",
  "lastName": "Xenter",
  "useUsername": "bot0860"
}

POST Change Password

/changepassword

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": 35688234,
  "accountId": "zds88zz960",
  "password": "@@123123Zz",
  "useUsername": "bot0860"
}

POST Suspend Account

/suspend

Description

ล็อค/ปลดล็อค account

Request Body

Parameter Type Required Description
customerId Number Required Customer ID ที่ต้องการ suspend
isSuspended Boolean Required true = ล็อค, false = ปลดล็อค

Request Example

{
  "customerId": 35688231,
  "isSuspended": true,
  "useUsername": "bot0860"
}

POST Suspend Bulk Accounts

/suspend/bulk

Description

ล็อค/ปลดล็อคหลาย accounts พร้อมกัน (สูงสุด 50 accounts)

Request Example

{
  "customers": [35688231, 35688232, 35688233],
  "isSuspended": true,
  "useUsername": "bot0860"
}

POST Topup Credit

/topup/credit

Description

เติมเครดิตให้กับ Member พร้อมตรวจสอบ boardtotal ก่อนเติม

Request Body

รองรับหลายรูปแบบ:

  • เติมให้ 1 คน
  • เติมให้หลายคนจำนวนเท่ากัน
  • เติมให้หลายคนจำนวนต่างกัน
Parameter Type Required Description
agentCustomerId Number Optional Agent Customer ID สำหรับดึงข้อมูลเครดิตก่อน/หลังเติม
boardtotal Number Optional ตรวจสอบ boardtotal ก่อนเติม (ถ้าไม่ตรงจะไม่เติม)

Request Examples

รูปแบบหลัก: เติมผ่าน customers array

{
  "agentCustomerId": 34185327,
  "customers": [
    // { "customerId": 35576317, "amount": 160 },
    {
      "customerId": 34837990,
      "amount": 100,
      "boardtotal": 46887
    }
  ],
  "useUsername": "zds88zzq"
}

รูปแบบที่ 1: เติมให้ 1 คน

{
  "customerId": 34837990,
  "amount": 100,
  "boardtotal": 1000,
  "accountId": "bot0860",
  "agentCustomerId": 34185327,
  "useUsername": "zds88zzq"
}

รูปแบบที่ 2: เติมให้หลายคนจำนวนเท่ากัน

{
  "customerId": "34837990,34837991,34837992",
  "amount": 100,
  "agentCustomerId": 34185327
}

รูปแบบที่ 3: เติมให้หลายคนจำนวนต่างกัน

{
  "customers": [
    {
      "customerId": 34837990,
      "amount": 130,
      "boardtotal": 1000,
      "accountId": "bot0860",
      "agentCustomerId": 34185327
    },
    {
      "customerId": 34837991,
      "amount": 105
    }
  ],
  "agentCustomerId": 34185327,
  "useUsername": "zds88zzq"
}

Response Example

{
  "success": true,
  "verify": {
    "status": "ok"
  },
  "message": "เติมเครดิต: สำเร็จ 2 accounts, ล้มเหลว 0 accounts, รวม 235 บาท",
  "usedUsername": "zds88zzq",
  "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": 34837990,
      "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

/getcredit

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": 34185327,
  "customerId": 34837990,
  "useUsername": "zds88zzq"
}

Response Example

{
  "success": true,
  "agentCustomerId": 34185327,
  "customerId": 34837990,
  "usedUsername": "zds88zzq",
  "data": {
    "customerId": 34837990,
    "credit": {
      "givenCredit": 1000
    },
    "balance": {
      "totalBalance": 500
    },
    "betCredit": 1500,
    "customerInfo": {
      "accountId": "zds88zz001",
      "isSuspended": false
    }
  },
  "message": "ดึงข้อมูลเครดิตสำเร็จสำหรับ Customer ID: 34837990"
}

POST Transfer (Withdraw)

/transfer

Description

ถอนเงินจาก Member โดยระบบจะ:

  • อ่านยอดกระดานและ cash balance ของ agent ก่อนถอนทุกครั้ง (ปิดได้ด้วย skipCashBalanceCheck สำหรับกระดานที่ไม่มี Cash Balance)
  • ตรวจสอบ bet credit ว่าเพียงพอหรือไม่
  • ตัดจาก Credit ก่อน หากไม่พอจะตัดจาก Balance (Transfer)
  • อ่านยอดลูกค้าซ้ำหลังถอน แล้วเทียบว่าลดลงเท่ายอดที่ถอนพอดี — ผลอยู่ในฟิลด์ verify
  • ยอดไม่ตรง (ก่อนหรือหลัง) → บันทึกเป็นรายการค้างตรวจสอบ ดูได้ที่ GET /reviews

Request Body

Parameter Type Required Description
customerId Number Required Customer ID ที่ต้องการถอนเงิน
amount Number Required จำนวนเงินที่ต้องการถอน (ส่งเป็นค่าบวก)
boardtotal Number Optional ยอดกระดานที่คาดไว้ — ถ้าไม่ตรงกับค่าจริงจะไม่ถอน และบันทึกเป็นรายการค้างตรวจสอบ
cashbalance Number Optional cash balance ที่คาดไว้ — ถ้าไม่ตรงกับค่าจริงจะไม่ถอน และบันทึกเป็นรายการค้างตรวจสอบ
skipCashBalanceCheck Boolean Optional สำหรับกระดานที่ไม่มี Cash Balance เท่านั้น — ปกติระบบอ่าน cash balance ของ agent ก่อนถอนทุกครั้ง ตั้งเป็น true เพื่อข้ามการอ่านนี้ (default: false)
skipBetCreditCheck Boolean Optional ข้ามการเช็ค bet credit (default: false)
agentCustomerId Number Recommended Agent Customer ID สำหรับเช็ค bet credit (แนะนำให้ใส่เสมอ)
useUsername String Optional ระบุ username ที่ต้องการใช้ JWT

Request Example

{
  "agentCustomerId": 34185327,
  "customerId": 34837990,
  "amount": 1,
  "boardtotal": 46887,
  "cashbalance": 174,
  "useUsername": "zds88zzq" // optional
}

Response Example

{
  "success": true,
  "verify": {
    "status": "ok"
  },
  "message": "Transfer สำเร็จสำหรับ Customer ID 35688234",
  "customerId": 35688234,
  "amount": 10,
  "usedUsername": "bot0860",
  "breakdown": {
    "totalWithdrawn": 10,
    "fromCredit": 10,
    "fromBalance": 0
  },
  "balanceBefore": {
    "credit": 15,
    "totalBalance": 100,
    "betCredit": 115,
    "boardtotal": 909015.0,
    "cashBalance": -19039187.83
  },
  "balanceAfter": {
    "credit": 5,
    "totalBalance": 100,
    "betCredit": 105,
    "boardtotal": 909015.0,
    "cashBalance": -19039177.83
  }
}

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

/reviews?status=pending

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": 34837990,
      "amount": 100,
      "expected": 450,
      "actual": 460,
      "useUsername": "zds88zzq",
      "note": "ยอดลูกค้าหลังถอนไม่ตรงกับที่คาด รอพนักงานตรวจสอบ",
      "status": "pending"
    }
  ]
}

หมายเหตุ

  • kind: deposit (เติม) | withdraw (ถอน)
  • stage: before = ไม่ตรงก่อนทำรายการ (ยังไม่มีเงินเคลื่อนไหว) | after = ไม่ตรงหลังทำรายการ (เงินขยับไปแล้ว ต้องตรวจสอบก่อนทำรายการซ้ำ)
  • field: ยอดที่ไม่ตรง — boardtotal | cashbalance | credit
  • ต้องตั้งค่า MongoDB จึงจะเก็บคิวนี้ได้

POST Resolve Review

/reviews/resolve

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"
}

รหัสข้อผิดพลาด

ทุกคำตอบที่ล้มเหลวจะมี code · error · fix

หน้าตาของคำตอบเมื่อล้มเหลว

{
  "success": false,
  "code": "FSB-CREDIT-003",
  "error": "boardtotal ไม่ตรง: ระบบ 4143777974.86 แต่ใน agent 4143777474.86",
  "fix": "อ่านยอดล่าสุดด้วย /getcredit แล้วส่งมาใหม่ ..."
}
  • error เขียนให้คนอ่าน มีรายละเอียดของคำขอนั้นๆ และอาจถูกแก้คำ
  • code ไม่เปลี่ยน ใช้แยกกรณีในโค้ดและใช้ค้นหาต้นตอ
  • fix คือสิ่งที่ควรทำต่อ

คำขอไม่ถูกต้อง

CodeHTTPหมายถึงแก้ยังไง
FSB-INPUT-001400body ไม่ใช่ JSON ที่อ่านได้ตรวจว่าส่ง Content-Type: application/json และ body เป็น JSON ที่ถูกต้อง
FSB-INPUT-002400ไม่ได้ส่งค่าที่จำเป็นดูชื่อฟิลด์ที่ขาดในข้อความ แล้วส่งมาให้ครบ
FSB-INPUT-003400ค่าที่ส่งมาผิดรูปแบบหรืออยู่นอกช่วงที่รับได้ดูชนิดและช่วงค่าที่รับได้ในเอกสารของเส้นนั้น
FSB-INPUT-004400ช่วงวันที่ผิดรูปแบบ กว้างเกินไป หรือ from อยู่หลัง toใช้รูปแบบ YYYY-MM-DD และให้ from ไม่เกิน to

การเข้าใช้ระบบนี้

CodeHTTPหมายถึงแก้ยังไง
FSB-AUTH-001401ไม่ได้แนบ API key หรือ session ที่ใช้ได้แนบ X-API-Key หรือ Authorization: Bearer <access token>
FSB-AUTH-002401ชื่อผู้ใช้หรือรหัสผ่านของระบบนี้ไม่ถูกต้องตรวจชื่อผู้ใช้และรหัสผ่าน
FSB-AUTH-003401session หมดอายุหรือถูกยกเลิกแล้วเรียก /auth/refresh ด้วย refresh token หรือเข้าสู่ระบบใหม่
FSB-AUTH-004403สิทธิ์ไม่พอสำหรับงานนี้ใช้ token ที่มีสิทธิ์ระดับที่ระบุในข้อความ หรือขอให้แอดมินเพิ่มสิทธิ์
FSB-AUTH-005401บัญชีนี้เปิดยืนยันตัวตนสองชั้นไว้ ต้องส่งรหัส 6 หลักมาด้วยส่ง code จากแอป authenticator มาในคำขอเดียวกัน
FSB-AUTH-006401รหัสยืนยันสองชั้นไม่ถูกต้องขอรหัสใหม่จากแอป และตรวจว่าเวลาบนเครื่องตรงกับเวลาจริง
FSB-AUTH-007401รหัสถูกต้องแต่หมดอายุไปแล้วใช้รหัสปัจจุบันจากแอป อย่ากรอกทิ้งไว้นานก่อนกดยืนยัน

บัญชีพอร์ทัลและ Token

CodeHTTPหมายถึงแก้ยังไง
FSB-TOKEN-001400ยังไม่มี JWT ของบัญชีพอร์ทัลที่จะใช้เรียก POST /login เพื่อล็อกอินบัญชีพอร์ทัลก่อน
FSB-TOKEN-002401JWT ของบัญชีพอร์ทัลหมดอายุและต่ออายุอัตโนมัติไม่ได้ล็อกอินบัญชีพอร์ทัลใหม่ หรือเก็บรหัสไว้เพื่อให้ระบบต่อให้เอง
FSB-TOKEN-003403บัญชีพอร์ทัลนี้ถูกปิดใช้งานไว้ในระบบเปิดใช้งานที่หน้าสถานะล็อกอิน หรือเลือกบัญชีอื่น
FSB-TOKEN-004403บัญชีที่ล็อกอินอยู่ไม่มีสิทธิ์สลับไปยังบัญชีที่ขอเลือกบัญชีจากรายการที่สลับได้ หรือให้ผู้ดูแลพอร์ทัลเปิดสิทธิ์ให้
FSB-TOKEN-005400บัญชีที่ขอสลับไปไม่ใช่บัญชีเอเย่นต์สมาชิกไม่ใช่บัญชีที่สลับเข้าไปได้ ให้ระบุด้วย customerId แทน
FSB-TOKEN-006409พอร์ทัลให้สลับบัญชีได้ครั้งเดียวต่อการล็อกอิน และครั้งนี้ใช้ไปแล้วเก็บรหัสพอร์ทัลไว้เพื่อให้ระบบล็อกอินใหม่ให้เอง หรือล็อกอินใหม่เอง

เครดิต

CodeHTTPหมายถึงแก้ยังไง
FSB-CREDIT-001502อ่านเครดิตปัจจุบันไม่สำเร็จ จึงไม่ทำรายการต่อลองใหม่อีกครั้ง — ระบบยกเลิกไว้แทนที่จะเดา เพราะเดาผิดคือเขียนทับเครดิตลูกค้า
FSB-CREDIT-002400ไม่พบลูกค้ารายนี้ในสายงานของ agent ที่ระบุตรวจ agentCustomerId ว่าเป็นต้นสายของลูกค้าคนนี้จริง
FSB-CREDIT-003409boardtotal ที่ส่งมาไม่ตรงกับยอดจริงที่พอร์ทัลอ่านยอดล่าสุดด้วย /getcredit แล้วส่งมาใหม่ หรือไม่ต้องส่ง boardtotal ถ้าไม่ต้องการให้ตรวจ
FSB-CREDIT-004409cashbalance ที่ส่งมาไม่ตรงกับยอดจริงที่พอร์ทัลอ่านยอดล่าสุดแล้วส่งมาใหม่ หรือไม่ต้องส่งถ้าไม่ต้องการให้ตรวจ
FSB-CREDIT-005400เครดิตคงเหลือไม่พอสำหรับจำนวนที่ขอดึงออกลดจำนวนลง หรือถ้าติดที่เครดิตที่ผูกกับบิลค้าง ให้ส่ง skipBetCreditCheck: true เมื่อยอมรับผลได้
FSB-CREDIT-006400amount ต้องมากกว่า 0เส้นเติมและเส้นดึงออกใช้จำนวนบวกทั้งคู่ ทิศทางกำหนดด้วยเส้นที่เรียก ไม่ใช่ด้วยเครื่องหมาย

สมาชิก

CodeHTTPหมายถึงแก้ยังไง
FSB-MEMBER-001404ไม่พบสมาชิกรายนี้ตรวจ customerId — ใช้ /getcredit หรือ /report/directory เพื่อดูรายชื่อที่มีจริง
FSB-MEMBER-002409ชื่อผู้ใช้นี้ถูกใช้ไปแล้วเปลี่ยนชื่อผู้ใช้ หรือให้ระบบสุ่มต่อท้ายให้

รายงาน

CodeHTTPหมายถึงแก้ยังไง
FSB-REPORT-001400ช่วงวันที่กว้างเกินกว่าที่พอร์ทัลตอบได้แบ่งเป็นช่วงสั้นลง หรือใช้เส้น /history/* ที่อ่านจากข้อมูลที่เก็บไว้
FSB-REPORT-002404ยังไม่เคยเก็บข้อมูลของช่วงวันที่นี้ไว้ใช้ /history/coverage ดูว่ามีวันไหนบ้าง หรือเปิดดูรายงานวันนั้นหนึ่งครั้งเพื่อให้ระบบเก็บ

Gateway

CodeHTTPหมายถึงแก้ยังไง
FSB-GW-001400path ที่ส่งมาไม่ใช่ path เปล่า — เป็น URL เต็ม ชื่อโฮสต์ หรือมี ".."ส่งเฉพาะ path เช่น "/report/winloss"
FSB-GW-002403เส้นนี้ไม่อนุญาตให้เรียกผ่าน gatewayใช้เส้นเฉพาะของระบบที่ทำงานเดียวกัน ดูชื่อเส้นในข้อความ
FSB-GW-003400method ที่ส่งมาไม่รองรับใช้ GET, POST, PUT, PATCH หรือ DELETE

พอร์ทัลปลายทาง

CodeHTTPหมายถึงแก้ยังไง
FSB-PORTAL-001502พอร์ทัลตอบกลับเป็นข้อผิดพลาดดู details และ upstreamStatus ในคำตอบ — เป็นข้อความจากพอร์ทัลโดยตรง
FSB-PORTAL-002504พอร์ทัลไม่ตอบภายในเวลาที่กำหนดลองใหม่อีกครั้ง ถ้าเกิดต่อเนื่องแปลว่าพอร์ทัลกำลังมีปัญหา
FSB-PORTAL-003429พอร์ทัลปฏิเสธเพราะเรียกถี่เกินไป และลองซ้ำให้แล้วยังไม่ผ่านลดความถี่ในการเรียกลง

การจำกัดอัตราและ IP

CodeHTTPหมายถึงแก้ยังไง
FSB-LIMIT-001429เรียกถี่เกินขีดจำกัดของระบบนี้รอสักครู่แล้วลองใหม่ ดู Retry-After ในหัวข้อคำตอบ
FSB-LIMIT-002403ที่อยู่ IP นี้ไม่อยู่ในรายการที่อนุญาตเรียกจาก IP ที่อยู่ใน allow list หรือให้แอดมินเพิ่ม IP นี้เข้าไป
FSB-LIMIT-003403ที่อยู่ IP นี้ถูกบล็อกไว้ติดต่อแอดมินเพื่อปลดบล็อก

ระบบ

CodeHTTPหมายถึงแก้ยังไง
FSB-SYS-001501ฟีเจอร์นี้ต้องใช้ MongoDB แต่ยังไม่ได้ตั้งค่าไว้ตั้งค่า MONGO_URI แล้วเริ่มเซิร์ฟเวอร์ใหม่
FSB-SYS-002500ข้อผิดพลาดภายในระบบแจ้งรหัสนี้พร้อมเวลาที่เกิดให้ผู้ดูแล — รายละเอียดอยู่ในบันทึกของเซิร์ฟเวอร์

POST Win/Loss Report

/report/winloss

Description

รายงานแพ้/ชนะ แยกตามดาวน์ไลน์และผลิตภัณฑ์ คืนยอดรวมของทุกคอลัมน์มาให้ในฟิลด์ totals

⚠️ เรื่องวันที่ — สำคัญที่สุด

  • ส่งวันที่เป็น "YYYY-MM-DD" ธรรมดา เช่น "2026-07-29" เท่านั้น
  • เซิร์ฟเวอร์แปลงเป็น epoch ให้เอง โดยใช้ เที่ยงคืน UTC-4 ซึ่งเป็นเส้นแบ่งวัน (win/lost date) ของ upstream
  • ห้ามคำนวณ epoch เอง — ถ้าใช้ UTC midnight หรือ GMT+7 ยอดจะเหลื่อมไปทั้งวัน
  • เว้น to ไว้ = รายงานวันเดียว, เว้นทั้งคู่ = วันนี้

Request Body

{
  "customerId": 2286064,
  "requesterId": 2286064,
  "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": 2286064,
  "from": "2026-07-29",
  "to": "2026-07-29",
  "mode": "detailed",
  "count": 27,
  "data": [
    {
      "displayName": "Efootball",
      "filter": 1,
      "customerId": 11126203,
      "accountId": "zds78",
      "turnover": 69540,
      "playerWinloss": -69540,
      "maTotal": 521.55,
      "smaTotal": 55214.76,
      "grossCommission": 521.55,
      "companyWinloss": 13803.69
    }
  ],
  "totals": { "turnover": 2851429, "companyWinloss": 18420.55 }
}

POST Dashboard

/report/dashboard

Description

รวม 9 เส้น dashboard ของ upstream ให้เหลือ request เดียว ยิงขนานฝั่งเซิร์ฟเวอร์ — ออกแบบมาให้เรียกซ้ำเป็นรอบ (realtime)

Request Body

{
  "accountId": "zds",
  "useUsername": "optional"
}

accountId คือ username ที่ได้จาก /report/directory (เช่น zds, zla, zum)

Response Example

{
  "success": true,
  "fetchedAt": "2026-07-29T05:20:11Z",
  "data": {
    "totalOutstanding": 356175.68,
    "remainingCredit": 0,
    "usedCredit": 580000000,
    "players": { "online": 73, "open": 35152 },
    "monthToDate": {
      "previous": 6275622.85,
      "current": 12957815.21,
      "trend": { "value": 1.0648, "display": true }
    },
    "inactivePlayers": [ { "accountId": "zds8810069", "betCredit": 0.43 } ]
  },
  "errors": null
}

ถ้ามีบางเส้นล้ม จะไม่ทำให้ทั้ง request พัง — คีย์นั้นจะหายไปจาก data แล้วไปโผล่ใน errors แทน ให้ tile ที่เหลือยังแสดงผลได้ (จะได้ 502 ก็ต่อเมื่อล้มทั้งหมด ซึ่งมักแปลว่า token หมดอายุ)

POST Metrics

/report/metrics

Description

time series แพ้/ชนะรายวัน แยกตามกลุ่มผลิตภัณฑ์ ใช้วาดกราฟ

Request Body

{
  "accountId": "zds",
  "filters": [1, 2, 3]
}

คีย์ของ data เป็นชื่อกลุ่มภาษาไทย (กีฬา / คาสิโน / เกม) แต่ละค่ามี current และ lastYear เป็น array ของ { labels: [epoch, epoch], value }

POST Statement

/report/statement

Description

งบรายวันแยกตามผลิตภัณฑ์ พร้อมรายการโอนเครดิตในช่วงเดียวกัน ดึงมาคู่กันเพราะพอร์ทัลใช้คู่กันเสมอ

Request Body

{
  "customerId": 2286064,
  "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:zds88(...)",
        "amount": -0.74,
        "transDate": 1784810147857
      }
    ]
  }
}

แถว type: 100 เป็นแถวยอดยกมา ซึ่ง product, betting, commission จะเป็น null

POST Calendar

/report/calendar

Description

ข้อมูลประกอบตัวเลือกวันที่ — วันไหนมีบิล และวันไหน upstream ปิดยอดแล้ว คืนมาเป็น "YYYY-MM-DD" ทั้งหมด ไม่ต้องแปลง epoch เอง

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

/report/bets

Description

บิลรายใบที่เคลียร์แล้ว แยกตามผลิตภัณฑ์ (sports, eSports, games, liveCasino, raceTote) พร้อมจำนวนบิลต่อผลิตภัณฑ์ในฟิลด์ counts

Request Body

{
  "customerId": 25783301,
  "from": "2026-07-28",
  "language": "1"
}

โครงสร้างบิลลึกและต่างกันตามผลิตภัณฑ์ จึงส่งผ่านตามที่ upstream คืนมาโดยไม่ปรับรูป

POST Breakdown

/report/breakdown

Description

รวม billing breakdown และ cancelled-campaign breakdown ไว้ในคำขอเดียว

หมายเหตุ

ทั้งสองเส้นคืน array ว่างในทุก response ที่เก็บได้จริง จึงส่งผ่านแบบไม่กำหนดชนิดข้อมูล แทนที่จะเดาโครงสร้างขึ้นมาเอง

POST Outstanding Bets

/report/outstanding

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

/report/credit

Description

ฐานะเครดิตของตัวเองพร้อมของดาวน์ไลน์ทุกคน รวม 3 เส้นเป็นคำขอเดียว (creditbalance/self, creditbalance/children, BalanceInfo)

Request Body

{
  "customerId": 2286064,
  "accountStatus": 0
}

accountStatus0 = ทุกสถานะ, 1 = เฉพาะที่ใช้งานอยู่ ค่าเริ่มต้นคือ 0 (ส่ง 0 มาเองก็ยังเป็น 0 ไม่ถูกแทนที่)

Response Example

{
  "data": {
    "self": {
      "customerId": 2286064,
      "customerInfo": { "accountId": "zds", "accountStatus": 1 },
      "credit": { "givenCredit": 580000000, "usedCredit": 580000000 },
      "balance": { "totalBalance": -441911.43 },
      "outstanding": { "totalOutstanding": 299643.86 },
      "betCredit": 4723897218.11
    },
    "children": [ "... 34 รายการ รูปแบบเดียวกับ self" ]
  }
}

POST IP Groups

/report/ipgroups

Description

ยอดเดิมพันรวมตาม IP ต้นทาง — ใช้จับกรณีหลายสมาชิกยิงจาก IP เดียวกัน ซึ่งเป็นเหตุผลที่รายงานนี้มีอยู่ เรียงตามจำนวนสมาชิกมากสุดก่อน แล้วค่อยตามยอดเดิมพัน

Request Body

{
  "filter": 1
}

filter เป็น ตัวเลขเดี่ยว (1 = กีฬา, 2 = คาสิโน, 3 = เกม) ไม่ใช่ array แบบเส้นอื่น เพราะ upstream รับ filters แค่ค่าเดียวที่เส้นนี้ ค่าเริ่มต้นคือ 1

Response Example

{
  "count": 421,
  "sharedIPs": 1,
  "totalStake": 1088714,
  "data": [
    {
      "ip": "223.205.251.58",
      "memberCount": 2,
      "betCount": 6,
      "memberTotal": 7500,
      "agentTotal": 0,
      "maTotal": 0,
      "smaTotal": 6000
    }
  ]
}

sharedIPs = จำนวน IP ที่มี memberCount > 1 — คือตัวเลขที่ควรดูก่อน ส่วน IP ที่มีสมาชิกคนเดียวเป็นเรื่องปกติ

POST Directory

/report/directory

Description

รายชื่อบัญชีที่ใช้ทำรายงานได้ — ดาวน์ไลน์ชั้นแรก และบัญชี agent ที่สลับไปได้

Response Example

{
  "data": {
    "linkedAgents": [
      { "customerId": 2286064, "username": "zds", "isLocked": false }
    ],
    "children": [
      { "customerId": 3014933, "accountId": "zds08", "firstName": "P.BOY" }
    ]
  }
}

เรียกเส้นนี้ก่อนเสมอusername ใช้เป็น accountId ของ dashboard/metrics ได้ตรงๆ และ customerId ใช้เป็น requesterId ของรายงานได้ตรงๆ จึงไม่ต้องให้ผู้ใช้พิมพ์เอง