📚 Pok Api v1 · 2026-05-15 (v10)
← กลับ

REST API Documentation

Sections: Authentication · Play Link · Wallet (Topup/Withdraw) · Rooms / Game History · 🌐 Downline · 🎁 Affiliate · Rank · 🔑 Special API

🔐 Authentication

ทุก endpoint (ยกเว้น register/login/health) ต้องส่ง auth ผ่าน header ตัวใดตัวหนึ่ง:

Authorization: Bearer <USER_TOKEN>
หรือ
X-API-Key: <API_KEY>

หรือใส่ใน body ก็ได้: { "token": "..." } / { "apiKey": "..." }

POST/api/v1/auth/registerAuth: Required
สร้างลูกค้า (customer) ของตัวเอง — ส่งแค่ suffix ระบบจะประกบ caller.id ให้อัตโนมัติ
เช่น caller = aham, ส่ง username:"555" → final = aham555

เงื่อนไข:
  • caller ต้องเป็น super_senior / senior / master / agent — เฉพาะตำแหน่งที่มีลูกค้าตรงได้
  • parent ของลูกค้าใหม่ = caller เสมอ (ไม่รับ parameter parentId)
  • role ถูกบังคับเป็น customer เสมอ (ไม่รับ parameter role) — สร้างลูกสายใช้หน้าหลังบ้าน
  • input username = suffix อะไรก็ได้ ระบบ sanitize เป็น a-z 0-9 _ + ประกบ caller.id ข้างหน้า
  • ถ้า input เริ่มด้วย caller.id อยู่แล้ว → ใช้ตามนั้น (กัน prefix ซ้ำ)
  • final username ยาว 2-16 ตัว
Body / Response
// ── Example 1 — caller=aham (master) ส่งแค่ suffix
// Request
{
  "username": "555",             // suffix อะไรก็ได้
  "password": "1234",
  "displayName": "ลูกค้าหนึ่ง",   // (optional — default = final username)
  "refCode": "A8K2X9MP"           // ★ optional — รหัสแนะนำ (ต้องอยู่สายเดียวกัน — ข้ามสายไม่ได้)
}

// Response 200
{
  "ok": true,
  "user": {
    "id": "aham555",
    "role": "customer",
    "parentId": "aham",
    "refCode": "X3Y9Z2P1",        // ★ รหัสแนะนำของลูกค้าใหม่ (auto-gen)
    "affWallet": 0,
    "referrerId": "some_user",    // ★ ผู้แนะนำ (null ถ้าไม่ได้ใส่ refCode)
    ...
  },
  "token": "abc123...",            // login token ของลูกค้าใหม่
  "finalUsername": "aham555",      // ★ username สุดท้ายที่ระบบสร้างให้
  "referralBound": true,           // ★ ผูก refCode สำเร็จไหม (false = code ไม่ถูก/ตัวเอง)
  "playUrl": "https://play.pokstack.com/?autotoken=abc123...",
  "referralCode": "X3Y9Z2P1",      // ★ รหัสของลูกค้าใหม่ (เอาไปแสดงให้เขา)
  "referralUrl": "https://play.pokstack.com/?ref=X3Y9Z2P1"  // ★ ลิงค์ชวนเพื่อน
}

// ── Example 2 — caller=aham, ส่งชื่อเต็ม (เริ่มด้วย "aham" อยู่แล้ว)
// Request
{ "username": "aham_vip01", "password": "1234" }

// Response 200 — ใช้ "aham_vip01" ตามที่ส่ง (ไม่ประกบซ้ำ)
{ "ok": true, "user": { "id": "aham_vip01", ... }, "finalUsername": "aham_vip01" }

// ── Error 403 — ผู้แนะนำอยู่คนละสาย (HARD BLOCK — ข้ามสายไม่ได้)
// HTTP 403 Forbidden
{
  "ok": false,
  "error": "❌ ผู้แนะนำ \"นาย A\" อยู่คนละสาย — แนะนำได้เฉพาะสมาชิกในสายของตัวเองเท่านั้น",
  "crossBranch": true,
  "referrerName": "นาย A",
  "refCode": "A8K2X9MP"
}
// → ต้องเปลี่ยน refCode เป็นของคนในสายเดียวกัน หรือเอา refCode ออก (สมัครโดยไม่ผูกผู้แนะนำ)

// ── Error 400 — ไม่พบรหัสแนะนำ
{ "ok": false, "error": "❌ ไม่พบรหัสแนะนำ \"A8K2X9MP\"" }

// ── Error 403 — caller ไม่มีสิทธิ์สร้างลูกค้า
{ "ok": false, "error": "role \"...\" ไม่สามารถสร้างลูกค้าผ่าน API ได้ — ต้องเป็น super_senior/senior/master/agent" }

// ── Error 400 — final username ยาวเกิน 16
{ "ok": false, "error": "username สุดท้าย \"aham_long_suffix\" ยาว 17 ตัว — เกิน 16 (suffix สั้นกว่านี้)" }
POST/api/v1/auth/loginAuth: Public
Login ด้วย username/password — ได้ token + playUrl สำหรับ redirect เข้าเล่น
returnUrl (optional) — URL ที่จะ redirect กลับเมื่อผู้เล่นกด "ออกจากระบบ" ในเกม
   ตัวอย่าง: agent ส่ง "returnUrl": "https://agent-site.com/lobby" → ลูกค้ากด logout → เด้งกลับเว็บ agent ทันที
   รับเฉพาะ http:// หรือ https:// — ความยาว ≤ 2000 ตัว
Body / Response
// Request
{
  "username": "user01",
  "password": "1234",
  "returnUrl": "https://agent-site.com/lobby"   // ★ optional — URL กลับเมื่อ logout
}

// Response 200
{
  "ok": true,
  "user": {
    "id":"user01", "displayName":"...", "role":"customer", "points":0,
    "refCode": "X3Y9Z2P1",   // ★ รหัสแนะนำของผู้ login
    "affWallet": 35.50,       // ★ ยอดคอม Aff รอถอน
    "referrerId": null,       // ★ ผู้แนะนำ (ถ้ามี)
    ...
  },
  "token": "abc123...",
  "playUrl": "https://play.pokstack.com/?autotoken=abc123...",
  "returnUrl": "https://agent-site.com/lobby",
  "referralCode": "X3Y9Z2P1",                                  // ★
  "referralUrl": "https://play.pokstack.com/?ref=X3Y9Z2P1"     // ★ ลิงค์ชวนเพื่อน
}
POST/api/v1/auth/meAuth: Required
ข้อมูลของผู้ใช้ปัจจุบัน (ที่ token/apiKey ผูกอยู่) — รวม refCode, affWallet, referrerId
Response
{
  "ok": true,
  "user": {
    "id": "user01", "displayName": "...", "role": "customer",
    "points": 1500, "bonusPoints": 0, "holdPct": 0,
    "refCode": "X3Y9Z2P1",      // ★ รหัสแนะนำเพื่อน (auto-gen ลำดับแรกที่เรียก)
    "affWallet": 35.50,          // ★ ยอดคอม Aff รอถอน
    "referrerId": null,           // ★ id ผู้แนะนำ (null = ไม่มี)
    ...
  },
  "authVia": "token"   // "token" | "apiKey"
}
POST/api/v1/auth/logoutAuth: Required
ทำลาย token (revoke) — สำหรับ apiKey ใช้ /api/downline/regen-api-key แทน
POST/api/v1/auth/play-linkAuth: Required (agent+)
Agent สร้างลิงก์ login ครั้งเดียวให้ลูกค้า (อายุ default 10 นาที) — ส่งลิงก์ให้ลูกค้าคลิกเข้าเล่นได้เลย ไม่ต้องกรอก password
returnUrl (optional) — URL ปลายทางตอนลูกค้ากด logout
   agent ส่ง URL ของหน้าตัวเอง → ลูกค้ากดออก → เด้งกลับ
Body / Response
// Request
{
  "customerId": "user01",
  "ttlMinutes": 15,
  "returnUrl": "https://agent-site.com/lobby"   // ★ optional
}

// Response 200
{
  "ok": true,
  "url": "https://play.pokstack.com/?autotoken=XXXX...",
  "expiresAt": 1762291800000,
  "ttlMinutes": 15,
  "customerId": "user01",
  "returnUrl": "https://agent-site.com/lobby"   // ★ echo (null ถ้าไม่ส่ง)
}

// ลูกค้าคลิก url → frontend exchange autotoken → real token → entered game
// ★ returnUrl จะถูกบันทึกในเซสชันลูกค้า — กดออก/logout → redirect กลับไป returnUrl
POST/api/v1/auth/exchange-autotokenAuth: Public
(ปกติ frontend จะเรียกอัตโนมัติ) แลก autotoken → real user token

💰 Wallet (Topup / Withdraw)

POST/api/v1/wallet/balanceAuth: Required
เช็คยอด — ตัวเอง / ใครก็ได้ใต้สาย (รวม downline + customers ในสายตัวเองทั้งหมด)
read-only — เช็คได้ลึกถึงสาย แต่เติมได้แค่ลูกค้าตรง
Body
// Request
{ "userId": "user01" }   // optional — ถ้าไม่ใส่ = ของตัวเอง

// Response 200
{ "ok": true, "userId": "user01", "points": 1500, "bonus": 0 }
POST/api/v1/wallet/agent-creditAuth: Required
เช็คเครดิตที่คุณเหลือไว้สำหรับเติมให้ลูกค้า (agent_credit) + ยอดเครดิตรวมในมือลูกค้าตรง
ก่อนเรียก /wallet/topup เช็คก่อนว่ามี credit พอเติมหรือเปล่า — และดูภาพรวมเงินสะพัดที่จ่ายให้ลูกค้าตัวเองทั้งหมด
  • super_senior / senior / master / agent → คืนเลขจริงจาก agent_credit
  • customercanTopupCustomer:false (ไม่มีสิทธิ์เติม)
  • customers.totalPoints = ผลรวม points ของลูกค้าตรงทุกคน — เฉพาะลูกค้าตัวเอง ไม่รวมลูกค้าใต้สายลึก
Body / Response
// Request (no body needed — ใช้ token/apiKey identify caller)
{}

// Response 200 — caller=agent01 (master)
{
  "ok": true,
  "userId": "agent01",
  "role": "master",
  "agentCredit": 12500,           // ★ เครดิตคงเหลือสำหรับเติมลูกค้า
  "canTopupCustomer": true,
  "customers": {                  // ★ ยอดรวมในลูกค้าตรงของตัวเอง
    "count": 8,                   //   จำนวนลูกค้าตรงทั้งหมด
    "totalPoints": 24500,         //   รวมเครดิตเล่นในมือลูกค้า
    "totalBonus": 1200,           //   รวมโบนัส
    "totalCombined": 25700        //   points + bonus
  }
}

// Response 200 — caller เป็น customer
{
  "ok": true,
  "userId": "user01",
  "role": "customer",
  "agentCredit": 0,
  "canTopupCustomer": false,
  "customers": { "count": 0, "totalPoints": 0, "totalBonus": 0, "totalCombined": 0 }
}
POST/api/v1/wallet/topupAuth: Required (agent only)
เติมเครดิตให้ ลูกค้าตรง ของคุณเท่านั้น (target.parent_id = caller.id + target ต้องเป็น customer)
หัก agent_credit ของคุณ
ข้อจำกัด:
  • เติมให้ลูกค้าของ ลูกสายตัวเอง ไม่ได้ — แต่ละ agent เติมให้ลูกค้าตัวเองเท่านั้น
  • เติม ลูกสาย (super_senior/senior/master/agent) ผ่าน API ไม่ได้ — ใช้หน้าหลังบ้าน
Body / Response
// Request (caller = agent01)
{ "userId": "agent01_user1", "amount": 1000, "reason": "เติมประจำวัน" }

// Response 200
{
  "ok": true,
  "userId": "agent01_user1",
  "before": 500, "after": 1500, "delta": 1000, "mode": "topup",
  "fromCredit": { "id": "agent01", "before": 5000, "after": 4000, "unlimited": false }
}

// Error 403 — target เป็น downline
{ "ok": false, "error": "target เป็น senior (downline) — เติมเครดิตให้ดาวไลน์ผ่าน API ไม่ได้ ใช้หน้า /downline.html เท่านั้น" }

// Error 403 — ไม่ใช่ลูกค้าตรง
{ "ok": false, "error": "เติมได้เฉพาะลูกค้าตรงของคุณเท่านั้น (target.parent_id ต้อง = caller.id)" }
POST/api/v1/wallet/withdrawAuth: Required (agent only)
ถอนเครดิตจาก ลูกค้าตรง ของคุณ — เครดิตคืนเข้ากระเป๋าเครดิตของคุณ
⚠️ ถ้าลูกค้ากำลังเล่นเกมอยู่ ยังเล่นไม่จบ จะถอนเครดิตไม่ได้ — ต้องรอจบเกม/รอบก่อน
Body / Response
// Request
{ "userId": "agent01_user1", "amount": 200, "reason": "ขอคืน" }

// Response — สำเร็จ
{
  "ok": true, "userId": "agent01_user1",
  "before": 1500, "after": 1300, "delta": -200, "mode": "withdraw",
  "fromCredit": { "id": "agent01", "before": 4000, "after": 4200, "unlimited": false }
}

// ★ ลูกค้ากำลังเล่นเกมอยู่ — ถอนไม่ได้
{
  "ok": false,
  "error": "⚠️ ผู้เล่น \"agent01_user1\" อยู่ในระหว่างเล่นเกม — ไม่สามารถถอนเครดิตได้ กรุณารอจบเกม/รอบก่อน",
  "reason": "in_active_game"
}
// → รอลูกค้าเล่นเกมจบรอบ หรือออกจากห้องก่อน แล้วลองใหม่
POST/api/v1/wallet/transactionsAuth: Required
รายการธุรกรรมของ user (ทั้งเติม/ถอน/ค่าต๋ง/รับ-เสียเกม)
Body
// Request
{ "userId": "user01", "limit": 30, "offset": 0, "fromTs": 1762000000000, "toTs": 1762999999999 }

// Response 200
{ "ok": true, "userId": "user01", "transactions": [...], "total": 142 }

🃏 Rooms / Game History

POST/api/v1/rooms/myAuth: Required
รายการห้องที่ user เคยเล่นทั้งหมด (paginated)
Body / Response
// Request
{
  "userId": "user01",       // optional (default = ตัวเอง)
  "limit": 30, "offset": 0,
  "fromTs": ..., "toTs": ...,
  "gameType": "dummy"       // optional: 'dummy' | 'pokdeng'
}

// Response 200
{
  "ok": true, "userId": "user01", "total": 87,
  "rooms": [
    { "id": 41, "ts": 1762293000000, "roomId": "AB1234",
      "gameType": "dummy", "rateName": "5/10/15",
      "playerIds": ["user01","user02","..."],
      "winnerId": "user01", "winType": "darkKnock", "rounds": 3 }
  ]
}
POST/api/v1/rooms/paymentsAuth: Required
รายละเอียดการหักเงิน/ได้เงินทุก event ในห้องนั้นๆ (เรียงตามเวลา)
Body / Response
// Request
{ "roomId": "AB1234", "userId": "user01" }

// Response 200
{
  "ok": true, "roomId": "AB1234", "total": 24,
  "payments": [
    { "id": 1234, "ts": 1762293010000,
      "eventKind": "ตีเต็ม", "reason": "ตีเต็ม (ตอง 5)",
      "payerId": "user01", "receiverId": null, "amount": 30 },
    { "id": 1235, "ts": 1762293020000,
      "eventKind": "เกิดหัว", "reason": "เกิดหัว Q♠",
      "payerId": "user01", "receiverId": "user02", "amount": 10 }
  ]
}
POST/api/v1/rooms/payments/summaryAuth: Required
สรุปรับ-จ่ายของแต่ละผู้เล่นในห้อง (sum per user)
Response
// Response 200
{ "ok": true, "roomId": "AB1234", "summary": [
  { "userId": "user01", "displayName": "ลูกค้า1", "received": 850, "paid": 90, "net": 760 },
  { "userId": "user02", "displayName": "ลูกค้า2", "received": 120, "paid": 460, "net": -340 }
]}

🌐 Downline

API ที่ตรงกับฟีเจอร์ในหน้า Downline (👤 สมาชิก / 🌐 ลูกสาย / 📊 รายงาน / ⚡ ทะลุบอท)

POST/api/v1/downline/listAuth: Required
ลูกตรงทั้งหมด (ลูกค้า + ลูกสาย) ของคุณ — พร้อมข้อมูล online, aff %, ทะลุบอท ฯลฯ
Response
// Response 200
{
  "ok": true,
  "list": [
    {
      "id": "ahyxhmyixitr",
      "displayName": "นาย A",
      "agentRole": null,              // null = customer; agent/master/senior/super_senior = ลูกสาย
      "holdPct": 0,
      "points": 1500,                 // เครดิตเล่น (เฉพาะ customer)
      "agentCredit": 0,               // เครดิตเติม (เฉพาะลูกสาย)
      "createdAt": 1762293000000,
      "isSuspended": false,
      "online": true,
      "botsOverride": false,          // ⚡ ทะลุบอท (per-customer)
      "affCommissionPct": 5,          // 🎁 % คอม Aff ของ customer (null = default 5%)
      "affWithdrawSchedule": "anytime"
    }
  ]
}
POST/api/v1/downline/customersAuth: Required
👤 เฉพาะลูกค้าตรง — กรองออก agent ออก (เหมาะกับหน้า "สมาชิก")
Response
// Response 200
{ "ok": true, "total": 5, "list": [ /* customers only */ ] }
POST/api/v1/downline/agentsAuth: Required
🌐 เฉพาะลูกสาย — agent/master/senior/super_senior ใต้ตัวเอง
POST/api/v1/downline/treeAuth: Required
🌳 สายทั้งหมด (recursive) — ทุกระดับใต้ตัวเอง
POST/api/v1/downline/dashboardAuth: Required
📊 สรุปจำนวน — ลูกค้า/ลูกสาย ทั้งหมด vs ตรง
Response
{
  "ok": true,
  "counts": {
    "totalSubtree": 47,      // ทั้งสาย (ไม่นับตัวเอง)
    "customers": 38,          // ลูกค้าทั้งสาย
    "agents": 9,              // ลูกสายทั้งหมด
    "directCustomers": 8,     // ลูกค้าตรง
    "directAgents": 2         // ลูกสายตรง
  }
}
POST/api/v1/downline/summaryAuth: Required
💼 สรุปการเงินช่วงเวลา — รายได้/cashback/เครดิตเติม-ลด ของลูกค้าใต้สาย
default = 30 วันย้อนหลัง
Body / Response
// Request
{ "fromTs": 1762000000000, "toTs": 1762999999999 }  // optional

// Response 200
{
  "ok": true,
  "period": { "fromTs": 1762000000000, "toTs": 1762999999999 },
  "income": 12500.75,         // ค่าต๋ง + ซื้อสติกเกอร์/ตัวละคร/ค่าห้อง (gross)
  "cashback": 350.20,          // คืนยอดเสีย
  "eventPayout": 500,          // รางวัล event (ลบจากรายได้สุทธิ)
  "netIncome": 11650.55,       // income − cashback − eventPayout
  "creditTopup": 25000,        // เติมเครดิตให้ลูกตรงรวม
  "creditWithdraw": 8000,      // ลดเครดิตจากลูกตรงรวม
  "creditNet": 17000,          // เติม − ลด
  "customerCount": 38,
  "directCount": 10
}
POST/api/v1/downline/rake-summaryAuth: Required
💰 รายได้ค่าต๋ง (rake) จากลูกค้าใต้สาย — filter โดย kind
Body / Response
// Request
{
  "fromTs": 1762000000000,
  "toTs":   1762999999999,
  "kind": "all"   // all | rake | sticker | character | room_fee
}

// Response 200
{ "ok": true, "kind": "rake", "total": 8420.50, "count": 1234,
  "period": { "fromTs": ..., "toTs": ... } }
POST/api/v1/downline/gamesAuth: Required
🎮 ประวัติเกมของลูกค้าใต้สาย — paginated + filter หลายช่อง
Body / Response
// Request
{
  "fromTs": ..., "toTs": ...,
  "gameType": "dummy",        // optional: dummy | pokdeng | makhos
  "playerId": "user01",        // optional
  "winnerId": "user02",        // optional
  "roomId": "AB12",            // optional (partial match)
  "limit": 30, "offset": 0
}

// Response 200
{
  "ok": true, "total": 142,
  "games": [
    {
      "id": 4711, "ts": 1762293010000, "roomId": "AB1234",
      "gameType": "dummy", "gameMode": "fast", "rateName": "5/10/15",
      "playerIds": ["user01","user02","user03"],
      "winnerId": "user01", "winType": "ตีเต็ม", "rounds": 4
    }
  ]
}
POST/api/v1/downline/payment-logsAuth: Required
📋 Payment logs — เกม + เติม/ลด เครดิต รวมในที่เดียว (paginated)
source: all (default) / game / wallet
Body / Response
// Request
{
  "source": "all",         // all | game | wallet
  "fromTs": ..., "toTs": ...,
  "eventKind": "ตีเต็ม",   // หรือ "topup" / "withdraw" สำหรับเครดิต
  "gameType": "dummy",     // optional
  "roomId": "AB12",        // optional
  "payerId": "...",
  "receiverId": "...",
  "limit": 30, "offset": 0
}

// Response 200 — แต่ละ row มี kind: 'game' หรือ 'wallet'
{
  "ok": true, "total": 89,
  "logs": [
    {
      "kind": "game", "id": "g:1234",
      "ts": 1762293010000, "eventKind": "เกิดหัว",
      "reason": "เกิดหัว Q♠",
      "roomId": "AB1234", "gameType": "dummy",
      "payerId": "user02", "receiverId": "user01",
      "amount": 10
    },
    {
      "kind": "wallet", "id": "w:5678",
      "ts": 1762293000000, "eventKind": "topup",
      "reason": "ฝากผ่านสลิป #240",
      "targetUserId": "user01", "byUser": "ahz",
      "amount": 500
    }
  ]
}
POST/api/v1/downline/set-bot-overrideAuth: Required
เปิด/ปิด "ทะลุบอท" ราย customer — บอทจะเข้าห้องลูกค้าคนนั้นได้
★ ตั้งได้เฉพาะ ลูกค้าตรง (เป็น customer เท่านั้น) ของคุณ
Body / Response
// Request
{ "targetId": "user01", "enabled": true }

// Response 200
{ "ok": true, "targetId": "user01", "botsOverride": true }

// Error 400 — target เป็นลูกสาย ไม่ใช่ลูกค้า
{ "ok": false, "error": "ทะลุบอทใช้ได้กับลูกค้า (customer) เท่านั้น" }

// Error 403 — ไม่ใช่ลูกตรง
{ "ok": false, "error": "ตั้งค่าได้เฉพาะลูกตรง" }
POST/api/v1/downline/suspendAuth: Required (agent+)
🚫 ระงับลูกสาย/ลูกค้า — cascade ทั้งสายใต้ (ลูกของลูกที่ระงับ ก็เข้าเล่นไม่ได้)
Body / Response
// Request
{ "targetId": "agent_a", "reason": "ผิดข้อตกลง" }

// Response 200
{ "ok": true, "affected": 8 }
POST/api/v1/downline/unsuspendAuth: Required (agent+)
ปลดระงับลูกสาย — ต้องไม่มีสายเหนือที่ยังถูกระงับอยู่ ถึงจะปลดได้จริง
Body / Response
// Request
{ "targetId": "agent_a" }

// Response 200
{ "ok": true, "unsuspended": true }
POST/api/v1/me/suspend-statusAuth: Required
เช็คสถานะ suspend ของตัวเอง (รวมถึงถูกล็อคจากสายเหนือ) — ใช้เช็คก่อนเข้าเกม
Response
// Response 200 — ปกติ
{ "ok": true, "suspended": false }

// Response 200 — ถูกระงับ
{ "ok": true, "suspended": true, "by": "agent_a", "reason": "...", "viaAncestor": true }

🎁 Affiliate (ระบบแนะนำเพื่อน)

Flow โดยรวม: ลูกค้า A ได้ refCode auto-gen → ส่งให้เพื่อน B → เว็บผู้ค้าจับ ?ref=XXX → สมัคร auth/register พร้อม refCode → B เล่นเสียค่าต๋ง → ระบบหัก agent_credit ของ Agent (parent ของ A) ตามเปอร์เซ็นต์ → +aff_wallet ของ A → A เรียก /aff/withdraw → +points
ค่าคอม Aff ไม่นับ Tier (ไม่เข้า topups) — ป้องกันโกง tier
agent_credit ไม่พอ → mark pending → เติม credit เมื่อไหร่ ระบบจ่ายอัตโนมัติ

POST/api/v1/aff/meAuth: Required
ดึงข้อมูล Aff ของผู้ใช้ปัจจุบัน — refCode, ยอด, ลิงค์, settings, ประวัติพอสรุป
Response
// Response 200
{
  "ok": true,
  "stats": {
    "refCode": "X3Y9Z2P1",
    "affWallet": 35.50,            // ยอดรอถอน
    "earnedPaid": 120.00,           // คอมที่จ่ายแล้วทั้งหมด
    "earnedPending": 25.00,         // คอมรอจ่าย (agent_credit ไม่พอ)
    "referredCount": 5,             // ชวนสำเร็จกี่คน
    "settings": {
      "commissionPct": 5,           // % ค่าคอม
      "withdrawSchedule": "anytime" // ตารางถอน
    },
    "canWithdraw": { "ok": true }   // ถอนได้ตอนนี้ไหม (ตามตาราง)
  },
  "referralCode": "X3Y9Z2P1",
  "referralUrl": "https://play.pokstack.com/?ref=X3Y9Z2P1"
}
POST/api/v1/aff/check-codeAuth: Required
ตรวจ refCode ก่อนสมัคร — บอกว่าเป็น cross-agent ไหม (preflight ก่อนเรียก register)
Body / Response
// Request
{ "refCode": "A8K2X9MP" }

// Response 200 — เจอ + ในสายเดียวกัน
{
  "ok": true, "found": true,
  "referrerId": "agent_a",
  "referrerName": "นาย A",
  "crossAgent": false,
  "hint": "ในสายเดียวกัน — สร้างได้ปกติ"
}

// Response 200 — เจอแต่ข้ามสาย ★
{
  "ok": true, "found": true,
  "referrerId": "agent_a",
  "referrerName": "นาย A",
  "crossAgent": true,
  "hint": "ผู้แนะนำอยู่ใต้สาย agent อื่น — ถ้าสมัครใต้คุณ คุณจะจ่ายคอมจาก agent_credit ตัวเอง"
}

// Response 200 — ไม่เจอ
{ "ok": true, "found": false }
POST/api/v1/aff/withdrawAuth: Required (customer)
ลูกค้าถอน aff_walletpoints (ตรวจ schedule, atomic)
Body / Response
// Request
{ "amount": 30 }

// Response 200
{
  "ok": true,
  "amount": 30,
  "newAffWallet": 5.50,
  "newPoints": 1030,
  "withdrawalId": 123
}

// Error — ไม่ถึงวันที่ถอน
{ "ok": false, "error": "วันนี้ยังถอนไม่ได้ (ตารางถอน: monthly — วันที่ 1)" }

// Error — ยอดไม่พอ
{ "ok": false, "error": "aff_wallet ไม่พอ (มี 35.50)" }
POST/api/v1/aff/historyAuth: Required
ประวัติคอม + ถอน ของผู้ใช้
Body / Response
// Request
{ "limit": 50 }

// Response
{
  "ok": true,
  "commissions": [
    { "id": 1, "ts": ..., "amount": 5.00, "baseAmount": 100, "pct": 5,
      "referredId": "some_b", "referredName": "นาย B",
      "paid": true, "paidTs": ... }
  ],
  "withdrawals": [
    { "id": 1, "ts": ..., "amount": 30 }
  ]
}
POST/api/v1/aff/referredAuth: Required
รายชื่อคนที่ใช้ refCode ของเรา (พร้อมยอดคอมที่ได้จากแต่ละคน)
Body / Response
// Request
{ "limit": 50 }

// Response
{
  "ok": true,
  "list": [
    { "id": "agent_b", "displayName": "นาย B",
      "createdAt": 1762000000000,
      "earned": 25.50 }
  ]
}
POST/api/v1/aff/agent/summaryAuth: Required (agent only)
เฉพาะ agent — สรุปลูกค้าตัวเองที่ชวนเพื่อน + ยอดคอมจ่ายแล้ว/รอจ่าย
Response
// Response 200
{
  "ok": true,
  "summary": {
    "activeReferrers": 3,        // จำนวนลูกค้าที่ชวนเพื่อนสำเร็จ
    "totalPaid": 250.00,          // คอมที่จ่ายไปแล้วรวม
    "totalPending": 50.00,        // คอมรอจ่ายรวม (เครดิตไม่พอ)
    "customers": [
      {
        "id": "agent_a", "displayName": "นาย A",
        "refCode": "X3Y9Z2P1",
        "referredCount": 2,
        "earnedPaid": 100, "earnedPending": 25,
        "affWallet": 35.50,
        "commissionPct": 5,
        "withdrawSchedule": "anytime"
      }
    ]
  }
}
POST/api/v1/aff/reportAuth: Required
รายงานช่วงเวลา ของผู้ใช้ปัจจุบัน — สรุป + breakdown รายวัน (90 วัน) + top 20 referred (ตามยอดสูงสุด) + ยอดถอนรวม
Body / Response
// Request — fromTs/toTs เป็น Unix ms (optional, default = 30 วันย้อนหลัง)
{
  "fromTs": 1762000000000,   // optional
  "toTs":   1762999999999    // optional
}

// Response 200
{
  "ok": true,
  "role": "user",
  "period": { "from": 1762000000000, "to": 1762999999999 },
  "summary": {
    "totalAmount": 145.50,        // คอมรวม (จ่ายแล้ว+รอจ่าย)
    "paidAmount": 120.00,
    "pendingAmount": 25.50,
    "totalBase": 2910,            // ฐานค่าต๋งรวม
    "eventCount": 28,             // จำนวน event คอม
    "uniqueReferred": 5           // ลูกค้าที่ทำคอมให้ในช่วงนี้
  },
  "daily": [                       // breakdown รายวัน (Bangkok TZ, สูงสุด 90 วัน)
    { "date": "2026-05-09", "events": 5, "amount": 25.50, "paid": 20, "pending": 5.50 },
    { "date": "2026-05-08", "events": 3, "amount": 15.00, "paid": 15, "pending": 0 }
  ],
  "perReferred": [                 // top 20 ลูกค้าที่ทำเงินสูงสุดในช่วงนี้
    {
      "id": "agent_b", "name": "นาย B",
      "events": 12, "amount": 60.00, "paid": 50, "pending": 10
    }
  ],
  "withdrawals": { "total": 100, "count": 3 }    // ยอดถอนรวมในช่วง
}
POST/api/v1/aff/agent/reportAuth: Required (agent only)
รายงานสำหรับ agent — สรุปคอม Aff ที่ ตัวเองจ่าย/ติดค้าง ให้ลูกค้าในสาย (group ตาม agent_id ของคอม)
Body / Response
// Request — fromTs/toTs เป็น Unix ms (optional)
{ "fromTs": 1762000000000, "toTs": 1762999999999 }

// Response 200 — โครงสร้างเหมือน /aff/report แต่ role="agent"
{
  "ok": true,
  "role": "agent",
  "period": { "from": ..., "to": ... },
  "summary": {
    "totalAmount": 850.50,         // คอมที่ตัวเองรับผิดชอบจ่าย (ทั้งหมด)
    "paidAmount": 700,
    "pendingAmount": 150.50,        // ยอดที่ยัง "รอจ่าย" — เครดิตไม่พอ
    "totalBase": 17010,
    "eventCount": 142,
    "uniqueReferred": 8
  },
  "daily": [...],
  "perReferred": [                  // top 20 ลูกค้าที่ตัวเองต้องจ่ายคอมสูงสุด
    { "id": "agent_b", "name": "นาย B", "events": 30, "amount": 200, "paid": 180, "pending": 20 }
  ],
  "withdrawals": null               // ★ agent role ไม่มีตัวเลขถอน (= null)
}
POST/api/v1/aff/agent/set-userAuth: Required (agent only)
เฉพาะ agent — ตั้ง commissionPct + withdrawSchedule per ลูกค้า
Body / Response
// Request
{
  "userId": "agent_a",
  "commissionPct": 7,                    // 0-100 (default 5%)
  "withdrawSchedule": "monthly"          // ตารางถอน (ดูข้างล่าง)
}

// withdrawSchedule values:
//   "anytime"     — ถอนได้ทุกเวลา (default)
//   "daily"       — ถอนได้ทุกวัน
//   "monthly"     — ถอนได้เฉพาะวันที่ 1 ของเดือน
//   "days:1,15"   — ถอนได้เฉพาะวันที่ 1 และ 15 ของเดือน
//   "days:1,10,20" — กำหนดเอง (1-31, คั่น ,)

// Response 200
{ "ok": true, "settings": { "commissionPct": 7, "withdrawSchedule": "monthly" } }

// Error 403 — userId ไม่ใช่ลูกใต้สาย
{ "ok": false, "error": "userId ไม่ได้อยู่ใต้สายของคุณ" }

🏆 Rank

POST/api/v1/rank/meAuth: Required
tier ปัจจุบัน + ยอดเติมรายวัน + tier ถัดไป + reset time (00:00 น. ไทย)
POST/api/v1/rank/leaderboardAuth: Required
Top players ตามยอดเติมรายวัน (default 10)

🔧 Misc

GET/api/v1/healthAuth: Public
เช็คว่า API ทำงาน
GET/api/v1/versionAuth: Public

📋 Permission Matrix (API)

API สร้าง user ได้เฉพาะ "customer" เท่านั้น — สร้างลูกสาย (super_senior/senior/master/agent) ต้องใช้หน้าหลังบ้านเท่านั้น
★ ทุก action ผ่าน API จำกัด เฉพาะลูกค้าตรง ของ caller — เติม/ถอนให้ลูกสายต้องใช้หน้าหลังบ้าน

บทบาท สร้าง user ผ่าน API เติม/ถอนเครดิต ผ่าน API เช็คยอด/ดูข้อมูล play-link ลูกค้า
super_senior ✗ (สร้างลูกสายใช้หน้าหลังบ้าน) ✗ ไม่มีลูกค้าตรง ✓ ใต้สายตัวเอง ✓ ใต้สายตัวเอง
senior ✗ (สร้างลูกสายใช้หน้าหลังบ้าน) ✗ ไม่มีลูกค้าตรง ✓ ใต้สายตัวเอง ✓ ใต้สายตัวเอง
master ✗ (สร้างลูกสายใช้หน้าหลังบ้าน) ✗ ไม่มีลูกค้าตรง ✓ ใต้สายตัวเอง ✓ ใต้สายตัวเอง
agent เฉพาะ customer ของตัวเอง เฉพาะลูกค้าตรงตัวเอง ✓ ลูกค้าตัวเอง ✓ ลูกค้าตัวเอง
customer เฉพาะตัวเอง

หมายเหตุ 1: สร้างลูกสาย (super_senior/senior/master/agent) ทำผ่าน API ไม่ได้เลย — ต้องใช้หน้าหลังบ้าน ➕ สร้างลูกสาย
หมายเหตุ 2: เติมเครดิตให้ ลูกสาย ผ่าน API ไม่ได้ — ใช้หน้าหลังบ้าน → ปุ่มเติม
หมายเหตุ 3: API ส่วนใหญ่ที่เกี่ยวกับ "agent" หมายถึงเฉพาะ agent role (ระดับล่างสุด ที่มีลูกค้าตรง)

📝 cURL Examples

ตัวอย่างด้านล่างใช้ $BASE = URL ของระบบ — ดูได้จากที่อยู่เว็บที่คุณ login เข้ามา (เช่น https://your-domain.com)

Login + Topup ลูกค้า
# 1) Login
curl -X POST "$BASE/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"agent01","password":"xxx"}'
# → { ok:true, token:"AGENT_TOKEN", playUrl:"...", user:{...} }

# 2a) เช็คเครดิตของตัวเอง (เผื่อพอเติมหรือไม่)
curl -X POST "$BASE/api/v1/wallet/agent-credit" \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "Content-Type: application/json" -d '{}'
# → { ok:true, agentCredit:12500, unlimited:false, canTopupCustomer:true, ... }

# 2b) เติมให้ลูกค้า user01
curl -X POST "$BASE/api/v1/wallet/topup" \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userId":"user01","amount":500,"reason":"เติมรายวัน"}'

# 3) สร้าง play-link ส่งให้ลูกค้า
curl -X POST "$BASE/api/v1/auth/play-link" \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"customerId":"user01","ttlMinutes":15}'
# → { ok:true, url:"...autotoken=XXX", ... }
# ส่ง url ให้ลูกค้าคลิก = เข้าเล่นได้เลย ไม่ต้องกรอก password
ดูห้องที่เคยเล่น + รายละเอียดการหักเงิน
# รายการห้องที่ user01 เคยเล่น
curl -X POST "$BASE/api/v1/rooms/my" \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"userId":"user01","limit":10}'

# รายละเอียดการหักเงินในห้อง AB1234
curl -X POST "$BASE/api/v1/rooms/payments" \
  -H "Authorization: Bearer AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"AB1234","userId":"user01"}'
ใช้ API Key แทน Bearer token
# Agent ทุกคนมี API key อัตโนมัติตอนสร้างบัญชี
# ดู key ของตัวเองได้ที่หน้าหลังบ้าน → ปุ่ม "🔑 API Key" (ต้องใส่รหัสผ่าน)

# ใช้ key แทน user token
curl -X POST "$BASE/api/v1/wallet/balance" \
  -H "X-API-Key: abc123def456..." \
  -d '{"userId":"user01"}'
🎁 Affiliate — สมัครพร้อม refCode + ดึงลิงค์แนะนำเพื่อน
# 1) ตรวจ refCode ก่อนสมัคร (preflight) — เช็คว่าผู้แนะนำอยู่สายเดียวกันไหม
curl -X POST "$BASE/api/v1/aff/check-code" \
  -H "X-API-Key: AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"refCode":"A8K2X9MP"}'
# → { found:true, crossAgent:false/true, referrerName:"...", ... }

# 2) สมัครลูกค้าใหม่พร้อมผูก refCode
curl -X POST "$BASE/api/v1/auth/register" \
  -H "X-API-Key: AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "username":"555",
    "password":"1234",
    "displayName":"เพื่อนของ A",
    "refCode":"A8K2X9MP"
  }'
# → 200 + { user, token, referralCode, referralUrl, playUrl, referralBound:true }
# → 403 ถ้าผู้แนะนำอยู่คนละสาย — ต้องเปลี่ยน refCode หรือสมัครโดยไม่ใส่

# 3) ลูกค้าดูยอด aff + ลิงค์ชวนเพื่อน
curl -X POST "$BASE/api/v1/aff/me" \
  -H "Authorization: Bearer USER_TOKEN" \
  -H "Content-Type: application/json" -d '{}'
# → { stats:{ refCode, affWallet, earnedPaid, earnedPending, ... },
#     referralCode, referralUrl }

# 4) ลูกค้าถอน aff_wallet → points
curl -X POST "$BASE/api/v1/aff/withdraw" \
  -H "Authorization: Bearer USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount":30}'

# 5) Agent ดูสรุปคอม Aff ของลูกค้าตัวเอง + ตั้ง %
curl -X POST "$BASE/api/v1/aff/agent/summary" \
  -H "X-API-Key: AGENT_KEY" -d '{}'

curl -X POST "$BASE/api/v1/aff/agent/set-user" \
  -H "X-API-Key: AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"userId":"agent_a","commissionPct":7,"withdrawSchedule":"monthly"}'

# 6) ★ รายงานช่วงเวลา — ลูกค้า (สรุป + daily + top referred)
curl -X POST "$BASE/api/v1/aff/report" \
  -H "Authorization: Bearer USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"fromTs":1762000000000,"toTs":1762999999999}'
# default = 30 วันย้อนหลัง (ส่ง {} ก็ได้)

# 7) ★ รายงานสำหรับ agent — ยอดที่ตัวเองจ่ายคอม Aff (รายวัน + per-ลูกค้า)
curl -X POST "$BASE/api/v1/aff/agent/report" \
  -H "X-API-Key: AGENT_KEY" \
  -H "Content-Type: application/json" -d '{}'
🌐 ตัวอย่าง integrate ใน PHP — ระบบสมัคร + ลิงค์ ?ref=
<?php
// === register.php ===
$AGENT_KEY = 'YOUR_AGENT_API_KEY';
$BASE = 'YOUR_BASE_URL';   // ★ ใส่ที่อยู่ของระบบที่คุณใช้

// 1) จับ ?ref=XXX จาก URL ตอนเพื่อนคลิกลิงค์
$refCode = $_GET['ref'] ?? null;

// 2) ฟอร์มสมัคร — เก็บ refCode ไว้ใน hidden input
?>
<form method="POST" action="do_register.php">
  <input name="username" placeholder="username">
  <input name="password" type="password">
  <input type="hidden" name="refCode" value="<?=htmlspecialchars($refCode)?>">
  <button>สมัคร</button>
</form>

<?php
// === do_register.php ===
$BASE = 'YOUR_BASE_URL';
function api($path, $data, $key) {
  global $BASE;
  $ch = curl_init("$BASE$path");
  curl_setopt_array($ch, [
    CURLOPT_POST=>true,
    CURLOPT_POSTFIELDS=>json_encode($data),
    CURLOPT_HTTPHEADER=>["Content-Type: application/json","X-API-Key: $key"],
    CURLOPT_RETURNTRANSFER=>true,
  ]);
  return json_decode(curl_exec($ch), true);
}

$res = api('/api/v1/auth/register', [
  'username' => $_POST['username'],
  'password' => $_POST['password'],
  'refCode'  => $_POST['refCode'] ?: null,
], $AGENT_KEY);

if (!empty($res['ok'])) {
  // สำเร็จ — เก็บ token, แสดงลิงค์ชวนเพื่อนของตัวเองให้ user คัดลอก
  $_SESSION['token'] = $res['token'];
  echo "<p>ลิงค์ชวนเพื่อนของคุณ: <a href='{$res['referralUrl']}'>{$res['referralUrl']}</a></p>";
  header('Location: '.$res['playUrl']); exit;
} elseif (!empty($res['crossBranch'])) {
  echo "<p>ผู้แนะนำ \"{$res['referrerName']}\" อยู่คนละสาย — แนะนำได้เฉพาะสมาชิกในสายเดียวกัน</p>";
}
?>

🔑 Special API (โปรเจคต์พิเศษ — Admin จัดสิทธิ์เป็นรายบุคคล)

POST /api/v1/special/rooms Auth: Required + Special permission
รายชื่อห้องทั้งหมดของสายตัวเอง พร้อมรายละเอียดเต็ม — players, hand sizes, game state, public melds, deck, discard pile top
สิทธิ์ใช้งาน: caller ต้องอยู่ใต้สาย Super Senior ที่:
   1. Admin มอบสิทธิ์ Special API ให้แล้ว (granted=TRUE)
   2. Super Senior เปิดสวิตช์ในหน้า downline เอง (enabled=TRUE)
★ ถ้า SS ปิด → ทั้งสายล่างเรียกไม่ได้ (กลับมาเปิด = ทั้งสายล่างใช้ได้ทันที)
★ Admin สามารถเรียกได้เสมอ (admin override)
Body / Response
// Request (body)
{}   // ไม่ต้องส่ง params — ดึงตาม subtree ของ caller อัตโนมัติ

// Response 200 — ลิสต์ห้องของสาย
{
  "ok": true,
  "count": 3,
  "rooms": [
    {
      "roomId": "ABC12XYZ",
      "gameType": "dummy",
      "roomType": "public",
      "rate": { "id": "r3", "name": "เรท 5", "entryFee": 5 },
      "gameMode": "fast",
      "isPractice": false,
      "isTournament": false,
      "maxPlayers": 4,
      "hostId": "ahalice1",
      "dealerId": null,
      "createdAt": 1779300000000,
      "players": [
        {
          "id": "ahalice1",
          "displayName": "Alice",
          "online": true,
          "handSize": 7,
          "seatIndex": null,
          "isMine": true        // ★ ลูกค้าใต้สายคุณ
        },
        {
          "id": "fk1a2b3c4d",
          "displayName": "บอท1",
          "online": true,
          "handSize": 8,
          "seatIndex": null,
          "isMine": false       // ★ บอท / นอกสาย
        }
      ],
      "gameState": {
        "phase": "play",
        "currentPlayerId": "ahalice1",
        "deckSize": 22,
        "discardPileTop": { "id": "K-H" },
        "publicMelds": [
          { "ownerId": "ahalice1", "type": "tong", "hasHead": false,
            "cards": [{ "rank":"7","suit":"S" }, { "rank":"7","suit":"H" }, { "rank":"7","suit":"D" }] }
        ],
        "headClaimed": false,
        "winnerId": null,
        "roundCount": 1
      }
    }
  ]
}

// ── Error 403 — ไม่มีสิทธิ์ (Super Senior ใน chain ยังไม่เปิด/Admin ยังไม่มอบ)
HTTP 403
{ "ok": false, "error": "ไม่มีสิทธิ์ใช้ Special API — ติดต่อ super senior เปิดสิทธิ์ให้" }
POST /api/v1/special/dissolve-room Auth: Required + Special permission
ยุบห้องบังคับ — บังคับปิดห้องที่กำลังเล่น/รออยู่ (ลูกค้าทั้งหมดเด้งกลับ lobby)
เงื่อนไข: ห้องต้องมีลูกค้า อย่างน้อย 1 คน เป็น downline ของคุณ
★ Admin → ยุบห้องใดก็ได้ (admin override)
★ ก่อนยุบ — ระบบ flush เงินที่ค้างให้ลูกค้าก่อน (ไม่เสียยอด)
★ ผู้เล่นได้รับ WS message roomDissolved + เด้งกลับ lobby อัตโนมัติ
Body / Response
// Request
{
  "roomId": "ABC12XYZ",
  "reason": "ลูกค้าร้องเรียน — รีเซ็ตเกม"   // optional, max 100 chars
}

// Response 200
{
  "ok": true,
  "roomId": "ABC12XYZ",
  "reason": "ลูกค้าร้องเรียน — รีเซ็ตเกม",
  "playersAffected": 3
}

// ── Error 403 — ห้องไม่มีลูกค้าของคุณ
{ "ok": false, "error": "ห้องนี้ไม่มีลูกค้าของคุณ — ยุบไม่ได้" }

// ── Error: ห้องไม่พบ
{ "ok": false, "error": "ไม่พบห้องนี้" }