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:

KaynakUç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ıç

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

    shell
    export VT_API_KEY="vt_live_…"
  2. 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.

    curl
    curl "https://hubvocal.com/v1/agents" \
      -H "Authorization: Bearer $VT_API_KEY"
  3. 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.

    curl
    curl -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"
      }
    }'
  4. Web görüşmesi açın

    Yanıttaki server_url ve token ile tarayıcıdan bağlanırsınız; ayrıntı aşağıda.

    curl
    curl -X POST "https://hubvocal.com/v1/web-calls" \
      -H "Authorization: Bearer $VT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "agent_id": "resepsiyon"
    }'

Kimlik doğrulama

Her istekte anahtarı Authorization başlığında Bearer olarak gönderin:

HTTP
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ı:

  1. Ziyaretçi düğmeye basar; sayfanız kendi sunucunuza istek atar.
  2. Sunucunuz API anahtarıyla POST /v1/web-calls çağırır ve yanıtı (server_url, token, room, max_seconds) sayfaya döner.
  3. Sayfa livekit-client ile 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

Node (Express)
// 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);

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.

index.html
<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
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"
}'

Yanıt

JSON
{
  "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
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"
}'

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.

  1. Ajanı PUT /v1/agents/{id} ile düzenleyin. Bu yalnız taslağı değiştirir; draft_changed true olur.
  2. Taslağı deneyin: version: "draft" ile bir web görüşmesi başlatın.
  3. POST /v1/agents/{id}/publish ile, isterseniz bir notla yayımlayın. Taslak numaralı yeni bir sürüm olur ve yayına girer.
  4. 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
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"
}'

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

  1. Bir senaryo yazın: kim arıyor (persona), ne istiyor (goal) ve en fazla 20 tur.
  2. İyi bir görüşmenin uyması gereken kuralları ekleyin (expect), ör. "randevudan önce arayanın adını sorar".
  3. Araçlar için örnek sonuçlar verin (mocks); gerçek bir araç ya da webhook çağrılmaz.
  4. Senaryoları taslağa karşı koşun. Ayrı bir model her kuralı denetler ve neden geçtiğini ya da kaldığını açıklar.
JSON
{
  "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

AdNe yapar
end_callAjan 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_messagename, phone, message ve callback_requested alır ve görüşme kaydında outcomes[] içine yazar. Onay istemez.
search_knowledgeAjanın knowledge alanında belge varsa kendiliğinden eklenir. Belgelerde arar ve kaynakları görüşme kaydına yazar.
check_availabilityOkuma. Ajanın çalışma saatleri içinde date_from ile date_to arasındaki boş zamanları döner. calendar ayarlıysa eklenir.
book_appointmentYazma; 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_appointmentYazma; 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
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…"
}'
  • name: küçük harf, rakam ve alt çizgi; harfle başlar, 3–41 karakter; organizasyonda benzersiz. end_call ve take_message ayrılmıştır.
  • description modele aracı ne zaman kullanacağını söyler. Bir iş arkadaşına talimat yazar gibi yazın.
  • parameters bir JSON Schema nesnesidir: string, number, integer ya da boolean türünde en fazla 20 alan (enum olur).
  • kind read ya da write; timeout_s 2–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:

HTTP
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

Node (Express)
// 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);

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_secret tanı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
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"
  }
}'

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
curl -X POST "https://hubvocal.com/v1/knowledge" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -F "file=@price-list-2026.pdf"
  • 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
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"
}'

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.

PUT /v1/agents/resepsiyon
{
  "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
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_…"
}'
  • 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.
  • write araç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 Code
claude mcp add --transport http hubvocal https://hubvocal.com/mcp \
  --header "Authorization: Bearer vt_live_…"

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çKapsamNe yapar
list_agentsreadAjanlarınızı listeler.
get_agentreadTek bir ajanı okur.
list_callsreadGörüşmeleri since, outcome ya da agent_id ile süzerek listeler (en fazla 50).
get_callreadBir görüşmeyi dökümü ve özetiyle okur.
list_numbersreadTelefon numaralarınızı listeler.
list_knowledgereadBilgi bankası belgelerini listeler.
search_knowledgereadBilgi bankasında arar.
get_usagereadBu ayın maliyeti ve dakikaları.
create_agentwriteAjan oluşturur.
update_agentwriteAjanın taslağını değiştirir.
publish_agentwriteAjanın taslağını yayımlar.
add_knowledge_textwriteBilgi bankasına metin belgesi ekler.
start_outbound_callcallsGiden 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:

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

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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
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"
  ]
}'

Olay

data, GET /v1/calls/{room} yanıtındaki görüşme nesnesinin aynısıdır; döküm ve özet dahil:

HTTP
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. 1 dakika sonra
  2. 5 dakika sonra
  3. 30 dakika sonra
  4. 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.

Node (Express)
// Ö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);
});

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 1Gemini LiveOpenAI RealtimeKlasik
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.

402
{
  "detail": "Monthly spending limit reached"
}
KodAnlamıNe zaman
400Geçersiz istekGövde okunamadı, zorunlu alan eksik ya da değer geçersiz (ör. numara E.164 değil, son ajan silinmeye çalışıldı).
401Kimlik yokAuthorization başlığı yok, anahtar vt_live_ ile başlamıyor ya da iptal edilmiş.
402Limit dolduOrganizasyonun aylık harcama limiti doldu; yeni görüşme başlatılamaz.
403AskıdaOrganizasyon askıya alınmış; görüşme başlatılamaz.
404BulunamadıAjan, görüşme, numara ya da uç yok.
409ÇakışmaPOST /v1/agents ile var olan bir id gönderildi.
429Hız sınırıAnahtar başına dakikada 120 istek aşıldı. Retry-After başlığındaki saniye kadar bekleyin.
502Sağlayıcı hatasıTelefon sağlayıcısı giden aramayı reddetti ya da yanıt vermedi.
500Sunucu 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.

Retry-After ile yeniden deneme
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;
}