Integrations
เชื่อมระบบโทรศัพท์ภายนอก — หรือถังเก็บไฟล์ที่ระบบโทรศัพท์เขียนลงไปอยู่แล้ว — เพื่อดึง recording เข้ามาให้อัตโนมัติ ตั้งค่าครั้งเดียวแล้วระบบ sync ให้เอง
DTAC orktrack
เชื่อม DTAC orktrack แล้วระบบจะดึง recording เข้ามาให้อัตโนมัติตามรอบที่ตั้งไว้ — credentials ถูกเก็บแบบเข้ารหัส
เริ่มใช้งาน
- Dashboard → Business → Settings → Providers
- Tab "Phone Integrations" → Add → kind =
dtac - กรอก baseUrl, username, password (default URL คือ
onecallvoicerecord.dtac.co.th) - กด Test connection เพื่อตรวจสอบว่าเชื่อมต่อได้
- Set as default — ระบบเริ่มดึง recording ให้อัตโนมัติ
3CX cloud
เชื่อม 3CX cloud ของคุณแล้วระบบจะดึง recording เข้ามาให้อัตโนมัติ — ถอดเสียง + สรุปด้วย AI พร้อม query ผ่าน API ได้เหมือน recording อื่น
ตั้งค่าครั้งเดียวที่ Dashboard → Business → Settings → Providers แล้วระบบจะ sync ให้เอง ไม่ต้องอัปโหลดไฟล์เอง
ถัง MinIO / S3
ถ้าตู้สาขาของคุณทิ้งไฟล์บันทึกลงถังแบบ S3 (MinIO, AWS S3, Wasabi, …) อยู่แล้ว ให้ชี้มาที่ถังนั้นแทนที่จะชี้ไปที่ระบบโทรศัพท์ — ไม่ต้องมี session ล็อกอินและไม่ต้องผ่าน API ของผู้ขายรายไหน เราจะไล่ดูไฟล์ในถังตามรอบแล้วดึงไฟล์เสียงที่เจอเข้ามา ทุกอย่างหลังจากนั้น (ถอดเสียง สรุปด้วย AI ให้คะแนน และการเรียกผ่าน API) เหมือนสายที่ดึงจากตู้สาขาทุกประการ โดยค่า sourceProvider ของสายที่ได้จะเป็น minio
เราอ่านอย่างเดียว ไม่เขียนกลับ ไม่ย้าย ไม่ลบอะไรในถังของคุณ ให้สิทธิ์คีย์แค่ list + read ก็พอ
เริ่มใช้งาน
- Dashboard → Business → Settings → Providers
- Tab "Phone Integrations" → Add → kind =
minio - กรอก endpoint, access key, secret key, ชื่อ bucket และโฟลเดอร์ (
prefix) ที่จะให้ไล่ดู - กด Test connection — ระบบยิง
bucketExistsจริง จึงตรวจทั้ง credential และการมองเห็นถังในครั้งเดียว - กดดูตัวอย่างไฟล์ เพื่อยืนยันว่าไฟล์ที่จะถูกดึงคือไฟล์ที่คุณตั้งใจ
- บันทึก — ตัวดึงจะเริ่มทำงานในรอบถัดไป
หรือทำแบบเดียวกันผ่าน API — POST /providers โดยใช้ kind: "minio" (ดูรูปร่างของ config ทั้งหมดได้ที่หน้า Providers)
curl -X POST https://phone.mcloud.co.th/api/v1/providers \
-H "Authorization: Bearer crk_..." \
-H "Content-Type: application/json" \
-d '{
"kind": "minio",
"name": "PBX drop folder",
"config": {
"endpoint": "https://s3.example.com:9000",
"accessKey": "AKIA...",
"secretKey": "s3cret",
"bucket": "recordings",
"prefix": "pbx/2026/",
"direction": "in"
}
}'
อะไรบ้างที่ถูกดึงเข้ามาจริง
มีสามด่านที่ตัดงานในแต่ละรอบ และการรู้ลำดับของมันช่วยไม่ให้ค่าใช้จ่ายบานปลาย:
- รายการนามสกุลที่อนุญาต (
extensions) — ถังที่ตู้สาขาเขียนลงมักมี CDR csv/json ไฟล์.tmpที่ยังเขียนไม่เสร็จ และบางทีก็มีวิดีโอบันทึกหน้าจอปนอยู่ ระบบจะหยิบเฉพาะนามสกุลไฟล์เสียง ที่เหลือปล่อยไว้เฉย ๆ - ช่วงเวลาย้อนหลัง (
lookbackHoursค่าเริ่มต้น 48) — พิจารณาเฉพาะไฟล์ที่แก้ไขภายในช่วงย้อนหลังนี้ ถังที่เก็บประวัติหลายปีจึงไม่ถูกไล่ใหม่ทั้งก้อนทุกรอบ ถ้าต้องการดึงของเก่าย้อนหลังถือเป็นงานแยกที่ต้องตั้งใจทำ - เงื่อนไขการเก็บสาย (
objectFilter) — เลือกเก็บ/ข้ามเป็นรายโฟลเดอร์ และกำหนดช่วงความยาวเสียง โดยโฟลเดอร์ถูกคัดตอน LIST ของ S3 คือก่อนโหลดแม้แต่ไบต์เดียว ส่วนความยาวไม่มีในผลลิสต์ จึงตรวจหลังโหลดไฟล์มา probe เสียค่าโหลด 1 ครั้ง แต่ยังข้ามการเก็บไฟล์ การสร้างแถวสาย การ transcode STT และงาน AI ทุกขั้น
ถังไม่มีข้อมูลผู้โทรและไม่มีทิศทางสาย ค่า direction จึงประกาศไว้ที่การเชื่อมต่อแล้วประทับลงทุกสายที่ดึงเข้ามา ส่วน path เต็มของไฟล์จะถูกเก็บเป็นโน้ตของสาย — นี่คือเหตุผลที่การตั้งเงื่อนไขรายโฟลเดอร์คุ้มค่า เพราะบนโฟลเดอร์ที่ตู้สาขาเขียนลง path มักเป็นตัวบอกสาขา/extension/วันที่อยู่แล้ว
ข้อจำกัดที่ควรรู้
maxObjectsPerTick(ค่าเริ่มต้น 500) จำกัดจำนวนไฟล์ต่อรอบ การ sync ครั้งแรกของถังใหญ่จึงทยอยเข้าหลายรอบ ไม่ใช่มาทีเดียวหมด- การเชื่อมต่อแบบนี้ไม่มีรายชื่อ agent
GET /providers/{id}/peopleจึงตอบprovider_kind_not_supported(422) — ให้ใช้การดูตัวอย่างไฟล์ในแดชบอร์ดเพื่อหา path ของโฟลเดอร์แทน - ธุรกิจเดียวเชื่อมได้หลายถัง แต่ละถังดึงตามรอบของตัวเอง
Webhooks
ใช้งานได้แล้วในชื่อ Callbacks — เรา POST ไป endpoint ของคุณเมื่อสายวิเคราะห์เสร็จ (analysis_completed) หรือวิเคราะห์ไม่สำเร็จ (analysis_failed) พร้อมลายเซ็น HMAC ให้ตรวจสอบได้ ตั้งค่าผ่าน GET / PUT / DELETE /api/v1/callbacks — รายละเอียด payload, การตรวจลายเซ็น, การส่งซ้ำ และกติกาของ URL อยู่ที่หน้า Callbacks
Telegram & Notification gateway
นอกจากใช้ผ่านหน้า UI ของแพลตฟอร์ม ยังเชื่อม Telegram และยิงการแจ้งเตือนผ่าน public API ได้ — เหมาะกับระบบภายนอก (เช่น CRM) ที่ต้องการเป็นเจ้าของ event logic เอง แล้วใช้ CRK เป็นท่อส่ง (delivery pipe)
ออกแบบสำหรับ mapping แบบ 1 tenant ของระบบภายนอก ↔ 1 business ของ CRK: ระบบภายนอกถือ API token ที่ผูกกับ business เดียว แล้วส่ง x-business-id header (หรือใช้ business เริ่มต้นของ token) ในทุก request
สิทธิ์ (scopes)
| Scope | ใช้กับ |
|---|---|
view_notifications | อ่าน status / chats / destinations / automations / preferences |
manage_notifications | mint link-code, toggle/unlink chat, test, ส่งการแจ้งเตือน, สร้าง/แก้/ลบ/toggle automation, เชื่อม/ลบ Discord webhook, แก้ preferences |
เชื่อม Telegram chat
แพลตฟอร์มใช้ bot Telegram กลางตัวเดียว (token ตั้งที่ฝั่ง server) — แต่ละ business เชื่อม chat ของตัวเองผ่าน flow /start <code>:
- เรียก
POST /telegram/link-code→ ได้{ code, deepLink, expiresInSec }(code อายุ 15 นาที) - ระบบภายนอก render QR code หรือ deep link
https://t.me/<bot>?start=<code>ให้ผู้ใช้สแกน/กด - ผู้ใช้กด start ใน Telegram → chat ถูกผูกกับ business นั้น
- poll
GET /telegram/chatsจนกว่า chat ใหม่จะเข้า list (มีchatId,title,isActive) - ส่งข้อความทดสอบผ่าน
POST /telegram/chats/{id}/testเพื่อยืนยัน
เปิด/ปิดการส่งชั่วคราวด้วย PATCH /telegram/chats/{id} ({ isActive: false }) หรือถอด chat ออกด้วย DELETE /telegram/chats/{id} (error chat_not_found 404 ถ้า id ไม่ใช่ของ business นี้)
ยิงการแจ้งเตือน (send-now gateway)
POST /notifications/send รับ payload แล้วส่งออกทันทีตาม channel ที่เลือก — CRK เป็น pure delivery pipe, caller เป็นเจ้าของ event logic:
{
"channel": "telegram",
"chatId": "<id จาก /telegram/chats>",
"title": "Lead ใหม่: คุณสมชาย",
"body": "เบอร์ 08x-xxx-xxxx สนใจบ้านเดี่ยว โครงการ A",
"link": "https://crm.example.com/leads/123",
"severity": "info"
}
channel: "telegram"→ ต้องส่งchatId(เอาจากGET /telegram/chatsหรือGET /notifications/destinations)channel: "discord"→ ต้องส่งwebhookId(เอาจากGET /notifications/destinations; webhook URL ไม่เคยถูก return ผ่าน API)channel: "in_app"→ สร้าง row ใน feed ในแพลตฟอร์ม (ส่งlinkได้ในdata) แล้ว fan-out ผ่าน realtime ไปยังสมาชิกใน business
ถ้า Telegram/Discord API ล้มเหลว จะได้ { ok: false, error: "..." } กลับมาใน body (ไม่ใช่ HTTP error) เพื่อให้ caller retry ได้. Discord webhook ที่ไม่ใช่ของ business นี้แจ้ง discord_webhook_not_found (404)
⚠️ Payload ไม่มี field ของ AI model / provider / ต้นทุน — CRK ไม่เปิดเผยข้อมูลนั้นกับระบบภายนอก
จัดการ automation (กฎการแจ้งเตือน)
CRK เก็บ + ยิง automation เองตอนสายวิเคราะห์เสร็จ. ระบบภายนอกจัดการเป็น "rule" แบบแบน ได้ครบผ่าน public API (CRK compile เป็น flow graph ให้เอง — ไม่ต้องรู้จักโครงสร้าง graph):
GET /notifications/automations— รายการ automation + สถิติการส่ง (sent/failed/lastRunAt)POST /notifications/automations— สร้างกฎใหม่จาก rule{ name, conditions[], message, telegramChatIds[], discordWebhookIds[] }→ คืน{ id }(201)GET /notifications/automations/{id}— อ่านกฎ (projected กลับเป็น rule); ถ้า graph ถูกแต่งซับซ้อนใน CRK UI เกิน shape ของ simple rule จะได้rule: null, advanced: true(ให้แก้ใน CRK UI แทน)PUT /notifications/automations/{id}— แทนที่ทั้งกฎ (recompile เป็น graph ใหม่)PATCH /notifications/automations/{id}— เปิด/ปิด ({ enabled })DELETE /notifications/automations/{id}— ลบ (automation_not_found404)
rule มี trigger เดียว = สายวิเคราะห์เสร็จ. conditions เป็น call field (score/tag/sentiment/direction/...) AND/OR กัน (ว่าง = ทุกสาย); message ใส่ตัวแปร {{score}}, {{customer_phone}}, {{summary}} ฯลฯ; telegramChatIds = chat row id (จาก /telegram/chats), discordWebhookIds = webhook id (จาก /notifications/destinations).
จัดการ Discord webhook (ปลายทาง)
POST /notifications/discord— เชื่อม webhook ใหม่{ name, webhookUrl }(ต้องเป็น URL ของ discord(app).com มิฉะนั้นแจ้งdiscord_invalid_webhook422) → คืน{ id }(201)DELETE /notifications/discord/{id}— ถอด webhook (discord_webhook_not_found404)
preferences
GET /notifications/preferences+PATCH /notifications/preferences— ค่าของผู้ใช้เจ้าของ token (inAppEnabled/sound/desktop/minSeverity/mutedRuleIds)
ผู้ช่วย AI
ถามเป็นภาษาคนแล้วได้คำตอบที่อ้างอิงจากข้อมูลสายของคุณเอง — ผู้ช่วยตัวเดียวกับที่ใช้ในหน้าแชทของแพลตฟอร์ม เปิดให้ n8n flow, CRM หรือระบบหลังบ้านใดก็ตามที่อยากถามมากกว่าจะประกอบ query เอง
POST /assistant/ask
Scopes: view_recording_all · view_recording_self
| Field | Type | Note |
|---|---|---|
question | string 1-4000 | required · ตัดช่องว่างหัวท้ายให้ |
model | string ≤120 | optional — ระบุรุ่นที่อยากใช้ได้ แต่ระบบไม่เคยส่งค่านี้กลับ |
conversationId | uuid | optional — คุยต่อจากเธรดเดิม ต้องเป็นเธรดของ business นี้ |
stream | boolean | default false · ถ้า true จะตอบเป็น Server-Sent Events แทน JSON |
curl -X POST https://phone.mcloud.co.th/api/v1/assistant/ask \
-H "Authorization: Bearer crk_..." \
-H "Content-Type: application/json" \
-d '{"question":"สรุปสายเมื่อวานให้หน่อย"}'
{
"answer": "เมื่อวาน (13 ส.ค.) มีสายเข้า 41 สาย รับได้ 36 ...",
"sources": [
{ "title": "สายจาก 08x-xxx-xxxx เวลา 10:14", "url": "https://phone.mcloud.co.th/app/recordings/0192b9...", "snippet": "ลูกค้าสอบถามโปรโมชั่น..." }
],
"conversationId": "0192d4...",
"usage": { "aiProcessed": true }
}
เก็บ conversationId ไว้แล้วส่งกลับมาในคำถามถัดไป เพื่อคุยต่อในเธรดเดิม
แบบสตรีม
เมื่อส่ง stream: true response จะเป็น text/event-stream; charset=utf-8 (cache-control: no-cache, no-transform, x-accel-buffering: no) · แต่ละบล็อกเป็น data: <json> และคง id: ไว้ให้ด้วย การต่อสตรีมด้วย Last-Event-ID เมื่อสายหลุดกลางคันจึงยังทำงาน
ชนิด event ที่ถูกส่งออกมีเท่านี้เท่านั้น: start, text, sources, title, done, error, ping · นี่เป็น allowlist — อะไรก็ตามที่บอกว่าคำตอบถูกผลิตอย่างไร (ขั้นตอนคิด, การเรียก tool, จำนวน token, การสลับโมเดล) จะถูกตัดทิ้งทั้งก้อน ไม่ใช่แค่กรองบางฟิลด์ event ใหม่จากต้นทางจึงรั่วออกมาเองไม่ได้
สิ่งที่ endpoint นี้ไม่มีวันคืน
ไม่มีชื่อโมเดล ไม่มีชื่อผู้ให้บริการ ไม่มีจำนวน token ไม่มีต้นทุน — ทั้งใน JSON body และในทุก SSE event · usage.aiProcessed: true คือสิ่งเดียวที่บอกเกี่ยวกับ AI · ข้อนี้บังคับด้วยโครงสร้างของโค้ด ไม่ใช่ด้วยการตรวจทาน: คำตอบแบบ JSON ถูกประกอบใหม่ทีละฟิลด์จาก schema ปิด ส่วนสตรีมถูกกรองทีละ event ก่อนถึงปลายทาง
ข้อผิดพลาด
| Status | Code | ความหมาย |
|---|---|---|
| 400 | validation_failed | ฟิลด์ใน body ไม่ผ่านการตรวจ (issues บอกว่าฟิลด์ไหน) |
| 403 | insufficient_scope | token ไม่มี view scope ทั้งสองตัว |
| 404 | conversation_not_found | ไม่มีเธรดนี้ หรือ เธรดเป็นของ business อื่น (ถ้าตอบ 403 เท่ากับยืนยันว่า id นั้นมีจริง) |
| 422 | invalid_payload | body ไม่ใช่ JSON object |
| 429 | rate_limited | ถามถี่เกินไป — มี header retry-after และ x-ratelimit-limit กำกับ |
ตัวผู้ช่วยเองล่มได้โดยไม่ใช่ความผิดของผู้เรียก กรณีเหล่านั้นจึงมีรหัสของตัวเองและไม่เคยเป็น 500: 503 agent_host_unreachable / agent_host_not_configured / agent_host_rejected / agent_host_error, 504 agent_host_timeout, 429 agent_host_rate_limited, 400 agent_host_bad_request, 499 request_cancelled · body เป็น { "error": { "code": "...", "retryable": true|false } } — ให้ลองใหม่เฉพาะตอน retryable เป็น true และเว้นระยะแบบ backoff
การจำกัดอัตราคิดต่อ API key ค่าเริ่มต้น 30 คำถามต่อนาที · การถาม 1 ครั้งคือการรันโมเดลและวนเรียก tool ให้คิดว่าเป็นคำขอที่แพง — ถามครั้งเดียวแล้วเก็บผลไว้ใช้ ดีกว่าถามทีละแถว
