นักพัฒนาระบบ · Voice Agent

ให้ AI รับสายและโทรออกจากระบบของคุณ

ฝังปุ่มคุยด้วยเสียงกับ AI บนเว็บไซต์หรือแอป สั่งให้ AI โทรออกหาลูกค้าผ่านแคมเปญ และดึงบทสนทนา สรุป และข้อมูลที่ AI เก็บได้ไปใช้ในระบบของคุณ
Base URL https://api.agenticos.tech/api/v1/voice-agent · ข้อมูลใน API ใช้ camelCase

1. ภาพรวม

ต้องการใช้ยืนยันสิทธิ์ด้วย
ให้ลูกค้าคุยกับ AI ด้วยเสียงบนเว็บ/แอปหน้าโทรสำเร็จรูป /v/<embed key> (ลิงก์ หรือ iframe)Embed key
ให้ AI โทรออกหารายชื่อลูกค้าCampaign APIJWT
ดึงบทสนทนา สรุป และข้อมูลที่ AI เก็บได้Calls APIJWT
ให้ AI รับสายเข้าเบอร์บริษัทผูกเบอร์กับ agent (Numbers)JWT

ก่อนเริ่ม: ต้องขออะไรจากแพลตฟอร์ม

บางอย่างขอจากแอดมินขององค์กรลูกค้า (owner หรือ admin ในบัญชี AgenticOS) บางอย่างต้องให้ทีม AgenticOS ทำ เพราะเกี่ยวกับระบบโทรศัพท์ ขอให้ครบตามสิ่งที่จะใช้ก่อนเริ่มเขียนโค้ด

ต้องขอใช้ทำอะไรขอจาก / หาได้ที่ใช้กับ
บัญชีผู้ใช้สำหรับเชื่อมระบบlogin เพื่อรับ JWT (ข้อ 3) ขอเป็นอีเมลเฉพาะของระบบ บทบาท admin และยืนยันอีเมลแล้วแอดมิน: ตั้งค่า → สมาชิก → เชิญโทรออก, ผลการโทร, เบอร์
ชื่อ agent ที่จะใช้หา agent id เองด้วย GET /agents แล้วใช้กับแคมเปญแอดมิน: วอยซ์เอเจนต์ → Agentsโทรออก
รายชื่อข้อมูลที่อยากได้หลังวางสายให้แอดมินตั้ง Variables to Extract ใน agent ด้วยชื่อ field ที่ตกลงกัน ค่าจะมาใน collectedแอดมิน: agent → ตัวแปรที่ต้องเก็บ (Variables to Extract)ผลการโทร
ลิงก์โทรบนเว็บ (embed key)ฝัง iframe หรือเปิดใน WebView (ข้อ 2)แอดมิน: agent → แชร์ → คัดลอกลิงก์/โค้ดฝังปุ่มโทรบนเว็บ
เพดานสายบนเว็บจำนวนสายต่อชั่วโมงต่อ IP และความยาวสาย (ค่าเริ่มต้น 20 สาย, 5 นาที)แอดมิน: ตั้งค่าวอยซ์เอเจนต์ปุ่มโทรบนเว็บ
เบอร์ที่ AI รับสายเบอร์ต้องต่อเข้าระบบโทรศัพท์ของ AgenticOS ก่อน แล้วแอดมินผูกเบอร์กับ agentทีม AgenticOS (เชื่อมเบอร์) + แอดมิน (ผูก agent)รับสายเข้า
หมายเลขที่ขึ้นตอนโทรออกเบอร์ที่ลูกค้าเห็นตอน AI โทรไป ระบุเองไม่ได้ ต้องเปิดเส้นทางให้ก่อนทีม AgenticOS (เปิดเส้นทาง) + แอดมิน (เลือกในตั้งค่า)โทรออก
เวลาทำการนอกเวลาบอทจะแจ้งปิดทำการ ต้องรู้เพื่อสั่งเริ่มแคมเปญในเวลาทำการแอดมิน: ตั้งค่าวอยซ์เอเจนต์ → เวลาทำการโทรออก
แพ็กเกจที่ใช้งานได้ถ้าช่วงทดลองหมดอายุ ระบบจะไม่รับหรือโทรออกowner ขององค์กรทุกอย่าง
รหัสผ่านของบัญชีเชื่อมระบบเป็นความลับ ให้ส่งผ่าน password manager และเก็บบนเซิร์ฟเวอร์เท่านั้น ถ้าผู้ใช้นี้อยู่หลายองค์กร ให้ขอชื่อองค์กรที่ต้องใช้ แล้วหา tenant id ด้วย GET /api/v1/auth/me ส่วน embed key เป็นค่าสาธารณะ
ข้อความขอข้อมูล (คัดลอกไปส่งได้)
สวัสดีครับ ทีมพัฒนาจะเชื่อมระบบกับวอยซ์เอเจนต์ของ AgenticOS รบกวนขอดังนี้ครับ
1. บัญชีผู้ใช้สำหรับเชื่อมระบบ บทบาท admin อีเมล: integration@<บริษัท> — ส่งรหัสผ่านผ่าน password manager
2. ชื่อ agent ที่จะใช้โทรออก / รับสาย
3. ตั้ง "ตัวแปรที่ต้องเก็บ (Variables to Extract)" ใน agent ตามนี้: customer_name, province, budget_thb, interested, callback_time
4. ลิงก์โทรบนเว็บของ agent (แชร์ → คัดลอกลิงก์) ถ้าจะฝังบนเว็บ/แอป
5. เวลาทำการ และเพดานสายบนเว็บที่ตั้งไว้
6. (ถ้าให้ AI รับสายเข้า) เบอร์ที่จะใช้ — ขอทีม AgenticOS เชื่อมเบอร์เข้าระบบ และหมายเลขที่จะขึ้นตอนโทรออก

2. ฝังปุ่มคุยด้วยเสียงบนเว็บไซต์หรือแอป

แต่ละ agent มีหน้าโทรสำเร็จรูป เปิดในเบราว์เซอร์แล้วกดคุยได้ทันที ไม่ต้องเขียนโค้ด WebRTC เอง ใช้ได้ทั้งแชร์เป็นลิงก์ ฝังเป็น iframe บนเว็บ หรือเปิดใน WebView ของแอป

ขอลิงก์

  • ในแอป: วอยซ์เอเจนต์ → เลือก agent → แชร์ → คัดลอกลิงก์หรือโค้ดฝัง
  • หรือผ่าน API (owner/admin): POST /agents/{agent_id}/embed-key คืน { "embedKey": "emb_…", "url": "https://app.agenticos.tech/v/emb_…" } เรียกซ้ำได้ค่าเดิม
HTML
<iframe
  src="https://app.agenticos.tech/v/emb_XXXXXXXX?theme=light"
  width="400" height="640"
  style="border:0;border-radius:16px"
  allow="microphone"
  title="Voice assistant"></iframe>
  • allow="microphone" จำเป็น ไม่งั้นเบราว์เซอร์ไม่ให้ใช้ไมค์
  • ?theme= dark · light · aurora · minimal (ค่าเริ่มต้น dark)
  • หน้าโทรแสดงข้อความขออนุญาตบันทึกเสียงก่อนเริ่ม และตอนนี้ข้อความบนหน้าเป็นภาษาไทย

ใช้ในแอปมือถือผ่าน WebView

เปิดลิงก์ /v/<embed key> ใน WebView ได้เลย สิ่งที่ต้องทำเพิ่มคือให้ WebView ใช้ไมโครโฟนและเล่นเสียงได้ ถ้าไม่ทำ กดปุ่มโทรแล้วจะเงียบหรือขึ้นว่าไม่ได้รับอนุญาต

เรื่องiOS (WKWebView)Android (WebView)
สิทธิ์ไมโครโฟนของแอปNSMicrophoneUsageDescriptionRECORD_AUDIO · MODIFY_AUDIO_SETTINGS และขอสิทธิ์ตอนใช้งาน
ให้หน้าเว็บใช้ไมค์iOS 15+: requestMediaCapturePermissionFor ใน WKUIDelegate ตอบ .grantonPermissionRequest ใน WebChromeClient ตอบ grant RESOURCE_AUDIO_CAPTURE
เล่นเสียงของ AIallowsInlineMediaPlayback = truemediaPlaybackRequiresUserGesture = false
อื่น ๆiOS 14.3 ขึ้นไป (WebRTC ใน WKWebView)javaScriptEnabled = true · domStorageEnabled = true
Swift
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self
webView.load(URLRequest(url: URL(string: "https://app.agenticos.tech/v/emb_XXXXXXXX?theme=light")!))

// iOS 15+: let the call page use the microphone (only for our host)
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin,
             initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType,
             decisionHandler: @escaping (WKPermissionDecision) -> Void) {
    decisionHandler(origin.host.hasSuffix("agenticos.tech") ? .grant : .deny)
}
// Info.plist: NSMicrophoneUsageDescription = "ใช้ไมโครโฟนเพื่อคุยกับผู้ช่วย AI"
Kotlin
// AndroidManifest.xml
// <uses-permission android:name="android.permission.RECORD_AUDIO" />
// <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

// Ask RECORD_AUDIO at runtime first (ActivityResultContracts.RequestPermission), then:
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
    override fun onPermissionRequest(request: PermissionRequest) {
        val ours = request.origin.host?.endsWith("agenticos.tech") == true
        if (ours && PermissionRequest.RESOURCE_AUDIO_CAPTURE in request.resources) {
            request.grant(arrayOf(PermissionRequest.RESOURCE_AUDIO_CAPTURE))
        } else {
            request.deny()
        }
    }
}
webView.loadUrl("https://app.agenticos.tech/v/emb_XXXXXXXX?theme=light")
  • Flutter: ใช้ flutter_inappwebview แล้วตอบ grant ใน onPermissionRequest พร้อมขอสิทธิ์ไมค์ของแอป
  • React Native: ใช้ react-native-webview, ขอสิทธิ์ไมค์ของแอปก่อน และบน iOS ตั้ง mediaCapturePermissionGrantType="grant"
  • ยังส่งข้อมูลลูกค้า (เช่นชื่อหรือรหัสสมาชิก) เข้าไปในสายผ่านลิงก์ไม่ได้ ทุกสายจากหน้านี้เป็นผู้โทรไม่ระบุตัวตน

ความปลอดภัยของลิงก์

  • embed key เป็นค่าสาธารณะ ใครมีลิงก์ก็โทรได้ ระบบจึงจำกัดจำนวนสายและความยาวสายไว้ (ดูข้อ 8)
  • ลิงก์หลุดหรือถูกใช้ผิด: POST /agents/{agent_id}/embed-key/rotate ได้ key ใหม่ และลิงก์เดิมใช้ไม่ได้ทันที
  • เปลี่ยน key ไม่กระทบ agent ประวัติการโทร หรือเบอร์โทร

3. ยืนยันตัวตนสำหรับ API จัดการระบบ (JWT)

API ส่วนโทรออก ผลการโทร และเบอร์ ใช้ access token (JWT) ของผู้ใช้ในองค์กร ตอนนี้ยังไม่มี API key แยก แนะนำให้สร้างผู้ใช้สำหรับเชื่อมระบบโดยเฉพาะ (บทบาท admin) แล้วให้เซิร์ฟเวอร์ของคุณ login ด้วยบัญชีนั้น

POST/api/v1/auth/login

shell
curl -s https://api.agenticos.tech/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"••••••••"}'

# → { "access_token": "eyJ…", "token_type": "bearer" }
  • ส่งทุกคำขอด้วย header Authorization: Bearer <access_token>
  • token อายุ 24 ชั่วโมง ยังไม่มี refresh token ให้ login ใหม่เมื่อได้ 401
  • ผู้ใช้อยู่หลายองค์กร: token ผูกกับองค์กรแรก เปลี่ยนด้วย POST /api/v1/auth/switch-tenant {"tenant_id": "<uuid>"}
  • เก็บรหัสผ่านและ token ไว้บนเซิร์ฟเวอร์เท่านั้น ห้ามใส่ในแอปหรือหน้าเว็บ

สิทธิ์: อ่านข้อมูลต้องมีสิทธิ์ใช้โมดูลวอยซ์เอเจนต์ ส่วนสร้าง/แก้ agent แคมเปญ เบอร์ และรายชื่อ ต้องเป็น owner หรือ admin

4. ให้ AI โทรออกด้วยแคมเปญ

AI โทรออกผ่านแคมเปญเท่านั้น ถ้าต้องการโทรหาคนเดียว ให้สร้างแคมเปญที่มี 1 รายชื่อ ระบบหยิบรายชื่อไปโทรทุก ~8 วินาทีขณะแคมเปญอยู่ในสถานะ running

  1. สร้างแคมเปญ (สถานะ draft)
  2. เพิ่มรายชื่อ
  3. สั่งเริ่ม: เปลี่ยนสถานะเป็น running
  4. ติดตามผลรายชื่อ และดึงผลการโทร (ข้อ 5)

POST/api/v1/voice-agent/campaigns

request
{
  "name": "Follow up · September leads",
  "agentId": "<voice agent uuid>",
  "objective": "Call people who asked about the starter package. Ask permission for 2 minutes, answer questions, and book a site survey.",
  "maxConcurrent": 1
}
fieldรายละเอียด
name1–200 ตัวอักษร (บังคับ)
agentIdagent ที่จะโทร ต้องมี ไม่งั้นระบบไม่โทร
objectiveเป้าหมาย/สคริปต์ของแคมเปญ ≤4000 ตัวอักษร ใส่ในการเปิดสายทุกสาย
maxConcurrentจำนวนสายพร้อมกัน 1–5 (ค่าเริ่มต้น 2)
statusdraft · running · paused · done · scheduled

POST/api/v1/voice-agent/campaigns/{id}/contacts

request
{
  "contacts": [
    { "phone": "0812345678", "name": "Somchai" },
    { "phone": "+66898765432", "name": "Malee" }
  ]
}

# → 201 { "added": 2, "skipped": 0, "total": 2 }
  • สูงสุด 5,000 รายชื่อต่อครั้ง เบอร์ซ้ำ (ในแคมเปญหรือในชุดเดียวกัน) จะถูกข้าม
  • เบอร์ 3–32 ตัว ระบบเก็บเฉพาะตัวเลขและ + นำหน้า เบอร์ +66/66 จะโทรเป็น 0…
  • ยังไม่มีอัปโหลด CSV ผ่าน API ให้แปลงเป็น JSON ก่อนส่ง

PATCH/api/v1/voice-agent/campaigns/{id}

เริ่มและหยุดแคมเปญด้วย {"status": "running"} / {"status": "paused"}

สถานะ scheduled และ startAt ยังไม่ทำงานอัตโนมัติ แคมเปญจะเริ่มโทรเมื่อเปลี่ยนเป็น running เท่านั้น และระบบโทรออกไม่ตรวจเวลาทำการก่อนโทร ถ้าโทรนอกเวลา บอทจะแจ้งว่าปิดทำการแล้ววางสาย ให้สั่งเริ่มในเวลาทำการเอง

GET/api/v1/voice-agent/campaigns/{id}/contacts

200
{
  "contacts": [
    { "id": "…", "phone": "0812345678", "name": "Somchai", "status": "connected", "attempts": 1, "callId": "…", "updatedAt": "…" }
  ],
  "counts": { "connected": 1, "no_answer": 1 }
}
สถานะรายชื่อความหมาย
pendingรอโทร
callingกำลังโทร
connectedรับสายและคุยแล้ว ดูผลที่ callId
no_answerไม่รับสายภายใน 40 วินาที
failedสายไม่ว่างหรือถูกปฏิเสธ
voicemailเจอระบบฝากข้อความ
  • ไม่มีการโทรซ้ำอัตโนมัติ สั่งโทรซ้ำด้วย POST /campaigns/{id}/contacts/retry-failed (no_answer, failed, voicemail ทั้งหมด) หรือ POST /campaigns/{id}/contacts/{contact_id}/retry
  • แคมเปญเปลี่ยนเป็น done เองเมื่อไม่มีรายชื่อรอโทร

5. ผลการโทร: บทสนทนา สรุป และข้อมูลที่ AI เก็บได้

GET/api/v1/voice-agent/calls/{id}

200
{
  "id": "c9a3cb03-…",
  "direction": "outbound",
  "channel": "phone",
  "status": "completed",
  "phone": "0812345678",
  "agentId": "…",
  "startedAt": "2026-09-30T03:51:42Z",
  "durationSec": 209,
  "intent": "booked_site_survey",
  "summary": {
    "text": "The customer is interested in the starter package…",
    "keyPoints": ["Has 40 m² in Nakhon Pathom", "Budget about 1M"],
    "outcome": "booked_site_survey",
    "nextAction": "Sales to call back Friday morning",
    "sentiment": "positive",
    "usage": { "totalThb": 4.82, "breakdown": [ … ] }
  },
  "collected": { "province": "Nakhon Pathom", "space_sqm": 40, "budget_thb": 1000000, "consent": true },
  "recordingUrl": "https://…(valid for 1 hour)",
  "transcript": [
    { "seq": 1, "who": "agent", "text": "Hello, this is …", "atOffset": "00:01" },
    { "seq": 2, "who": "customer", "text": "Yes, go ahead", "atOffset": "00:06" }
  ]
}
fieldความหมาย
collectedข้อมูลที่ AI ดึงจากบทสนทนาตอนวางสาย ตามตัวแปรที่ตั้งไว้ใน agent (Variables to Extract) ชนิด string, number, boolean, date, email, phone
summaryสรุปการโทร จุดสำคัญ ผลลัพธ์ สิ่งที่ต้องทำต่อ อารมณ์ และค่าใช้จ่าย AI
transcriptบทสนทนาทีละประโยค who เป็น agent หรือ customer
recordingUrlไฟล์เสียง ลิงก์ใช้ได้ 1 ชั่วโมง ขอใหม่ได้โดยเรียก endpoint นี้ซ้ำ
statuscompleted · voicemail

GET/api/v1/voice-agent/collected

รายการเฉพาะสายที่มีข้อมูลที่ AI เก็บได้: [{ "callId", "agentId", "caller", "phone", "channel", "startedAt", "collected" }]

GET/api/v1/voice-agent/calls

ทุกสายเรียงจากใหม่ไปเก่า ตอนนี้ยังไม่มีการแบ่งหน้าหรือตัวกรอง ให้เก็บ id ที่ประมวลผลแล้วฝั่งคุณ และดึงรายละเอียดด้วย /calls/{id}

ยังไม่มี webhook แจ้งเมื่อวางสาย ให้ดึงข้อมูลเป็นรอบ เช่น ทุก 1–5 นาที: GET /collected หรือ GET /calls แล้วดึงเฉพาะ id ใหม่
Python
# Python — pull new captured leads every few minutes
import requests
H = {"Authorization": f"Bearer {token}"}
rows = requests.get("https://api.agenticos.tech/api/v1/voice-agent/collected", headers=H, timeout=30).json()
for r in rows:
    if r["callId"] in seen:
        continue
    save_lead(r["phone"], r["collected"])   # your CRM
    seen.add(r["callId"])

6. ติดตามสายที่กำลังคุย

  • GET /live สายที่กำลังคุยตอนนี้
  • GET /live/stream Server-Sent Events: snapshot, start, turn (ข้อความทีละประโยค), end ส่ง ping ทุก 15 วินาที
  • EventSource ของเบราว์เซอร์ใส่ header Authorization ไม่ได้ ให้ใช้ fetch แล้วอ่าน stream เอง

7. เบอร์โทรและการรับสายเข้า

ให้ AI รับสายเข้าเบอร์บริษัท: เพิ่มเบอร์แล้วผูกกับ agent เบอร์ต้องต่อเข้าระบบโทรศัพท์ของ AgenticOS อยู่แล้ว (ติดต่อทีมงานเพื่อเชื่อมเบอร์) การเพิ่มเบอร์ผ่าน API ไม่ได้เปิดเบอร์ใหม่ให้

request
POST https://api.agenticos.tech/api/v1/voice-agent/numbers
{ "label": "Head office", "number": "021234567", "agentId": "<voice agent uuid>", "status": "connected" }
  • สายเข้าจับคู่กับ agent ด้วยตัวเลขของเบอร์ (ไม่สนขีดหรือเว้นวรรค)
  • เปลี่ยน agent ที่รับสาย: PATCH /numbers/{id} {"agentId": "…"}
  • หมายเลขที่ขึ้นปลายทางตอนโทรออก เลือกได้จากเส้นทางที่ระบบเปิดไว้ (GET /settings → availableOutboundRoutes) ระบุหมายเลขเองไม่ได้

8. ข้อจำกัด

เรื่องค่า
หน้าโทรบนเว็บ: จำนวนสายต่อชั่วโมงตั้งได้ในองค์กร ค่าเริ่มต้น 20 สาย/ชั่วโมง ต่อ IP (0 = ไม่จำกัด)
หน้าโทรบนเว็บ: ความยาวสายค่าเริ่มต้น 5 นาที (ตั้งได้ 30 วินาที–60 นาที)
สายโทรออกจากแคมเปญยาวสุด 10 นาทีต่อสาย
สายพร้อมกันต่อแคมเปญ1–5
รอสายก่อนนับว่าไม่รับ40 วินาที
สายนอกเวลาทำการแจ้งปิดทำการ ยาวสุด 45 วินาที
รายชื่อต่อคำขอ5,000
อายุ access token24 ชั่วโมง

9. ยังไม่รองรับในตอนนี้

เขียนไว้ให้ชัดเพื่อวางแผนการเชื่อมระบบได้ถูก ถ้าต้องการข้อไหน แจ้งทีมงานได้

  • API key สำหรับเซิร์ฟเวอร์ (ตอนนี้ใช้ login + JWT)
  • webhook แจ้งเมื่อวางสายหรือได้ข้อมูลลูกค้า (ตอนนี้ดึงเป็นรอบ)
  • endpoint สั่งโทรออกทันทีทีละเบอร์ (ตอนนี้ใช้แคมเปญ 1 รายชื่อ)
  • การแบ่งหน้าและตัวกรองของ /calls
  • ส่งข้อมูลลูกค้าเข้าไปในสายผ่านลิงก์โทรบนเว็บ (ตอนนี้รองรับแค่ ?theme=)
  • เริ่มแคมเปญตามเวลาที่ตั้งไว้ และโทรซ้ำอัตโนมัติ

ดู endpoint ทั้งหมดได้ที่ Swagger: https://api.agenticos.tech/docs