Geliştiriciler
Ajanları, numaraları ve görüşmeleri kendi sisteminizden yönetin; sesli görüşmeyi kendi sitenize gömün.
Genel bakış
API bir REST API'dir. İstek ve yanıt gövdeleri JSON (UTF-8), zaman damgaları unix saniyesi, tutarlar USD'dir. Taban adres:
https://hubvocal.com/v1
Bir API anahtarı tek bir organizasyona aittir ve o organizasyonda yönetici yetkisiyle çalışır. Panelde yapabildiğiniz her şeyi yapabilirsiniz:
| Kaynak | Uçlar |
|---|---|
| Ajanlar | /v1/agents, /v1/agents/{id} |
| Bilgi bankası | /v1/knowledge, /v1/knowledge/{id}, /v1/knowledge/search |
| Araçlar | /v1/tools, /v1/tools/{id}, /v1/tools/{id}/test |
| MCP sunucuları | /v1/mcp-servers, /v1/mcp-servers/{id}, /v1/mcp-servers/{id}/sync |
| Webhook'lar | /v1/webhooks, /v1/webhooks/{id}, /v1/webhooks/{id}/deliveries |
| Sesler ve motorlar | /v1/voices |
| Web görüşmesi | /v1/web-calls |
| Görüşmeler ve giden arama | /v1/calls, /v1/calls/{room} |
| Numaralar | /v1/numbers, /v1/numbers/{number} |
| Tüketim | /v1/usage |
Makine tarafından okunabilir OpenAPI 3.1 tanımı GET /v1/openapi.json adresinde; anahtar gerekmez. Her ucun ayrıntısı için API başvurusu sayfasına bakın.
Hızlı başlangıç
-
API anahtarı oluşturun
Panelde Ayarlar > API anahtarları bölümünü açın ve anahtara ne için kullanacağınızı anlatan bir ad verin. Anahtarı yalnız organizasyon sahipleri ve yöneticileri oluşturabilir. Gizli değer (
vt_live_…) yalnız bir kez gösterilir; hemen bir sır yöneticisine kaydedin.export VT_API_KEY="vt_live_…" -
Ajanlarınızı listeleyin
Anahtarın çalıştığını görmenin en kısa yolu. Yanıttaki id'yi sonraki adımlarda kullanacaksınız.
curlcurl "https://hubvocal.com/v1/agents" \ -H "Authorization: Bearer $VT_API_KEY"JavaScriptconst res = await fetch("https://hubvocal.com/v1/agents", { headers: { Authorization: `Bearer ${process.env.VT_API_KEY}`, }, }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status}: ${data.detail}`); console.log(data);Pythonimport os import requests resp = requests.get( "https://hubvocal.com/v1/agents", headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"}, timeout=30, ) if not resp.ok: raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}") print(resp.json()) -
Bir ajan oluşturun
Motor, dil, ses ve talimat tek bir nesnede. Geçerli ses adları ve sınırlar için
GET /v1/voices.curlcurl -X POST "https://hubvocal.com/v1/agents" \ -H "Authorization: Bearer $VT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Resepsiyon", "lang": "tr", "engine": "live", "instructions": "Bir diş kliniğinin resepsiyonistisin. Randevu almak isteyenlere yardım et, genel soruları kısa yanıtla. Bilmediğin bir şeyi uydurma; ilgili kişinin geri döneceğini söyle.", "greeting": "Merhaba, kliniğimizi aradığınız için teşekkürler. Size nasıl yardımcı olabilirim?", "voice": { "voice": "marin", "speed": 1 }, "tools": { "builtins": [ "end_call", "take_message" ], "webhooks": [ "tl_8f3a2c" ] }, "turn": { "first_speaker": "agent", "idle_check_s": 30 }, "recording": { "enabled": false, "announce": true }, "post_call": { "summary": true, "fields": [ { "name": "appointment_date", "type": "date", "description": "Requested appointment date, if any" }, { "name": "callback", "type": "boolean", "description": "Does the caller want a callback?" } ] }, "knowledge": { "doc_ids": [ "kb_7d21e4" ], "mode": "tool" } }'JavaScriptconst res = await fetch("https://hubvocal.com/v1/agents", { method: "POST", headers: { Authorization: `Bearer ${process.env.VT_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "name": "Resepsiyon", "lang": "tr", "engine": "live", "instructions": "Bir diş kliniğinin resepsiyonistisin. Randevu almak isteyenlere yardım et, genel soruları kısa yanıtla. Bilmediğin bir şeyi uydurma; ilgili kişinin geri döneceğini söyle.", "greeting": "Merhaba, kliniğimizi aradığınız için teşekkürler. Size nasıl yardımcı olabilirim?", "voice": { "voice": "marin", "speed": 1 }, "tools": { "builtins": [ "end_call", "take_message" ], "webhooks": [ "tl_8f3a2c" ] }, "turn": { "first_speaker": "agent", "idle_check_s": 30 }, "recording": { "enabled": false, "announce": true }, "post_call": { "summary": true, "fields": [ { "name": "appointment_date", "type": "date", "description": "Requested appointment date, if any" }, { "name": "callback", "type": "boolean", "description": "Does the caller want a callback?" } ] }, "knowledge": { "doc_ids": [ "kb_7d21e4" ], "mode": "tool" } }), }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status}: ${data.detail}`); console.log(data);Pythonimport os import requests resp = requests.post( "https://hubvocal.com/v1/agents", headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"}, json={ "name": "Resepsiyon", "lang": "tr", "engine": "live", "instructions": "Bir diş kliniğinin resepsiyonistisin. Randevu almak isteyenlere yardım et, genel soruları kısa yanıtla. Bilmediğin bir şeyi uydurma; ilgili kişinin geri döneceğini söyle.", "greeting": "Merhaba, kliniğimizi aradığınız için teşekkürler. Size nasıl yardımcı olabilirim?", "voice": { "voice": "marin", "speed": 1, }, "tools": { "builtins": [ "end_call", "take_message", ], "webhooks": [ "tl_8f3a2c", ], }, "turn": { "first_speaker": "agent", "idle_check_s": 30, }, "recording": { "enabled": False, "announce": True, }, "post_call": { "summary": True, "fields": [ { "name": "appointment_date", "type": "date", "description": "Requested appointment date, if any", }, { "name": "callback", "type": "boolean", "description": "Does the caller want a callback?", }, ], }, "knowledge": { "doc_ids": [ "kb_7d21e4", ], "mode": "tool", }, }, timeout=30, ) if not resp.ok: raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}") print(resp.json()) -
Web görüşmesi açın
Yanıttaki
server_urlvetokenile tarayıcıdan bağlanırsınız; ayrıntı aşağıda.curlcurl -X POST "https://hubvocal.com/v1/web-calls" \ -H "Authorization: Bearer $VT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "resepsiyon" }'JavaScriptconst res = await fetch("https://hubvocal.com/v1/web-calls", { method: "POST", headers: { Authorization: `Bearer ${process.env.VT_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "agent_id": "resepsiyon" }), }); const data = await res.json(); if (!res.ok) throw new Error(`${res.status}: ${data.detail}`); console.log(data);Pythonimport os import requests resp = requests.post( "https://hubvocal.com/v1/web-calls", headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"}, json={ "agent_id": "resepsiyon", }, timeout=30, ) if not resp.ok: raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}") print(resp.json())
Kimlik doğrulama
Her istekte anahtarı Authorization başlığında Bearer olarak gönderin:
Authorization: Bearer vt_live_… - Anahtar organizasyonunuzda yönetici yetkisiyle çalışır: ajan oluşturur, numara atar, arama başlatır. Ona bir parola gibi davranın.
- Anahtarı tarayıcı koduna, mobil uygulamaya ya da depoya koymayın. Tarayıcıdan görüşme için sunucunuzda kısa ömürlü token alın.
- Panelde Ayarlar > API anahtarları listesinde her anahtarın son kullanım zamanını görürsünüz. İptal ettiğiniz anahtar anında geçersiz olur.
- Entegrasyon başına ayrı anahtar kullanın; birini iptal etmek diğerlerini bozmaz.
Web görüşmesini gömme
Ziyaretçileriniz ajanla doğrudan sitenizden, tarayıcıdan konuşabilir. Akış üç adımlı:
- Ziyaretçi düğmeye basar; sayfanız kendi sunucunuza istek atar.
- Sunucunuz API anahtarıyla
POST /v1/web-callsçağırır ve yanıtı (server_url,token,room,max_seconds) sayfaya döner. - Sayfa
livekit-clientile odaya bağlanır ve mikrofonu açar. Ajan odaya kendisi katılır ve karşılama cümlesini söyler.
1. Sunucu: token alın
// server.js (Node 18+, Express)
import express from "express";
const app = express();
app.post("/voice/session", async (req, res) => {
// Kendi kimlik doğrulamanızı ve hız sınırınızı burada uygulayın.
const r = await fetch("https://hubvocal.com/v1/web-calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ agent_id: "resepsiyon" }),
});
res.status(r.status).json(await r.json());
});
app.listen(3000); # app.py (Flask)
import os
import requests
from flask import Flask, jsonify
app = Flask(__name__)
@app.post("/voice/session")
def voice_session():
# Kendi kimlik doğrulamanızı ve hız sınırınızı burada uygulayın.
r = requests.post(
"https://hubvocal.com/v1/web-calls",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={"agent_id": "resepsiyon"},
timeout=15,
)
return jsonify(r.json()), r.status_code 2. Tarayıcı: bağlanın
Paket yöneticisi kullanıyorsanız npm install livekit-client@2.23.0 ile kurup aynı adları içe aktarabilirsiniz. Aşağıdaki örnek CDN'den sabit sürümü yükler.
<button id="talk" type="button">Konuşmaya başla</button>
<script type="module">
import { Room, RoomEvent, Track } from "https://cdn.jsdelivr.net/npm/livekit-client@2.23.0/dist/livekit-client.esm.mjs";
const button = document.getElementById("talk");
let room;
button.addEventListener("click", async () => {
if (room) {
await room.disconnect();
return;
}
button.disabled = true;
// Sunucunuz POST /v1/web-calls yanıtını olduğu gibi döner.
const res = await fetch("/voice/session", { method: "POST" });
const session = await res.json();
if (!res.ok) {
button.disabled = false;
alert(session.detail);
return;
}
room = new Room();
// Ajanın sesi gelince bir <audio> öğesine bağla.
room.on(RoomEvent.TrackSubscribed, (track) => {
if (track.kind === Track.Kind.Audio) document.body.appendChild(track.attach());
});
room.on(RoomEvent.Disconnected, () => {
room?.remoteParticipants.forEach((p) =>
p.audioTrackPublications.forEach((pub) => pub.track?.detach().forEach((el) => el.remove())),
);
room = undefined;
button.textContent = "Konuşmaya başla";
});
await room.connect(session.server_url, session.token);
await room.localParticipant.setMicrophoneEnabled(true);
button.disabled = false;
button.textContent = "Görüşmeyi bitir";
});
</script> Bağlantıyı bir kullanıcı hareketiyle (tıklama) başlatın: tarayıcılar mikrofon iznini ve otomatik ses çalmayı buna bağlar. Sayfa HTTPS üzerinden sunulmalı.
Görüşme en fazla max_seconds sürer, sonra ajan kapatır. Görüşme bitince kaydını GET /v1/calls/{room} ile alabilirsiniz.
Giden arama
Organizasyonunuzun numaralarından biriyle bir numarayı arayın; cevaplanınca ajan konuşur. from numarası GET /v1/numbers listesinde olmalı. agent_id vermezseniz o numaraya atanmış ajan kullanılır. Numaralar E.164 biçiminde (+90…).
curl -X POST "https://hubvocal.com/v1/calls" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+908501234567",
"to": "+905321234567",
"agent_id": "resepsiyon"
}' const res = await fetch("https://hubvocal.com/v1/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"from": "+908501234567",
"to": "+905321234567",
"agent_id": "resepsiyon"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/calls",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"from": "+908501234567",
"to": "+905321234567",
"agent_id": "resepsiyon",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Yanıt
{
"room": "tel-out-9c41e0b7",
"provider": "didww",
"sip_call_id": "SCL_7mVt2kQb9xRa"
} Yanıttaki room görüşmenin kimliğidir. Görüşme bittiğinde kaydı (döküm, maliyet, kim kapattı) GET /v1/calls/{room} ile gelir. Cevaplanmayan aramalarda ended_by değeri no_answer olur.
Gelen arama
Numaralarınıza gelen aramaya, numaraya atanmış ajan cevap verir. Numaralar DIDWW, Twilio ya da kendi SIP hattınız (Asterisk vb.) üzerinden gelir ve platform ekibi tarafından organizasyonunuza eklenir. Hangi ajanın cevap vereceğini siz seçersiniz:
curl -X PUT "https://hubvocal.com/v1/numbers/%2B908501234567" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "resepsiyon",
"label": "Ana hat"
}' const res = await fetch("https://hubvocal.com/v1/numbers/%2B908501234567", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"agent_id": "resepsiyon",
"label": "Ana hat"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.put(
"https://hubvocal.com/v1/numbers/%2B908501234567",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"agent_id": "resepsiyon",
"label": "Ana hat",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Sürümler ve güvenli yayın
Ajanı düzenlemek taslağını değiştirir. Numaralarınıza gelen aramalar, web görüşmeleri ve giden aramalar yayındaki sürümü kullanır; böylece canlı görüşmeleri etkilemeden taslak üzerinde çalışabilirsiniz.
- Ajanı
PUT /v1/agents/{id}ile düzenleyin. Bu yalnız taslağı değiştirir;draft_changedtrue olur. - Taslağı deneyin:
version: "draft"ile bir web görüşmesi başlatın. POST /v1/agents/{id}/publishile, isterseniz bir notla yayımlayın. Taslak numaralı yeni bir sürüm olur ve yayına girer.- Bir sorun varsa
POST /v1/agents/{id}/rollbackönceki bir sürüm numarasıyla o sürümü yeni bir sürüm olarak yeniden yayımlar.
curl -X POST "https://hubvocal.com/v1/agents/resepsiyon/publish" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"note": "Shorter greeting"
}' const res = await fetch("https://hubvocal.com/v1/agents/resepsiyon/publish", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"note": "Shorter greeting"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/agents/resepsiyon/publish",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"note": "Shorter greeting",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) POST /v1/web-calls ve POST /v1/calls version alır: "live" (varsayılan), "draft" ya da bir sürüm numarası. Hiç yayımlanmamış bir ajan taslağını kullanır.
curl -X POST "https://hubvocal.com/v1/web-calls" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "resepsiyon",
"version": "draft"
}' Yayından önce test
Test laboratuvarı, simüle bir arayanla ajanınız arasında metin görüşmeleri koşturur; böylece bir değişikliği gerçek arayanlar duymadan denetlersiniz.
- Bir senaryo yazın: kim arıyor (
persona), ne istiyor (goal) ve en fazla 20 tur. - İyi bir görüşmenin uyması gereken kuralları ekleyin (
expect), ör. "randevudan önce arayanın adını sorar". - Araçlar için örnek sonuçlar verin (
mocks); gerçek bir araç ya da webhook çağrılmaz. - Senaryoları taslağa karşı koşun. Ayrı bir model her kuralı denetler ve neden geçtiğini ya da kaldığını açıklar.
{
"scenarios": [
{
"name": "Randevu, ad vermeden",
"persona": "Acelesi olan, kısa konuşan bir arayan",
"goal": "Yarın öğleden sonra randevu almak",
"max_turns": 12,
"expect": [
"Randevudan önce arayanın adını sorar",
"Saati okuyup onay ister"
],
"mocks": {
"check_availability": {
"ok": true,
"slots": [
"2026-11-06T14:00",
"2026-11-06T15:30"
]
}
}
}
],
"version": "draft"
} Görüşmeler metin modunda koşar; yani talimatı ve araç kullanımını dener, sesi ya da gecikmeyi değil. Simülasyonlar panelden koşturulur; bunlar için henüz /v1 ucu yoktur.
Araçlar
Araçlar ajanın görüşme sırasında bir şey yapabilmesini sağlar. Aynı tanım dört motorda da çalışır; GPT-Live'da araçları arka plan modeli yürütür. Araçlar ajan başına tools alanında açılır.
Yerleşik araçlar
| Ad | Ne yapar |
|---|---|
end_call | Ajan vedalaşır, konuşması bittikten 0,75 sn sonra kapatır. Arayan bu arada konuşursa kapatma iptal olur. Görüşme kaydında ended_by: "agent" görünür. |
take_message | name, phone, message ve callback_requested alır ve görüşme kaydında outcomes[] içine yazar. Onay istemez. |
search_knowledge | Ajanın knowledge alanında belge varsa kendiliğinden eklenir. Belgelerde arar ve kaynakları görüşme kaydına yazar. |
check_availability | Okuma. Ajanın çalışma saatleri içinde date_from ile date_to arasındaki boş zamanları döner. calendar ayarlıysa eklenir. |
book_appointment | Yazma; onay kapısından geçer. name ve phone için start zamanına randevu alır; isteğe bağlı note. Randevu outcomes[] içine yazılır. |
cancel_appointment | Yazma; onay kapısından geçer. booking_ref ile randevuyu iptal eder. |
Webhook araçları
Webhook aracı sizin tarafınızdaki bir HTTPS ucudur. Organizasyon için bir kez tanımlayın: panelde Araçlar bölümünden ya da POST /v1/tools ile. Sonra kimliğini ajanın tools.webhooks listesine ekleyin (ajan başına en fazla 16).
curl -X POST "https://hubvocal.com/v1/tools" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "check_order",
"description": "Sipariş numarasıyla kargo durumunu getirir.",
"kind": "read",
"url": "https://crm.example.com/hooks/order",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Sipariş numarası"
}
},
"required": [
"order_id"
]
},
"timeout_s": 8,
"enabled": true,
"secret": "whsec_2b7f…"
}' const res = await fetch("https://hubvocal.com/v1/tools", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "check_order",
"description": "Sipariş numarasıyla kargo durumunu getirir.",
"kind": "read",
"url": "https://crm.example.com/hooks/order",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Sipariş numarası"
}
},
"required": [
"order_id"
]
},
"timeout_s": 8,
"enabled": true,
"secret": "whsec_2b7f…"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/tools",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"name": "check_order",
"description": "Sipariş numarasıyla kargo durumunu getirir.",
"kind": "read",
"url": "https://crm.example.com/hooks/order",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Sipariş numarası",
},
},
"required": [
"order_id",
],
},
"timeout_s": 8,
"enabled": True,
"secret": "whsec_2b7f…",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) name: küçük harf, rakam ve alt çizgi; harfle başlar, 3–41 karakter; organizasyonda benzersiz.end_callvetake_messageayrılmıştır.descriptionmodele aracı ne zaman kullanacağını söyler. Bir iş arkadaşına talimat yazar gibi yazın.parametersbir JSON Schema nesnesidir: string, number, integer ya da boolean türünde en fazla 20 alan (enum olur).kindreadya dawrite;timeout_s2–15 saniye.
Gönderdiğimiz istek
Model aracı çağırınca API adresinize bir POST atar. Gövdede araç adı, modelin seçtiği argümanlar ve görüşmeyle ilgili bilgiler bulunur:
POST /hooks/order HTTP/1.1
Host: crm.example.com
Content-Type: application/json
X-VT-Timestamp: 1793832421
X-VT-Signature: sha256=5f0c9a…
{
"tool": "check_order",
"arguments": {
"order_id": "A-10234"
},
"call": {
"room": "tel-6b1f04d2",
"org_id": "6f1c2a40-3b7e-4d0c-9a51-2f8e7c1d9b03",
"agent": "front-desk",
"channel": "phone_in",
"caller": "+905321234567",
"called": "+908501234567",
"lang": "en"
}
} Her istek aracın gizli anahtarıyla imzalanır. X-VT-Timestamp unix zamanıdır; X-VT-Signature ise sha256= ve ardından timestamp + "." + body değerinin hex HMAC-SHA256 özetidir. Gövdeyi ayrıştırmadan önce ham haliyle doğrulayın ve birkaç dakikadan eski zaman damgalarını reddedin.
İmzayı doğrulama
// server.js (Node 18+, Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.VT_TOOL_SECRET;
// İmza ham baytları kapsar: gövdeyi ham olarak okuyun.
app.post("/hooks/order", express.raw({ type: "*/*" }), (req, res) => {
const ts = req.get("X-VT-Timestamp") ?? "";
const sig = req.get("X-VT-Signature") ?? "";
// Eski istekleri reddedin (tekrar oynatma koruması).
if (!/^\d+$/.test(ts) || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(ts + ".").update(req.body).digest("hex");
const a = Buffer.from(sig);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);
const { tool, arguments: args, call } = JSON.parse(req.body.toString("utf8"));
// Yanıtı kısa tutun: modele yalnız ilk 4 KB gider.
res.json({ order_id: args.order_id, status: "shipped", eta: "2026-11-07" });
});
app.listen(3000); # app.py (Flask)
import hashlib
import hmac
import json
import os
import time
from flask import Flask, abort, jsonify, request
app = Flask(__name__)
SECRET = os.environ["VT_TOOL_SECRET"].encode()
@app.post("/hooks/order")
def order():
ts = request.headers.get("X-VT-Timestamp", "")
sig = request.headers.get("X-VT-Signature", "")
# Eski istekleri reddedin (tekrar oynatma koruması).
if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
abort(401)
# İmza ham baytları kapsar: gövdeyi ham olarak okuyun.
body = request.get_data()
expected = "sha256=" + hmac.new(SECRET, ts.encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
payload = json.loads(body)
args = payload["arguments"]
# Yanıtı kısa tutun: modele yalnız ilk 4 KB gider.
return jsonify(order_id=args["order_id"], status="shipped", eta="2026-11-07") Yanıtınız
2xx durum koduyla JSON ya da düz metin dönün. Modele yalnız ilk 4 KB gider; ajanın söyleyeceği kadarını dönün. 2xx dışı bir kod ya da zaman aşımı modele aracın başarısız olduğunu söyler; ajan bunu görüşmede ele alır.
Okuma ve yazma araçları
- read araçları hemen çalışır: sipariş durumu, çalışma saatleri gibi sorgular için.
- write araçları onay kapısından geçer. İlk çağrı yürütülmez; ajan ayrıntıları arayana okur ve evet ya da hayır diye sorar. Arayanın sonraki turu görüşme dilinde açık bir "evet" içeriyorsa çalışır.
- Aynı çağrı (ad ve argümanlar) bir görüşmede en fazla bir kez yürütülür.
Güvenlik
- Adresler
https://olmalı. Yerel ve özel ağ adresleri reddedilir. - Gizli anahtar şifreli saklanır ve API'den hiç dönmez (
has_secrettanımlı olup olmadığını gösterir). Ajan anahtarı görmez; isteği API atar. - Argümanlar ve yanıtlar saklanmaz. Görüşme kaydında
tools[](ad, tür, sonuç, süre) ve zaman çizelgesinde bir ARAÇ şeridi tutulur.
POST /v1/tools/{id}/test örnek argümanlarla gerçek bir istek atar; durum kodunu, süreyi ve yanıt gövdesini görürsünüz.
curl -X POST "https://hubvocal.com/v1/tools/tl_8f3a2c/test" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"arguments": {
"order_id": "A-10234"
}
}' const res = await fetch("https://hubvocal.com/v1/tools/tl_8f3a2c/test", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"order_id": "A-10234"
}
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/tools/tl_8f3a2c/test",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"arguments": {
"order_id": "A-10234",
},
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Bilgi bankası
Bilgi bankası organizasyonunuzun belgelerini tutar. Bir ajana belge seçildiğinde ajan search_knowledge aracını alır ve bulduğuna göre yanıt verir. Belgelerde olmayanı uydurmaması talimatı vardır.
Belge ekleyin
Dosyayı multipart (file), düz metni JSON (title, text) ya da tek bir web sayfasını JSON (url) olarak gönderin. İşleme arka planda yapılır: belge processing olarak başlar, ready ya da failed olur.
curl -X POST "https://hubvocal.com/v1/knowledge" \
-H "Authorization: Bearer $VT_API_KEY" \
-F "file=@price-list-2026.pdf" import fs from "node:fs";
const form = new FormData();
form.append("file", new Blob([await fs.promises.readFile("price-list-2026.pdf")]), "price-list-2026.pdf");
const res = await fetch("https://hubvocal.com/v1/knowledge", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.VT_API_KEY}` },
body: form,
});
console.log(await res.json()); import os
import requests
with open("price-list-2026.pdf", "rb") as f:
resp = requests.post(
"https://hubvocal.com/v1/knowledge",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
files={"file": ("price-list-2026.pdf", f, "application/pdf")},
timeout=60,
)
print(resp.json()) - Dosyalar: pdf, docx, txt, md ya da html; her biri en fazla 10 MB.
- Organizasyon başına en fazla 200 belge ve toplam 200 MB.
- Adresler: tek sayfa, yalnız https; yerel ve özel adresler reddedilir.
Ajana verin
Belge kimliklerini ajanın knowledge.doc_ids listesine yazın (en fazla 50). Yayına almadan önce POST /v1/knowledge/search ile aramaları deneyin.
curl -X POST "https://hubvocal.com/v1/knowledge/search" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "diş taşı temizliği ne kadar"
}' const res = await fetch("https://hubvocal.com/v1/knowledge/search", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"query": "diş taşı temizliği ne kadar"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/knowledge/search",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"query": "diş taşı temizliği ne kadar",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Görüşme kaydında her arama tools[] içinde search_knowledge olarak, sources (belge, başlık ve parça) bilgisiyle görünür.
Takvim ve randevu
Bir takvim bağlıysa ajan görüşme sırasında uygunluğa bakabilir, randevu alabilir ya da iptal edebilir.
Takvimler panelde Entegrasyonlar bölümünden bağlanır (Google Calendar). Entegrasyonlar için /v1 ucu yoktur; API'de yalnız ajanı bir entegrasyona yönlendirirsiniz.
Ajan ayarları
Ajanın calendar nesnesini doldurun: hangi entegrasyon ve takvim, randevu süresi (15–240 dk), ara (0–60 dk), saat dilimi, çalışma saatleri, en az ne kadar önceden (0–72 sa) ve en fazla kaç gün sonrasına (1–90 gün) randevu alınabileceği.
{
"calendar": {
"integration_id": "int_4a7c10",
"calendar_id": "primary",
"duration_min": 30,
"buffer_min": 10,
"timezone": "Europe/Istanbul",
"working_hours": {
"mon": [
[
"09:00",
"18:00"
]
],
"tue": [
[
"09:00",
"18:00"
]
],
"wed": [
[
"09:00",
"18:00"
]
],
"thu": [
[
"09:00",
"18:00"
]
],
"fri": [
[
"09:00",
"17:00"
]
]
},
"min_notice_h": 2,
"max_days_ahead": 30
}
} Ajan bu durumda üç yerleşik araç alır: check_availability (okuma), book_appointment ve cancel_appointment (ikisi de yazma). Yazmalar onay kapısından geçer: ajan saati, adı ve telefonu arayana okur ve ancak açık bir "evet"ten sonra randevu alır.
Her randevu görüşme kaydında outcomes[] altında {kind: "appointment", start, name, phone, ref} olarak saklanır.
MCP
HubVocal Model Context Protocol'ü iki şekilde destekler: ajanlarınız kendi MCP sunucularınızdaki araçları kullanabilir, MCP istemcileri de platformu yönetebilir.
Ajanlarınıza MCP sunucusu bağlayın
Sunucunuzu POST /v1/mcp-servers ile ya da panelden ekleyin. Araç listesini (tools/list) çekeriz; yeni araçlar kapalı gelir. İstediklerinizi açın, her birini read ya da write olarak işaretleyin ve ajanın tools.mcp listesine yazın.
curl -X POST "https://hubvocal.com/v1/mcp-servers" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "crm",
"url": "https://mcp.example.com/mcp",
"auth": "bearer",
"secret": "sk_live_…"
}' const res = await fetch("https://hubvocal.com/v1/mcp-servers", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "crm",
"url": "https://mcp.example.com/mcp",
"auth": "bearer",
"secret": "sk_live_…"
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/mcp-servers",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"name": "crm",
"url": "https://mcp.example.com/mcp",
"auth": "bearer",
"secret": "sk_live_…",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) - Bağlantıyı ajan değil API kurar; ajan sunucunuzun gizli anahtarını hiç görmez.
- Adresler https olmalı; yerel ve özel ağ adresleri, bağlantı anında da, reddedilir.
writearaçlar onay kapısından geçer: ajan ayrıntıları okur ve aracı ancak açık bir "evet"ten sonra, görüşmede en fazla bir kez çalıştırır.- Her çağrı 10 saniyede zaman aşımına uğrar; sonucun en fazla 4 KB'ı modele gider.
- Organizasyon başına en fazla 10 sunucu, sunucu başına 50 araç. MCP araçları ajanın 16 araçlık sınırına dahildir.
Görüşmede ve görüşme kaydında bir MCP aracının adı <server>.<tool> (sunucu.araç) biçimindedir, ör. crm.find_customer.
Platformu bir MCP istemcisinden yönetin
HubVocal ayrıca https://hubvocal.com/mcp adresinde bir MCP sunucusudur (streamable HTTP). Kimlik için organizasyon API anahtarını Bearer olarak gönderin. Hız sınırı /v1 ile ortaktır.
İstemciyi bağlayın
vt_live_… yerine kendi anahtarınızı yazın. İstemcinin ihtiyaç duyduğu kapsamlardan fazlasını vermeyen bir anahtar kullanın.
claude mcp add --transport http hubvocal https://hubvocal.com/mcp \
--header "Authorization: Bearer vt_live_…" {
"mcpServers": {
"hubvocal": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://hubvocal.com/mcp",
"--header",
"Authorization:${VT_AUTH}"
],
"env": {
"VT_AUTH": "Bearer vt_live_…"
}
}
}
} {
"mcpServers": {
"hubvocal": {
"url": "https://hubvocal.com/mcp",
"headers": {
"Authorization": "Bearer vt_live_…"
}
}
}
} from openai import OpenAI
client = OpenAI()
resp = client.responses.create(
model="gpt-5.6-luna",
tools=[{
"type": "mcp",
"server_label": "hubvocal",
"server_url": "https://hubvocal.com/mcp",
"headers": {"Authorization": "Bearer vt_live_…"},
"require_approval": "always",
}],
input="Dünkü görüşmelerin özetini çıkar.",
)
print(resp.output_text) ChatGPT uygulamasında özel MCP bağlayıcıları geliştirici modunda eklenir; API anahtarı başlığı kullanıp kullanamayacağınız planınıza ve bağlayıcı ayarlarına bağlıdır. OpenAI API'siyle anahtarı aşağıdaki gibi doğrudan verebilirsiniz.
Araçlar
| Araç | Kapsam | Ne yapar |
|---|---|---|
list_agents | read | Ajanlarınızı listeler. |
get_agent | read | Tek bir ajanı okur. |
list_calls | read | Görüşmeleri since, outcome ya da agent_id ile süzerek listeler (en fazla 50). |
get_call | read | Bir görüşmeyi dökümü ve özetiyle okur. |
list_numbers | read | Telefon numaralarınızı listeler. |
list_knowledge | read | Bilgi bankası belgelerini listeler. |
search_knowledge | read | Bilgi bankasında arar. |
get_usage | read | Bu ayın maliyeti ve dakikaları. |
create_agent | write | Ajan oluşturur. |
update_agent | write | Ajanın taslağını değiştirir. |
publish_agent | write | Ajanın taslağını yayımlar. |
add_knowledge_text | write | Bilgi bankasına metin belgesi ekler. |
start_outbound_call | calls | Giden arama başlatır (agent_id, to). Giden arama kuralları ve sınırları aynen geçerlidir. |
Kaynaklar ve istemler
agent://{id}: bir ajanın ayarları.call://{id}: bir görüşmenin dökümü, metin olarak.- İstem
daily_call_report: günün görüşmelerinin özeti.
Kapsamlar ve denetim kaydı
Her API anahtarının, panelde oluştururken seçilen kapsamları vardır:
read: yalnız okuma araçları ve uçları.write: ayrıca ajan ve belge oluşturma ve değiştirme.calls: ayrıca giden arama başlatma.
Kapsamlardan önce oluşturulmuş anahtarlarda üçü de vardır. Her MCP araç çağrısı anahtar, araç, zaman ve sonuçla denetim kaydına yazılır.
Web widget'ı
Widget, web sitenize bir arama düğmesi koyar. Ziyaretçiler tarayıcıdan ajanınızla konuşur; sizin tarafınızda sunucu kodu gerekmez.
Herkese açık anahtar oluşturun
Panelde ajanı açın ve bir herkese açık anahtar (pk_…) oluşturun. Herkese açık anahtar tek bir ajana aittir ve sayfanıza koymak güvenlidir: yalnız o ajanla ve yalnız izin verdiğiniz alan adlarından web görüşmesine izin verir.
- İzinli alan adları (
allowed_origins): başka sitelerden gelen görüşmeler reddedilir. - Görüşme başına süre sınırı.
- Aynı IP adresinden saatte en fazla 10 görüşme.
Script'i ekleyin
Bunu kapanan </body> etiketinden önce yapıştırın:
<script src="https://hubvocal.com/widget.js" data-agent="pk_…" async></script> Renk, konum ve düğme metni (ziyaretçinin dilinde) ajanın widget ayarlarından gelir: color, position, label.
{
"widget": {
"color": "#2563eb",
"position": "bottom-right",
"label": "Bizi arayın"
}
} Widget herkese açık anahtarla POST /api/public/web-calls çağırır; API anahtarınızı hiç görmez. Herkese açık anahtarlar panelden yönetilir.
CRM'e yapılandırılmış alanlar
Yapılandırılmış alanlar bir görüşmeyi sistemlerinizin kullanabileceği veriye çevirir: randevu tarihi, sorulan ürün ya da geri aranmak istenip istenmediği gibi.
Alanları tanımlayın
Ajanın post_call.fields listesine en fazla 24 alan ekleyin. Her alanın bir name (küçük harf, rakam ve alt çizgi), bir type (string, number, boolean, enum ya da date), modele neye bakacağını anlatan bir description değeri ve isteğe bağlı enum değerleri ile required bilgisi vardır.
{
"post_call": {
"summary": true,
"fields": [
{
"name": "appointment_date",
"type": "date",
"description": "İstenen randevu tarihi"
},
{
"name": "service",
"type": "enum",
"enum": [
"checkup",
"cleaning",
"whitening"
],
"description": "Sorulan hizmet"
},
{
"name": "callback",
"type": "boolean",
"required": true,
"description": "Arayan geri aranmak istiyor mu?"
}
]
}
} Görüşmeden sonra özeti yazan model alanları da doldurur. Konuşulmayan değerler null olur. Bunları GET /v1/calls/{room} ve görüşme listesinde extracted olarak, call.ended webhook gövdesinde de alırsınız.
{
"extracted": {
"appointment_date": "2026-11-12",
"service": "cleaning",
"callback": true
}
} CSV dışa aktarma her alanı ayrı bir sütun olarak içerir.
Görüşme webhook'ları
Bir webhook, görüşme bittiğinde ve kaydı (özet dahil) tamamlandığında sisteminize bir call.ended olayı gönderir. Destek kaydı açmak, CRM'deki kişiyi güncellemek ya da takip mesajı göndermek için kullanın.
Görüşme kalitesi puanları açıksa call.quality_low olayına da abone olabilirsiniz. Puan organizasyonunuzun eşiğinin altına düştüğünde görüşme başına bir kez gönderilir.
Organizasyon başına en fazla 5 webhook'u panelden ya da POST /v1/webhooks ile ekleyin. secret göndermezseniz biri üretilir ve yalnız o yanıtta döner. Adresler https olmalı; yerel ve özel adresler reddedilir.
curl -X POST "https://hubvocal.com/v1/webhooks" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://crm.example.com/hooks/voice",
"events": [
"call.ended"
]
}' const res = await fetch("https://hubvocal.com/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"url": "https://crm.example.com/hooks/voice",
"events": [
"call.ended"
]
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/webhooks",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"url": "https://crm.example.com/hooks/voice",
"events": [
"call.ended",
],
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Olay
data, GET /v1/calls/{room} yanıtındaki görüşme nesnesinin aynısıdır; döküm ve özet dahil:
POST /hooks/voice HTTP/1.1
Host: crm.example.com
Content-Type: application/json
X-VT-Event: call.ended
X-VT-Delivery: dl_51b7a9
X-VT-Timestamp: 1793832440
X-VT-Signature: sha256=9b1e44…
{
"id": "evt_8a41f0c2",
"type": "call.ended",
"created_at": "2026-11-05T09:47:20Z",
"data": {
"room": "demo-8585522d",
"org_id": "6f1c2a40-3b7e-4d0c-9a51-2f8e7c1d9b03",
"org_name": "Örnek Klinik",
"channel": "web",
"profile": "Resepsiyon",
"engine": "live",
"lang": "tr",
"voice": "marin",
"started_at": 1793832409.1,
"ended_at": 1793832434.2,
"duration_s": 25.1,
"total_usd": 0.021215,
"ended_by": "caller",
"outcome": "message_taken",
"summary_text": "Arayan perşembe öğleden sonra için randevu istedi; geri arama için mesaj alındı.",
"extracted": {
"appointment_date": "2026-11-12",
"callback": true
},
"first_audio_s": 2.43,
"turns": 4,
"source_gaps": {
"count": 2,
"total_ms": 230,
"max_ms": 141,
"agent_audio_s": 31.2,
"events": [
[
16.4,
141
],
[
40.1,
89
]
]
},
"quality": {
"verdict": "Temiz: kayıp ve boşluk yok.",
"protocol": "UDP",
"loss_pct": 0,
"conceal_pct": 0,
"jitter_ms": 4,
"rtt_ms": 38
},
"cost": {
"live": {
"billed_s": 25.1,
"reported": true,
"voice_usd": 0.020917,
"backend": {
"in": 1840,
"cached": 1024,
"out": 96
},
"backend_usd": 0.000298
},
"llm": {
"input_tokens": 1840,
"cached_tokens": 1024,
"output_tokens": 96,
"usd": 0.000298
},
"tts": {
"input_tokens": 0,
"audio_tokens": 0,
"audio_s": 25.1,
"usd": 0.020917
},
"stt": {
"audio_s": 0,
"usd": 0
}
},
"tools": [
{
"t": 14.2,
"name": "check_order",
"kind": "read",
"ok": true,
"ms": 412,
"status": 200
}
],
"outcomes": [],
"summary": {
"text": "Arayan perşembe öğleden sonra için randevu istedi; geri arama için mesaj alındı.",
"language": "tr",
"outcome": "message_taken",
"sentiment": "neutral",
"topics": [
"randevu"
],
"action_items": [
"Arayanı perşembe sabahı geri ara"
]
},
"recording": {
"available": true,
"duration_s": 25.1,
"size_bytes": 201344
},
"supervisors": [
{
"user": "ayse@example.com",
"joined_at": 1793832420.4,
"left_at": 1793832431,
"modes": [
"listen",
"whisper"
]
}
],
"greeting": {
"connect_s": 0.15,
"session_started_s": 0.05,
"reply_requested_s": 0.05,
"first_audio_s": 2.4,
"reply_done_s": 7.1
},
"transcript": [
{
"role": "assistant",
"text": "Merhaba, kliniğimizi aradığınız için teşekkürler. Size nasıl yardımcı olabilirim?",
"t": 2.4,
"interrupted": false
},
{
"role": "user",
"text": "Perşembe öğleden sonra randevu almak istiyorum.",
"t": 9.8
}
]
}
} İstekler araç istekleriyle aynı şekilde imzalanır: X-VT-Timestamp ve X-VT-Signature. İki başlık daha eklenir: X-VT-Event (olay türü) ve X-VT-Delivery (bu teslimin benzersiz kimliği).
Yeniden deneme
10 saniye içinde gelen her 2xx yanıt teslim sayılır. Aksi halde yeniden deneriz:
- 1 dakika sonra
- 5 dakika sonra
- 30 dakika sonra
- 2 saat sonra
Toplam 5 denemeden sonra teslim failed olarak işaretlenir. Her denemeyi GET /v1/webhooks/{id}/deliveries ile görebilir, başarısız bir teslimi POST /v1/webhooks/deliveries/{id}/retry ile yeniden kuyruğa alabilirsiniz.
Tekrarları ayıklayın
Önceki yanıtınız yolda kaybolsa bile yeniden deneme gelebilir; yani aynı olay birden fazla kez ulaşabilir. X-VT-Delivery kimliğini saklayın ve daha önce işlediğiniz kimlikleri atlayın. Hızlı yanıt verin, yavaş işi arka planda yapın.
// Önce imzayı doğrulayın (araçlardakiyle aynı kod).
app.post("/hooks/voice", express.raw({ type: "*/*" }), async (req, res) => {
if (!verify(req)) return res.sendStatus(401);
// Bu teslim daha önce işlendiyse onaylayıp durun.
const delivery = req.get("X-VT-Delivery");
if (await db.deliveries.exists(delivery)) return res.sendStatus(200);
await db.deliveries.insert(delivery);
// Hızlı yanıt verin; yavaş işi arka planda yapın.
res.sendStatus(200);
const event = JSON.parse(req.body.toString("utf8"));
queue.add("call-ended", event.data);
}); # Önce imzayı doğrulayın (araçlardakiyle aynı kod).
@app.post("/hooks/voice")
def voice_hook():
if not verify(request):
abort(401)
# Bu teslim daha önce işlendiyse onaylayıp durun.
delivery = request.headers["X-VT-Delivery"]
if db.deliveries.exists(delivery):
return "", 200
db.deliveries.insert(delivery)
# Hızlı yanıt verin; yavaş işi arka planda yapın.
event = json.loads(request.get_data())
queue.enqueue("call_ended", event["data"])
return "", 200 POST /v1/webhooks/{id}/test örnek bir call.ended olayı gönderir; durum kodunu, süreyi ve yanıt gövdesini döner.
Desteklenen görüşme dilleri
Her motorun hangi dillerde görüşme yürütebildiği. Ajanın dili lang alanıyla, aşağıdaki kodlarla seçilir.
- Destekleniyor
- Deneysel Motor bu dili resmi olarak listelemiyor; talimatla çalışır, kalite ölçülmedi.
| Dil | lang | GPT-Live 1 | Gemini Live | OpenAI Realtime | Klasik |
|---|---|---|---|---|---|
Türkçetr | tr | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
İngilizceEnglishen | en | Destekleniyor | Destekleniyor | Deneysel | Destekleniyor |
AlmancaDeutschde | de | Destekleniyor | Destekleniyor | Deneysel | Destekleniyor |
FransızcaFrançaisfr | fr | Destekleniyor | Destekleniyor | Deneysel | Destekleniyor |
İspanyolcaEspañoles | es | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
İtalyancaItalianoit | it | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
PortekizcePortuguêspt | pt | Destekleniyor | Destekleniyor | Deneysel | Destekleniyor |
FelemenkçeNederlandsnl | nl | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
RusçaРусскийru | ru | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
UkraynacaУкраїнськаuk | uk | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
LehçePolskipl | pl | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
RumenceRomânăro | ro | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
YunancaΕλληνικάel | el | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
BulgarcaБългарскиbg | bg | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
ÇekçeČeštinacs | cs | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
SlovakçaSlovenčinask | sk | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
MacarcaMagyarhu | hu | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
HırvatçaHrvatskihr | hr | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
SırpçaСрпскиsr | sr | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
BoşnakçaBosanskibs | bs | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
SlovenceSlovenščinasl | sl | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
MakedoncaМакедонскиmk | mk | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
İsveççeSvenskasv | sv | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
DancaDanskda | da | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
NorveççeNorskno | no | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
FinceSuomifi | fi | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
EstoncaEestiet | et | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
LetoncaLatviešulv | lv | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
LitvancaLietuviųlt | lt | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
KatalancaCatalàca | ca | Deneysel | Destekleniyor | Deneysel | Destekleniyor |
Deneysel dilleri telefona bağlamadan önce tarayıcıda denemenizi öneririz.
Hız sınırı ve hatalar
Hız sınırı anahtar başına dakikada 120 istektir. Aşıldığında 429 ve Retry-After başlığı (saniye) döner. Hatalar HTTP durum koduyla ve her zaman aynı gövdeyle gelir; detail insanların okuyacağı bir açıklamadır.
{
"detail": "Monthly spending limit reached"
} | Kod | Anlamı | Ne zaman |
|---|---|---|
| 400 | Geçersiz istek | Gövde okunamadı, zorunlu alan eksik ya da değer geçersiz (ör. numara E.164 değil, son ajan silinmeye çalışıldı). |
| 401 | Kimlik yok | Authorization başlığı yok, anahtar vt_live_ ile başlamıyor ya da iptal edilmiş. |
| 402 | Limit doldu | Organizasyonun aylık harcama limiti doldu; yeni görüşme başlatılamaz. |
| 403 | Askıda | Organizasyon askıya alınmış; görüşme başlatılamaz. |
| 404 | Bulunamadı | Ajan, görüşme, numara ya da uç yok. |
| 409 | Çakışma | POST /v1/agents ile var olan bir id gönderildi. |
| 429 | Hız sınırı | Anahtar başına dakikada 120 istek aşıldı. Retry-After başlığındaki saniye kadar bekleyin. |
| 502 | Sağlayıcı hatası | Telefon sağlayıcısı giden aramayı reddetti ya da yanıt vermedi. |
| 500 | Sunucu hatası | Beklenmeyen hata. Tekrar deneyin; sürerse bize yazın. |
Yalnız 429 ve 5xx hatalarını yeniden deneyin. 4xx hataları isteğin kendisinde bir sorun olduğunu söyler; tekrar göndermek sonucu değiştirmez.
async function vt(path, init = {}, attempt = 0) {
const res = await fetch(`https://hubvocal.com/v1${path}`, {
...init,
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
...init.headers,
},
});
if (res.status === 429 && attempt < 3) {
const wait = Number(res.headers.get("Retry-After") ?? 60);
await new Promise((r) => setTimeout(r, wait * 1000));
return vt(path, init, attempt + 1);
}
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
return data;
}