ส่งข้อมูลสายเข้าระบบของคุณ
https://api.agenticos.tech/api/public/v1 · ข้อมูลใช้ snake_case และไม่เปลี่ยนชื่อฟิลด์เดิม (อาจเพิ่มฟิลด์ใหม่ได้)1. ภาพรวม: เลือกแบบไหนดี
| ต้องการ | ใช้ | ตั้งค่าที่ |
|---|---|---|
| ให้ระบบของเราได้ข้อมูลทันทีที่จบสาย | Webhook (call.ended) | ตั้งค่า → API & การเชื่อมต่อ → Webhook |
| รู้ทันทีเมื่อมีสายเข้า หรือลูกค้าขอคุยกับคนระหว่างสาย | Webhook (call.started, call.handoff_requested) หรือแจ้งเตือน LINE | ตั้งค่า → API & การเชื่อมต่อ |
| ดึงย้อนหลัง หรือดึงเป็นรอบ (เช่น ทุกคืน) | API (GET /voice/calls) | ตั้งค่า → API & การเชื่อมต่อ → API key |
| ไฟล์เสียงของสาย | API (GET /voice/calls/{id} → recording_url) | API key |
| ข้อมูลทั้งหมดขององค์กรเป็นไฟล์เดียว | ส่งออกเป็น ZIP | ตั้งค่า → API & การเชื่อมต่อ (เจ้าของบัญชี) |
2. สร้าง API key
- เข้าแอปด้วยบัญชีเจ้าของหรือแอดมินองค์กร แล้วไปที่ ตั้งค่า → API & การเชื่อมต่อ
- ในการ์ด API key กด "สร้าง key" ตั้งชื่อให้รู้ว่าใช้กับระบบไหน เช่น "CRM"
- คัดลอก key (ขึ้นต้นด้วย agos_) เก็บในเซิร์ฟเวอร์ของคุณทันที ระบบแสดง key เต็มครั้งเดียว เราเก็บแค่ค่า hash
- ถ้า key หลุด กด "ยกเลิก" แล้วสร้างใหม่ key เดิมใช้ไม่ได้ทันที
ใช้ key แยกกันต่อระบบ เพื่อยกเลิกได้ทีละระบบ และดูได้ว่าแต่ละ key ถูกใช้ล่าสุดเมื่อไร
3. การยืนยันสิทธิ์
ส่ง key ใน header แบบใดแบบหนึ่ง:
Authorization: Bearer agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| สถานะ | ความหมาย |
|---|---|
| 200 | สำเร็จ |
| 401 | ไม่ได้ส่ง key, key ผิด หรือ key ถูกยกเลิกแล้ว |
| 403 | key ไม่มีสิทธิ์ voice:read |
| 404 | ไม่พบสายนี้ในองค์กรของ key |
| 422 | พารามิเตอร์หรือ cursor ไม่ถูกต้อง |
ข้อผิดพลาดตอบเป็น {"detail": "..."}
4. ดึงรายการสาย
GET/api/public/v1/voice/calls
สายขององค์กรเจ้าของ key เรียงจากใหม่ไปเก่า ไม่รวมสายที่กำลังคุยอยู่และสายที่ถูกลบ รายการนี้ไม่มีบทสนทนา (ดึงจากข้อ 5)
| พารามิเตอร์ | ชนิด | คำอธิบาย |
|---|---|---|
since | ISO-8601 | เฉพาะสายที่สร้างตั้งแต่เวลานี้ เช่น 2026-10-01T00:00:00+07:00 |
until | ISO-8601 | เฉพาะสายที่สร้างก่อนเวลานี้ |
direction | inbound | outbound | สายเข้า หรือสายออก |
limit | 1–100 | จำนวนต่อหน้า ค่าเริ่มต้น 50 |
cursor | string | ค่า next_cursor จากหน้าก่อน เพื่อดึงหน้าถัดไป |
curl -s "https://api.agenticos.tech/api/public/v1/voice/calls?since=2026-10-01T00:00:00%2B07:00&limit=50" \
-H "Authorization: Bearer $AGENTICOS_API_KEY"{
"data": [ { …call (section 6)… }, { … } ],
"next_cursor": "WyIyMDI2LTEwLTA5VDA5OjI5OjIzIiwgIjEzMTVmODQzLi4uIl0="
}ถ้า next_cursor เป็น null แปลว่าหมดแล้ว ถ้าดึงเป็นรอบ ให้จำเวลาของสายล่าสุดที่ได้ แล้วใช้เป็น since ของรอบถัดไป
5. ดึงสายเดียว พร้อมบทสนทนาและไฟล์เสียง
GET/api/public/v1/voice/calls/{id}
curl -s "https://api.agenticos.tech/api/public/v1/voice/calls/1315f843-00df-4350-9d72-a29c0cdbd8f1" \
-H "X-API-Key: $AGENTICOS_API_KEY"ได้ข้อมูลสาย (ข้อ 6) บวกอีก 2 ฟิลด์:
{
…call fields…,
"transcript": [
{ "seq": 1, "who": "agent", "at": "00:00", "text": "สวัสดีค่ะ ศูนย์บริการลูกค้า ยินดีให้บริการค่ะ" },
{ "seq": 2, "who": "customer", "at": "00:06", "text": "อยากได้กล่องลูกฟูกใส่ขนมครับ" }
],
"recording_url": "https://…/call-1315f843….wav?X-Amz-…"
}- transcript ถอดจากไฟล์เสียงทั้งสายหลังวางสาย who เป็น customer หรือ agent ส่วน at คือเวลาตั้งแต่เริ่มสาย (นาที:วินาที)
- recording_url เป็นลิงก์ดาวน์โหลดไฟล์ WAV ใช้ได้ 1 ชั่วโมง ถ้าต้องการเก็บ ให้ดาวน์โหลดไปเก็บเอง เป็น null ถ้าสายนั้นไม่มีไฟล์เสียง
6. ฟิลด์ของสาย
{
"id": "1315f843-00df-4350-9d72-a29c0cdbd8f1",
"direction": "inbound",
"channel": "phone",
"mode": "ai",
"status": "completed",
"phone": "0812345678",
"caller": "0812345678",
"agent": { "id": "eea31038-5836-4bd5-a941-bd98f5b86a2a", "name": "ศูนย์บริการลูกค้า (AI)" },
"started_at": "2026-10-09T09:29:23.392000+00:00",
"duration_sec": 95,
"urgent": false,
"summary": {
"text": "ลูกค้าสอบถามกล่องลูกฟูกสำหรับใส่ขนม ประมาณ 5,000 ใบต่อเดือน และขอให้ฝ่ายขายติดต่อกลับ",
"key_points": ["กล่องลูกฟูกใส่ขนม", "5,000 ใบต่อเดือน"],
"outcome": "ส่งต่อฝ่ายขายกล่องลูกฟูก",
"next_action": "ฝ่ายขายโทรกลับพร้อมใบเสนอราคา",
"sentiment": "positive",
"emotion": "สนใจ",
"needs_human": false,
"urgent_reason": ""
},
"collected": {
"intent": "สอบถามสินค้า/ขอใบเสนอราคา",
"customer_name": "สมชาย",
"company": "เอบีซีฟู้ด",
"callback_phone": "0812345678"
},
"topics": ["สอบถามราคา"],
"tags": [],
"has_recording": true,
"app_url": "https://app.agenticos.tech/apps/voice-agent/calls/1315f843-00df-4350-9d72-a29c0cdbd8f1"
}| ฟิลด์ | คำอธิบาย |
|---|---|
id | รหัสสาย (ใช้กับ GET /voice/calls/{id}) |
direction | inbound = ลูกค้าโทรเข้า, outbound = โทรออก |
channel | phone = โทรศัพท์, web = คุยผ่านปุ่มบนเว็บ |
mode | ai = AI คุย, manual = พนักงานขายโทรเองผ่านผู้ช่วยโทรขาย |
phone | เบอร์ผู้โทร (สายเข้า) หรือเบอร์ที่โทรไป (สายออก) |
agent | AI agent ที่คุยสายนี้ ({id, name}) หรือ null |
started_at / duration_sec | เวลาเริ่มสาย (UTC) และความยาวเป็นวินาที |
urgent | AI ประเมินว่าลูกค้าต้องการคุยกับคน หรือเป็นเรื่องด่วน |
summary | สรุปจาก AI: text, key_points, outcome, next_action, sentiment (positive/neutral/negative), emotion, needs_human, urgent_reason |
collected | ข้อมูลที่ AI เก็บได้ ชื่อฟิลด์ตรงกับ "ตัวแปรที่ต้องการเก็บ" ที่ตั้งใน agent ของคุณเอง เช่น customer_name, callback_phone |
topics | หมวดเรื่องที่ลูกค้าโทรมา (AI จัดหมวดหลังวางสายไม่กี่นาที อาจยังว่างใน webhook) |
has_recording / app_url | มีไฟล์เสียงไหม และลิงก์ไปหน้าสายนี้ในแอป |
7. Webhook: ให้เราส่งข้อมูลไปหาคุณ
- เตรียม URL ฝั่งคุณที่รับ POST ได้ ต้องเป็น https และเข้าถึงได้จากอินเทอร์เน็ต
- ไปที่ ตั้งค่า → API & การเชื่อมต่อ → Webhook → เพิ่ม endpoint แล้วเลือกเหตุการณ์ที่ต้องการ
- คัดลอก signing secret (ขึ้นต้นด้วย whsec_) เก็บในเซิร์ฟเวอร์ ใช้ตรวจว่าข้อมูลมาจาก AgenticOS จริง (ข้อ 9) ระบบแสดงครั้งเดียว กด "เปลี่ยน secret" ได้ภายหลัง
- กด "ทดสอบส่ง" ระบบจะส่งเหตุการณ์ test ไปที่ URL ของคุณ ผลการส่งดูได้ในประวัติการส่งของ endpoint นั้น
แต่ละ endpoint มีสวิตช์เปิด/ปิด และเลือกเหตุการณ์ได้เอง องค์กรหนึ่งมีได้สูงสุด 5 endpoint
รูปแบบคำขอที่เราส่ง
POST https://your-server.example.com/agenticos/webhook
Content-Type: application/json
User-Agent: AgenticOS-Webhook/1.0
X-AgenticOS-Event: call.ended
X-AgenticOS-Delivery: 7b0c2b1e-4f7d-4a63-9d0e-2f4b8e1c9a10
X-AgenticOS-Signature: t=1760001234,v1=5f2b…c9
{
"id": "7b0c2b1e-4f7d-4a63-9d0e-2f4b8e1c9a10",
"event": "call.ended",
"created_at": "2026-10-09T09:31:02.118000+00:00",
"organization_id": "7dc951c1-9cbc-4571-b56a-cd2bd7979f44",
"data": { … }
}8. เหตุการณ์และข้อมูลใน data
| เหตุการณ์ | ส่งเมื่อ | data |
|---|---|---|
call.started | มีสายเข้าถึง AI (โทรศัพท์หรือเว็บ) | {live_id, direction, channel, phone, agent, started_at} |
call.handoff_requested | ระหว่างสาย AI กำลังโอนสายให้คน เพราะลูกค้าขอคุยกับเจ้าหน้าที่ หรือผู้ดูแลกดโอน | {live_id, channel, phone, agent} |
call.ended | บันทึกสายเสร็จ (มีสรุป ข้อมูลที่เก็บได้ และบทสนทนาแล้ว) ทั้งสายเข้าและสายออกที่ AI คุย | {call: …call + transcript} |
callpilot.ended | สายที่พนักงานขายโทรเองผ่านผู้ช่วยโทรขายจบ (ต้องติ๊กเลือกเอง) | {call: …} mode = "manual" |
test | กดทดสอบส่งในหน้าตั้งค่า | {message} |
call.started
"data": {
"live_id": "9eea211e4a484e3aaf416cf58fd96c8e",
"direction": "inbound",
"channel": "phone",
"phone": "0812345678",
"agent": { "id": "eea31038-…", "name": "ศูนย์บริการลูกค้า (AI)" },
"started_at": "2026-10-09T09:29:23.392Z"
}call.handoff_requested
"data": {
"live_id": "9eea211e4a484e3aaf416cf58fd96c8e",
"channel": "phone",
"phone": "0812345678",
"agent": { "id": "eea31038-…", "name": "ศูนย์บริการลูกค้า (AI)" }
}live_id ใช้จับคู่ call.started กับ call.handoff_requested ของสายเดียวกัน ส่วน call.ended ใช้ id ของสายที่บันทึกแล้ว
call.ended / callpilot.ended
"data": {
"call": {
…every field in section 6…,
"transcript": [ { "seq": 1, "who": "agent", "at": "00:00", "text": "…" } ]
}
}9. ตรวจลายเซ็น
ทุกคำขอมี header X-AgenticOS-Signature: t=<unix เวลา>,v1=<hex> โดย v1 = HMAC-SHA256(signing secret, "<t>.<body ดิบ>") ฝั่งคุณควร:
- คำนวณจาก body ดิบก่อนแปลง JSON (ถ้าแปลงแล้วแปลงกลับ ลายเซ็นจะไม่ตรง)
- เทียบด้วยฟังก์ชันแบบ constant-time และปฏิเสธถ้า t เก่ากว่า 5 นาที เพื่อกันการส่งซ้ำจากคนอื่น
- ใช้ id (เท่ากับ X-AgenticOS-Delivery) กันประมวลผลซ้ำ เพราะเหตุการณ์เดิมอาจถูกส่งมากกว่า 1 ครั้งเมื่อมีการส่งซ้ำ
Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.AGENTICOS_WEBHOOK_SECRET; // whsec_...
// Read the RAW body — the signature is over the exact bytes we sent.
app.post("/agenticos/webhook", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("X-AgenticOS-Signature") || ""; // t=1760000000,v1=ab12...
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", SECRET)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // 5 min, against replays
const ok = fresh && typeof parts.v1 === "string" && parts.v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body);
// event.id is unique per delivery — store it and skip repeats (retries).
if (event.event === "call.ended") {
const call = event.data.call;
console.log(call.phone, call.summary.text, call.collected);
}
res.status(200).end(); // answer fast; do slow work in a queue
});
app.listen(3000);Python (Flask)
import hashlib, hmac, json, os, time
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["AGENTICOS_WEBHOOK_SECRET"].encode() # whsec_...
@app.post("/agenticos/webhook")
def agenticos_webhook():
raw = request.get_data() # exact bytes, before parsing
parts = dict(p.split("=", 1) for p in request.headers.get("X-AgenticOS-Signature", "").split(",") if "=" in p)
t = parts.get("t", "")
expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest()
if not t.isdigit() or abs(time.time() - int(t)) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
abort(401)
event = json.loads(raw)
if event["event"] == "call.ended":
call = event["data"]["call"]
print(call["phone"], call["summary"]["text"], call["collected"])
return "", 20010. การตอบกลับและการส่งซ้ำ
- ตอบ HTTP 2xx ภายใน 10 วินาที ถือว่ารับสำเร็จ ควรตอบก่อนแล้วค่อยประมวลผลต่อเบื้องหลัง
- ถ้าตอบอย่างอื่น, ไม่ตอบ, หรือเชื่อมต่อไม่ได้ ระบบส่งซ้ำหลัง 1 นาที, 5 นาที, 30 นาที, 2 ชั่วโมง และ 6 ชั่วโมง (รวม 6 ครั้ง) แล้วหยุด
- ระบบไม่ตาม redirect ถ้า URL เปลี่ยน ให้แก้ URL ในหน้าตั้งค่า
- ประวัติการส่งย้อนหลัง 30 วัน ดูได้ต่อ endpoint ในหน้าตั้งค่า (สถานะ, HTTP code, ข้อผิดพลาด, ข้อมูลที่ส่ง)
11. แจ้งเตือนเข้า LINE
ถ้าไม่มีระบบรับ webhook ให้ทีมรับแจ้งเตือนทาง LINE ได้ ระบบส่งผ่าน LINE OA ขององค์กรที่เชื่อมไว้ใน OmniChat
- เชื่อม LINE OA ใน OmniChat ก่อน (ถ้ายังไม่ได้เชื่อม)
- ตั้งค่า → API & การเชื่อมต่อ → แจ้งเตือน LINE → "เชื่อม LINE" จะได้รหัส เช่น AGOS-123456 ใช้ได้ 15 นาที
- ส่งรหัสนั้นเข้า LINE OA จากแชทส่วนตัว หรือในกลุ่มที่เชิญ OA เข้าไปแล้ว แชทหรือกลุ่มนั้นจะได้รับแจ้งเตือน
- เลือกเปิด/ปิด: มีสายเข้า, ลูกค้าขอคุยกับเจ้าหน้าที่ระหว่างสาย, เรื่องด่วน (AI ประเมินหลังจบสาย), ทุกสายเข้าที่จบพร้อมสรุป
ข้อความแจ้งเตือนนับรวมในโควตาข้อความรายเดือนของ LINE OA
12. ขอข้อมูลทั้งหมดออกมา
เจ้าของบัญชีสั่งส่งออกข้อมูลทั้งหมดเป็นไฟล์ ZIP ได้ที่ ตั้งค่า → API & การเชื่อมต่อ ระบบส่งอีเมลแจ้งเมื่อไฟล์พร้อม ดาวน์โหลดได้ 7 วัน ในไฟล์มี:
| โฟลเดอร์ | เนื้อหา |
|---|---|
voice/ | ตั้งค่า agent, ประวัติการโทรพร้อมบทสนทนา สรุป และข้อมูลที่เก็บได้ (calls.jsonl รูปแบบเดียวกับ API และ calls.csv เปิดด้วย Excel ได้), ไฟล์เสียงของแต่ละสาย (เลือกไม่รวมได้) |
chat/ | ช่องทางแชท (ไม่รวมรหัสผ่านหรือโทเคน), บทสนทนาพร้อมข้อความทั้งหมด, ไฟล์แนบ |
knowledge/ | คลังความรู้, เนื้อหาเอกสารแต่ละฉบับ (.txt), ไฟล์ต้นฉบับที่อัปโหลด |
README.txt | คำอธิบายไฟล์และจำนวนข้อมูล |
13. ข้อจำกัดและความปลอดภัย
- API key และ signing secret ใช้ได้ในเซิร์ฟเวอร์เท่านั้น ห้ามใส่ในแอปมือถือหรือหน้าเว็บ
- API ตอนนี้เป็นแบบอ่านอย่างเดียว (voice:read) ดึงได้ครั้งละไม่เกิน 100 สาย
- Webhook ต้องเป็น https และ host ต้องเป็น IP สาธารณะ ระบบไม่ส่งเข้า IP ภายในหรือ localhost
- สูงสุด 5 webhook endpoint ต่อองค์กร
- ลิงก์ไฟล์เสียงใช้ได้ 1 ชั่วโมง และไฟล์เสียงในระบบถูกลบตามระยะเวลาจัดเก็บที่องค์กรตั้งไว้ (ตั้งค่า Voice Agent → PDPA)
ดู endpoint ทั้งหมดใน Swagger หัวข้อ public-api: api.agenticos.tech/docs
ต้องการความช่วยเหลือในการเชื่อมระบบ ติดต่อ [email protected]