Developers · API & webhooks

Call data, in your own systems

After every call AgenticOS has the caller's number, a summary, the data the AI captured and the full transcript. Your systems can pull it with the API, or have us push it the moment the call ends by webhook — into your CRM, ERP or ticketing.
Base URL 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 wantUseSet up in
Your system to get the data the moment a call endsWebhook (call.ended)Settings → API & Integrations → Webhooks
To know right away when a call comes in or a caller asks for a personWebhook (call.started, call.handoff_requested) or LINE alertsSettings → API & Integrations
To pull history, or on a schedule (e.g. nightly)API (GET /voice/calls)Settings → API & Integrations → API keys
A call's audioAPI (GET /voice/calls/{id} → recording_url)API key
All of the organization's data in one fileZIP exportSettings → API & Integrations (owner)
The webhook and the API carry the same call object, so one parser on your side handles both.

2. Create an API key

  1. Sign in as an owner or admin and open Settings → API & Integrations
  2. In the API keys card press "Create key" and name it after the system that will use it, e.g. "CRM"
  3. Copy the key (it starts with agos_) into your server's secrets now — it is shown once; we only keep a hash
  4. 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:

http
Authorization: Bearer agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: agos_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
StatusMeaning
200OK
401No key, a wrong key, or a revoked key
403The key lacks the voice:read scope
404No such call in the key's organization
422A 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).

ParameterTypeDescription
sinceISO-8601Calls created at or after this time, e.g. 2026-10-01T00:00:00+07:00
untilISO-8601Calls created before this time
directioninbound | outboundInbound or outbound
limit1–100Page size, default 50
cursorstringnext_cursor from the previous page
bash
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"
json
{
  "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}

bash
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:

json
{
  …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

json
{
  "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"
}
FieldDescription
idCall id (for GET /voice/calls/{id})
directioninbound = the customer called, outbound = we called
channelphone, or web (the talk button on a website)
modeai = the AI talked, manual = a sales rep through Sales Callpilot
phoneThe caller's number (inbound) or the number dialled (outbound)
agentThe AI agent on the call ({id, name}) or null
started_at / duration_secStart time (UTC) and length in seconds
urgentThe AI judged the caller needs a person / it's urgent
summaryAI summary: text, key_points, outcome, next_action, sentiment (positive/neutral/negative), emotion, needs_human, urgent_reason
collectedCaptured data, keyed by the extract variables you set on the agent, e.g. customer_name, callback_phone
topicsWhat the call was about (labelled a few minutes after the call — may still be empty in the webhook)
has_recording / app_urlWhether audio exists, and the call's page in the app

7. Webhooks: we push to you

  1. Prepare a URL on your side that accepts POST — https, reachable from the internet
  2. Open Settings → API & Integrations → Webhooks → add an endpoint and pick its events
  3. Copy the signing secret (whsec_…) into your server — it proves a request came from us (section 9). Shown once; rotate it any time
  4. 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

http
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

EventSent whendata
call.startedAn inbound call reaches the AI (phone or web){live_id, direction, channel, phone, agent, started_at}
call.handoff_requestedMid-call, the AI is handing the caller to a person (they asked, or a supervisor pressed transfer){live_id, channel, phone, agent}
call.endedThe call is saved with summary, captured data and transcript — inbound and outbound AI calls{call: …call + transcript}
callpilot.endedA Sales Callpilot call (placed by a rep) ended — opt in per endpoint{call: …} mode = "manual"
testYou pressed Send test{message}

call.started

json
"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

json
"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

json
"data": {
  "call": {
    …every field in section 6…,
    "transcript": [ { "seq": 1, "who": "agent", "at": "00:00", "text": "…" } ]
  }
}
Webhooks never carry the audio link (it is short-lived) — call GET /voice/calls/{id} with the id. Sales Callpilot audio uploads a few seconds after the call, so its webhook may say has_recording: false.

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)

javascript
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)

python
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 "", 200

10. 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.

  1. Connect your LINE OA in OmniChat (if you haven't)
  2. Settings → API & Integrations → LINE alerts → "Connect LINE" gives a code such as AGOS-123456, valid 15 minutes
  3. 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
  4. 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:

FolderContents
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.txtWhat 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]