Call data, in your own systems
https://api.agenticos.tech/api/public/v1 · Fields are snake_case; existing names never change (new fields may be added)1. Overview: which one to use
| You want | Use | Set up in |
|---|---|---|
| Your system to get the data the moment a call ends | Webhook (call.ended) | Settings → API & Integrations → Webhooks |
| To know right away when a call comes in or a caller asks for a person | Webhook (call.started, call.handoff_requested) or LINE alerts | Settings → API & Integrations |
| To pull history, or on a schedule (e.g. nightly) | API (GET /voice/calls) | Settings → API & Integrations → API keys |
| A call's audio | API (GET /voice/calls/{id} → recording_url) | API key |
| All of the organization's data in one file | ZIP export | Settings → API & Integrations (owner) |
2. Create an API key
- Sign in as an owner or admin and open Settings → API & Integrations
- In the API keys card press "Create key" and name it after the system that will use it, e.g. "CRM"
- Copy the key (it starts with agos_) into your server's secrets now — it is shown once; we only keep a hash
- If a key leaks, revoke it and create a new one — the old key stops working immediately
Use one key per system so you can revoke them one at a time and see when each was last used.
3. Authentication
Send the key in either header:
Authorization: Bearer agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| Status | Meaning |
|---|---|
| 200 | OK |
| 401 | No key, a wrong key, or a revoked key |
| 403 | The key lacks the voice:read scope |
| 404 | No such call in the key's organization |
| 422 | A bad parameter or cursor |
Errors come back as {"detail": "..."}
4. List calls
GET/api/public/v1/voice/calls
The key's organization's calls, newest first, excluding calls in progress and deleted calls. No transcripts here (see section 5).
| Parameter | Type | Description |
|---|---|---|
since | ISO-8601 | Calls created at or after this time, e.g. 2026-10-01T00:00:00+07:00 |
until | ISO-8601 | Calls created before this time |
direction | inbound | outbound | Inbound or outbound |
limit | 1–100 | Page size, default 50 |
cursor | string | next_cursor from the previous page |
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="
}A null next_cursor means there are no more. For scheduled pulls, remember the newest call's time and pass it as since next time.
5. Get one call, with transcript and audio
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"Returns the call (section 6) plus two fields:
{
…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 is transcribed from the whole recording after the call; who is customer or agent, at is mm:ss from the start
- recording_url is a WAV download link valid for 1 hour — download it to keep it; null when the call has no audio
6. Call fields
{
"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"
}| Field | Description |
|---|---|
id | Call id (for GET /voice/calls/{id}) |
direction | inbound = the customer called, outbound = we called |
channel | phone, or web (the talk button on a website) |
mode | ai = the AI talked, manual = a sales rep through Sales Callpilot |
phone | The caller's number (inbound) or the number dialled (outbound) |
agent | The AI agent on the call ({id, name}) or null |
started_at / duration_sec | Start time (UTC) and length in seconds |
urgent | The AI judged the caller needs a person / it's urgent |
summary | AI summary: text, key_points, outcome, next_action, sentiment (positive/neutral/negative), emotion, needs_human, urgent_reason |
collected | Captured data, keyed by the extract variables you set on the agent, e.g. customer_name, callback_phone |
topics | What the call was about (labelled a few minutes after the call — may still be empty in the webhook) |
has_recording / app_url | Whether audio exists, and the call's page in the app |
7. Webhooks: we push to you
- Prepare a URL on your side that accepts POST — https, reachable from the internet
- Open Settings → API & Integrations → Webhooks → add an endpoint and pick its events
- Copy the signing secret (whsec_…) into your server — it proves a request came from us (section 9). Shown once; rotate it any time
- Press "Send test" — a test event goes to your URL and shows up in that endpoint's delivery log
Each endpoint can be switched off and subscribes to its own events. Up to 5 endpoints per organization.
What each request looks like
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. Events and their data
| Event | Sent when | data |
|---|---|---|
call.started | An inbound call reaches the AI (phone or web) | {live_id, direction, channel, phone, agent, started_at} |
call.handoff_requested | Mid-call, the AI is handing the caller to a person (they asked, or a supervisor pressed transfer) | {live_id, channel, phone, agent} |
call.ended | The call is saved with summary, captured data and transcript — inbound and outbound AI calls | {call: …call + transcript} |
callpilot.ended | A Sales Callpilot call (placed by a rep) ended — opt in per endpoint | {call: …} mode = "manual" |
test | You pressed Send 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 ties call.started to call.handoff_requested for the same call; call.ended carries the saved call's id.
call.ended / callpilot.ended
"data": {
"call": {
…every field in section 6…,
"transcript": [ { "seq": 1, "who": "agent", "at": "00:00", "text": "…" } ]
}
}9. Verify the signature
Every request carries X-AgenticOS-Signature: t=<unix time>,v1=<hex> where v1 = HMAC-SHA256(signing secret, "<t>.<raw body>"). On your side:
- Compute it over the raw body, before JSON parsing (re-serialized JSON won't match)
- Compare in constant time, and reject a t older than 5 minutes (replay protection)
- Use id (= X-AgenticOS-Delivery) to skip duplicates — a retried event can arrive more than once
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. Responses and retries
- Answer HTTP 2xx within 10 seconds to acknowledge — reply first, process in the background
- Anything else, a timeout or a connection error is retried after 1 min, 5 min, 30 min, 2 h and 6 h (6 attempts in all), then given up
- Redirects are not followed — update the URL in Settings instead
- A 30-day delivery log per endpoint is in Settings (status, HTTP code, error, payload)
11. LINE alerts
No webhook receiver? The team can get alerts in LINE instead, sent through the organization's own LINE OA connected in OmniChat.
- Connect your LINE OA in OmniChat (if you haven't)
- Settings → API & Integrations → LINE alerts → "Connect LINE" gives a code such as AGOS-123456, valid 15 minutes
- Send it to the OA from a 1:1 chat, or in a group the OA has joined — that chat or group starts receiving alerts
- Switch on what you want: a call comes in, a caller asks for a person mid-call, urgent (judged after the call), every finished inbound call with its summary
Alerts count toward the LINE OA's monthly message quota.
12. Export everything
The account owner can export everything as a ZIP from Settings → API & Integrations. We e-mail when it's ready; it stays downloadable for 7 days. It contains:
| Folder | Contents |
|---|---|
voice/ | Agent settings, call history with transcripts, summaries and captured data (calls.jsonl in the API's shape, and calls.csv for spreadsheets), each call's audio (optional) |
chat/ | Chat channels (no credentials), every conversation with its messages, attachments |
knowledge/ | Knowledge collections, each document's text (.txt), the originally uploaded files |
README.txt | What each file is, and the counts |
13. Limits and security
- API keys and signing secrets belong on servers only — never in a mobile app or web page
- The API is read-only today (voice:read), at most 100 calls per page
- Webhook URLs must be https on a public host — internal IPs and localhost are refused
- Up to 5 webhook endpoints per organization
- Audio links last 1 hour; stored audio is deleted after the organization's retention period (Voice Agent settings → PDPA)
Every endpoint is in Swagger under public-api: api.agenticos.tech/docs
Need help with an integration? Write to [email protected]