Розробникам

Керуйте агентами, номерами й дзвінками зі своїх систем і додайте голосовий дзвінок на свій сайт.

Огляд

Це REST API. Тіла запитів і відповідей — JSON (UTF-8), час — у секундах unix, суми — у доларах США. Базова URL-адреса:

https://hubvocal.com/v1

Ключ API належить одній організації і діє в ній з правами адміністратора. Усе, що можна зробити в панелі керування, можна зробити й через API:

РесурсЕндпоінти
Агенти/v1/agents, /v1/agents/{id}
База знань/v1/knowledge, /v1/knowledge/{id}, /v1/knowledge/search
Інструменти/v1/tools, /v1/tools/{id}, /v1/tools/{id}/test
MCP-сервери/v1/mcp-servers, /v1/mcp-servers/{id}, /v1/mcp-servers/{id}/sync
Вебхуки/v1/webhooks, /v1/webhooks/{id}, /v1/webhooks/{id}/deliveries
Голоси й рушії/v1/voices
Веб-дзвінки/v1/web-calls
Дзвінки та вихідні дзвінки/v1/calls, /v1/calls/{room}
Номери/v1/numbers, /v1/numbers/{number}
Використання/v1/usage

Машинозчитуваний документ OpenAPI 3.1 доступний за GET /v1/openapi.json, ключ для нього не потрібен. Детальний опис кожного ендпоінта — у довіднику API.

Швидкий старт

  1. Створіть ключ API

    У панелі керування відкрийте Налаштування > Ключі API і дайте ключу назву, з якої зрозуміло, для чого він. Створювати ключі можуть лише власники й адміністратори організації. Секрет (vt_live_…) показується один раз, тому одразу збережіть його в менеджері секретів.

    shell
    export VT_API_KEY="vt_live_…"
  2. Отримайте список агентів

    Найшвидший спосіб перевірити, що ключ працює. Id з відповіді знадобиться на наступних кроках.

    curl
    curl "https://hubvocal.com/v1/agents" \
      -H "Authorization: Bearer $VT_API_KEY"
  3. Створіть агента

    Рушій, мова, голос та інструкції — в одному об’єкті. Допустимі назви голосів і ліміти повертає 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": "Front desk",
      "lang": "en",
      "engine": "live",
      "instructions": "You are the front desk assistant for a dental clinic. Help callers book appointments and answer general questions briefly. Never make things up; offer a callback from the team instead.",
      "greeting": "Hello, thanks for calling the clinic. How can I help you today?",
      "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. Почніть веб-дзвінок

    З браузера ви підключаєтеся за server_url і token із відповіді; подробиці нижче.

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

Автентифікація

Передавайте ключ як Bearer-токен у заголовку Authorization у кожному запиті:

HTTP
Authorization: Bearer vt_live_…
  • Ключ діє у вашій організації з правами адміністратора: створює агентів, призначає номери й здійснює дзвінки. Бережіть його, як пароль.
  • Ніколи не додавайте ключ у код для браузера, мобільний застосунок чи репозиторій. Для дзвінків із браузера отримуйте короткочасний токен на своєму сервері.
  • У списку Налаштування > Ключі API видно, коли кожен ключ використовували востаннє. Відкликаний ключ перестає працювати одразу.
  • Використовуйте окремий ключ для кожної інтеграції, щоб відкликання одного не зламало решту.

Веб-дзвінок на сайті

Відвідувачі можуть говорити з агентом просто на вашому сайті, у браузері. Схема складається з трьох кроків:

  1. Відвідувач натискає кнопку, і ваша сторінка звертається до вашого сервера.
  2. Ваш сервер викликає POST /v1/web-calls із ключем API і передає відповідь (server_url, token, room, max_seconds) назад на сторінку.
  3. Сторінка підключається до кімнати через livekit-client і вмикає мікрофон. Агент приєднується сам і вітається.

1. Сервер: отримайте токен

Node (Express)
// server.js (Node 18+, Express)
import express from "express";

const app = express();

app.post("/voice/session", async (req, res) => {
  // Тут додайте власну автентифікацію й обмеження частоти запитів.
  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: "front-desk" }),
  });
  res.status(r.status).json(await r.json());
});

app.listen(3000);

2. Браузер: підключіться

Якщо використовуєте бандлер, установіть npm install livekit-client@2.23.0 та імпортуйте ті самі імена. Приклад нижче завантажує зафіксовану версію з CDN.

index.html
<button id="talk" type="button">Почати розмову</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;
    // Ваш сервер повертає відповідь POST /v1/web-calls без змін.
    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();
    // Коли надійде звук агента, під’єднайте його до елемента <audio>.
    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 = "Почати розмову";
    });

    await room.connect(session.server_url, session.token);
    await room.localParticipant.setMicrophoneEnabled(true);
    button.disabled = false;
    button.textContent = "Завершити";
  });
</script>

Починайте підключення з дії користувача (кліку): браузери пов’язують із нею дозвіл на мікрофон і автовідтворення звуку. Сторінка має відкриватися через HTTPS.

Дзвінок триває не довше за max_seconds, після чого агент кладе слухавку. Коли дзвінок завершиться, отримайте його картку через GET /v1/calls/{room}.

Вихідні дзвінки

Телефонуйте на будь-який номер з одного з номерів вашої організації; агент заговорить, щойно абонент відповість. Номер from має бути в GET /v1/numbers. Якщо agent_id не вказано, говорить агент, призначений цьому номеру. Номери — у форматі E.164 (+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": "front-desk"
}'

Відповідь

JSON
{
  "room": "tel-out-9c41e0b7",
  "provider": "didww",
  "sip_call_id": "SCL_7mVt2kQb9xRa"
}

room у відповіді ідентифікує дзвінок. Після завершення дзвінка його картка (розшифровка, вартість, хто поклав слухавку) доступна через GET /v1/calls/{room}. Для дзвінків без відповіді ended_by має значення no_answer.

Вхідні дзвінки

На вхідні дзвінки на ваші номери відповідає агент, призначений відповідному номеру. Номери підключаються через DIDWW, Twilio або ваш SIP-транк (Asterisk тощо), а до вашої організації їх додає команда платформи. Ви обираєте, який агент відповідатиме:

curl
curl -X PUT "https://hubvocal.com/v1/numbers/%2B908501234567" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "agent_id": "front-desk",
  "label": "Main line"
}'

Версії та безпечна публікація

Зміни в агенті потрапляють у чернетку. Дзвінки на ваші номери, веб-дзвінки й вихідні дзвінки використовують опубліковану версію, тож із чернеткою можна працювати, не зачіпаючи реальних дзвінків.

  1. Змініть агента через PUT /v1/agents/{id}. Змінюється лише чернетка; draft_changed стає true.
  2. Перевірте чернетку: почніть веб-дзвінок із version: "draft".
  3. Опублікуйте через POST /v1/agents/{id}/publish, за бажанням із приміткою. Чернетка стає новою версією з власним номером, і дзвінки починають використовувати саме її.
  4. Якщо щось пішло не так, викличте POST /v1/agents/{id}/rollback із номером однієї з попередніх версій: її буде опубліковано знову, як нову версію.
curl
curl -X POST "https://hubvocal.com/v1/agents/front-desk/publish" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "Shorter greeting"
}'

POST /v1/web-calls і POST /v1/calls приймають version: "live" (за замовчуванням), "draft" або номер версії. Агент, якого жодного разу не публікували, працює за чернеткою.

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

Перевірка перед публікацією

Тестова лабораторія проводить текстові розмови між імітованим абонентом і вашим агентом, щоб ви могли перевірити зміну, перш ніж її почують справжні абоненти.

  1. Напишіть сценарій: хто телефонує (persona), чого він хоче (goal) і до 20 реплік.
  2. Додайте правила, яким має відповідати вдалий дзвінок (expect), наприклад «питає ім’я абонента перед записом».
  3. Задайте тестові результати для інструментів (mocks); справжні інструменти й вебхуки не викликаються.
  4. Запустіть сценарії на чернетці. Окрема модель перевіряє кожне правило й пояснює, чому його виконано чи ні.
JSON
{
  "scenarios": [
    {
      "name": "Booking without a name",
      "persona": "A caller in a hurry who gives short answers",
      "goal": "Book an appointment tomorrow afternoon",
      "max_turns": 12,
      "expect": [
        "Asks for the caller's name before booking",
        "Reads the time back and asks for confirmation"
      ],
      "mocks": {
        "check_availability": {
          "ok": true,
          "slots": [
            "2026-11-06T14:00",
            "2026-11-06T15:30"
          ]
        }
      }
    }
  ],
  "version": "draft"
}

Розмови відбуваються в текстовому режимі, тож перевіряються інструкції та використання інструментів, а не голос чи затримка. Симуляції запускаються з панелі керування; ендпоінта /v1 для них поки немає.

Інструменти

Інструменти дають агентові змогу діяти під час дзвінка. Те саме визначення працює на всіх чотирьох рушіях; у GPT-Live їх виконує фонова модель. Інструменти вмикаються для кожного агента окремо в полі tools.

Вбудовані інструменти

НазваЩо робить
end_callАгент прощається й кладе слухавку через 0,75 с після того, як замовкне. Якщо абонент тим часом заговорить, завершення скасовується. У картці дзвінка буде ended_by: "agent".
take_messageПриймає name, phone, message і callback_requested та записує їх у outcomes[] картки дзвінка. Підтвердження не запитує.
search_knowledgeДодається автоматично, коли в агента є документи в knowledge. Шукає в них і записує джерела в картку дзвінка.
check_availabilityТип read. Повертає вільні слоти між date_from і date_to у межах робочих годин агента. Додається, коли задано calendar.
book_appointmentТип write, проходить крок підтвердження. Бронює час start на ім’я name і телефон phone, за бажанням із приміткою note. Запис потрапляє в outcomes[].
cancel_appointmentТип write, проходить крок підтвердження. Скасовує запис за його booking_ref.

Вебхук-інструменти

Вебхук-інструмент — це HTTPS-ендпоінт на вашому боці. Визначте його один раз для організації в панелі керування в розділі Інструменти або через POST /v1/tools, а потім додайте його id у tools.webhooks агента (до 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": "Looks up the shipping status of an order by its number.",
  "kind": "read",
  "url": "https://crm.example.com/hooks/order",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "Order number"
      }
    },
    "required": [
      "order_id"
    ]
  },
  "timeout_s": 8,
  "enabled": true,
  "secret": "whsec_2b7f…"
}'
  • name: малі латинські літери, цифри й підкреслення, від 3 до 41 символу, першим має бути літера; назва має бути унікальною в організації. Назви end_call і take_message зарезервовано.
  • description пояснює моделі, коли використовувати інструмент. Пишіть його як інструкцію колезі.
  • parameters — об’єкт JSON Schema, до 20 полів типів string, number, integer або boolean (можна enum).
  • kind — read або write; timeout_s — від 2 до 15 секунд.

Запит, який ми надсилаємо

Коли модель викликає інструмент, API надсилає POST на вашу URL-адресу. У тілі — назва інструмента, аргументи, які обрала модель, і контекст дзвінка:

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

Кожен запит підписано секретом інструмента. X-VT-Timestamp — час unix; X-VT-Signature — це sha256= і далі шістнадцятковий HMAC-SHA256 від timestamp + "." + body. Перевіряйте підпис за сирим тілом ще до розбору JSON і відхиляйте запити з міткою часу, старшою за кілька хвилин.

Перевірка підпису

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;

// Підпис охоплює точні байти, тому читайте сире тіло.
app.post("/hooks/order", express.raw({ type: "*/*" }), (req, res) => {
  const ts = req.get("X-VT-Timestamp") ?? "";
  const sig = req.get("X-VT-Signature") ?? "";

  // Відхиляйте старі запити (захист від повторного надсилання).
  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"));
  // Відповідайте коротко: до моделі доходять лише перші 4 КБ.
  res.json({ order_id: args.order_id, status: "shipped", eta: "2026-11-07" });
});

app.listen(3000);

Ваша відповідь

Відповідайте зі статусом 2xx і JSON або простим текстом. До моделі доходять лише перші 4 КБ, тож повертайте тільки те, що агентові потрібно сказати. Статус поза 2xx або тайм-аут означає для моделі, що інструмент не спрацював, і агент враховує це в розмові.

Інструменти читання й запису

  • read: виконуються одразу. Підходять для довідок на кшталт статусу замовлення чи годин роботи.
  • write: проходять крок підтвердження. Перший виклик не виконується: агент повторює абонентові деталі й просить відповісти «так» чи «ні». Інструмент спрацює, лише якщо в наступній репліці абонента буде чітке «так» мовою дзвінка.
  • Той самий виклик (назва й аргументи) виконується щонайбільше один раз за розмову.

Безпека

  • URL-адреси мають починатися з https://. Локальні адреси й адреси приватних мереж відхиляються.
  • Секрет зберігається в зашифрованому вигляді, і API ніколи його не повертає (has_secret показує, чи його задано). Агент секрету не бачить: запит надсилає API.
  • Аргументи й відповіді не зберігаються. У картці дзвінка лишається tools[] з назвою, типом, результатом і тривалістю, а на шкалі часу — доріжка TOOL.

Через POST /v1/tools/{id}/test можна надіслати справжній запит із тестовими аргументами й побачити статус, час виконання та тіло відповіді.

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

База знань

У базі знань зберігаються документи вашої організації. Коли агентові вибрано документи, він отримує інструмент search_knowledge і відповідає на основі знайденого. Агент має інструкцію не вигадувати того, чого в документах немає.

Додавання документа

Надішліть файл як multipart (file), звичайний текст як JSON (title, text) або одну вебсторінку як JSON (url). Обробка відбувається у фоні: спершу документ має статус processing, а потім ready або failed.

curl
curl -X POST "https://hubvocal.com/v1/knowledge" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -F "file=@price-list-2026.pdf"
  • Файли: pdf, docx, txt, md або html, до 10 МБ кожен.
  • До 200 документів і 200 МБ загалом на організацію.
  • URL: одна сторінка, лише https; локальні й приватні адреси відхиляються.

Підключення до агента

Перелічіть id документів у knowledge.doc_ids агента (до 50). Перш ніж запускати агента, перевірте пошук через POST /v1/knowledge/search.

curl
curl -X POST "https://hubvocal.com/v1/knowledge/search" \
  -H "Authorization: Bearer $VT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "how much is a scale and polish"
}'

У картці дзвінка кожен пошук видно в tools[] як search_knowledge разом із джерелами sources (документ, заголовок і фрагмент).

Календар і записи

Коли календар підключено, агент може під час дзвінка перевіряти вільний час, записувати абонента на прийом і скасовувати записи.

Календарі підключаються в панелі керування, у розділі Інтеграції (Google Calendar). Ендпоінта /v1 для інтеграцій немає: в API ви лише вказуєте агентові, яку з них використовувати.

Налаштування агента

Задайте об’єкт calendar агента: інтеграція й календар, тривалість прийому (15–240 хв), перерва між прийомами (0–60 хв), часовий пояс, робочі години, мінімальний час до запису (0–72 год) і на скільки днів наперед можна записатися (1–90).

PUT /v1/agents/front-desk
{
  "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
  }
}

Після цього агент отримує три вбудовані інструменти: check_availability (read), book_appointment і cancel_appointment (обидва write). Операції write проходять крок підтвердження: агент повторює час, ім’я та телефон і записує лише після чіткого «так».

Кожен запис зберігається в картці дзвінка в outcomes[] у вигляді {kind: "appointment", start, name, phone, ref}.

MCP

HubVocal підтримує Model Context Protocol у два способи: ваші агенти можуть користуватися інструментами ваших власних MCP-серверів, а MCP-клієнти — керувати платформою.

Під’єднання MCP-сервера до агентів

Додайте сервер через POST /v1/mcp-servers або в панелі керування. Ми отримаємо список його інструментів (tools/list); нові інструменти з’являються вимкненими. Увімкніть потрібні, позначте кожен як read або write і перелічіть їх у полі tools.mcp агента.

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_…"
}'
  • З’єднання встановлює API, а не агент, тож агент ніколи не бачить секрету вашого сервера.
  • URL-адреси мають бути https; локальні й приватні адреси відхиляються, зокрема й у момент з’єднання.
  • Інструменти write проходять крок підтвердження: агент повторює деталі й запускає інструмент лише після чіткого «так», не більше одного разу за дзвінок.
  • На кожен виклик відведено 10 секунд; до моделі потрапляє не більше 4 КБ результату.
  • До 10 серверів на організацію і до 50 інструментів на сервер. MCP-інструменти входять у загальний ліміт агента — 16 інструментів.

У розмові та в картці дзвінка MCP-інструмент має назву <server>.<tool>, наприклад crm.find_customer.

Керування платформою з MCP-клієнта

HubVocal і сам є MCP-сервером за адресою https://hubvocal.com/mcp (streamable HTTP). Для автентифікації передавайте ключ API організації як Bearer-токен. Ліміт запитів спільний із /v1.

Під’єднання клієнта

Замініть vt_live_… на свій ключ. Давайте клієнтові ключ лише з тими областями доступу, які йому потрібні.

Claude Code
claude mcp add --transport http hubvocal https://hubvocal.com/mcp \
  --header "Authorization: Bearer vt_live_…"

У застосунку ChatGPT власні MCP-конектори додаються в режимі розробника; чи можна передати ключ API в заголовку, залежить від вашого тарифу й налаштувань конектора. Через OpenAI API ключ можна передати безпосередньо, як показано нижче.

Інструменти

ІнструментОбласть доступуЩо робить
list_agentsreadСписок ваших агентів.
get_agentreadДані одного агента.
list_callsreadСписок дзвінків із фільтром за since, outcome або agent_id (до 50).
get_callreadДзвінок із розшифровкою та підсумком.
list_numbersreadСписок ваших номерів телефонів.
list_knowledgereadСписок документів бази знань.
search_knowledgereadПошук у базі знань.
get_usagereadВартість і хвилини за поточний місяць.
create_agentwriteСтворити агента.
update_agentwriteЗмінити чернетку агента.
publish_agentwriteОпублікувати чернетку агента.
add_knowledge_textwriteДодати текстовий документ до бази знань.
start_outbound_callcallsЗдійснити вихідний дзвінок (agent_id, to). Діють звичайні правила й ліміти для вихідних дзвінків.

Ресурси й промпти

  • agent://{id}: налаштування агента.
  • call://{id}: розшифровка дзвінка у вигляді тексту.
  • Промпт daily_call_report: підсумок дзвінків за день.

Області доступу та журнал аудиту

Кожен ключ API має області доступу, які ви обираєте, коли створюєте ключ у панелі керування:

  • read: інструменти й ендпоінти лише для читання.
  • write: також створення й зміна агентів і документів.
  • calls: також вихідні дзвінки.

Ключі, створені до появи областей доступу, мають усі три. Кожен виклик MCP-інструмента записується в журнал аудиту: ключ, інструмент, час і результат.

Веб-віджет

Віджет додає на ваш сайт кнопку дзвінка. Відвідувачі розмовляють з вашим агентом просто в браузері; серверний код на вашому боці не потрібен.

Створіть публічний ключ

У панелі керування відкрийте агента й створіть публічний ключ (pk_…). Публічний ключ належить одному агентові, і його безпечно розміщувати на сторінці: він дозволяє лише веб-дзвінки з цим агентом і лише з доменів, які ви дозволили.

  • Дозволені домени (allowed_origins): дзвінки з інших сайтів відхиляються.
  • Обмеження тривалості кожного дзвінка.
  • Не більше 10 дзвінків на годину з однієї IP-адреси.

Додайте скрипт

Вставте цей код перед закривальним тегом </body>:

HTML
<script src="https://hubvocal.com/widget.js" data-agent="pk_…" async></script>

Колір, розташування й текст кнопки (мовою відвідувача) беруться з налаштувань widget агента: color, position, label.

JSON
{
  "widget": {
    "color": "#2563eb",
    "position": "bottom-right",
    "label": "Talk to us"
  }
}

Віджет викликає POST /api/public/web-calls з публічним ключем; ваш ключ API він ніколи не бачить. Публічними ключами ви керуєте в панелі керування.

Структуровані поля у вашу CRM

Структуровані поля перетворюють розмову на дані, з якими можуть працювати ваші системи: наприклад, дату запису на прийом, товар, про який питали, чи прохання передзвонити.

Задайте поля

Додайте до 24 полів у post_call.fields агента. Кожне поле має name (малі латинські літери, цифри й підкреслення), type (string, number, boolean, enum або date), description, що підказує моделі, що шукати, а за потреби також значення enum і required.

JSON
{
  "post_call": {
    "summary": true,
    "fields": [
      {
        "name": "appointment_date",
        "type": "date",
        "description": "Requested appointment date"
      },
      {
        "name": "service",
        "type": "enum",
        "enum": [
          "checkup",
          "cleaning",
          "whitening"
        ],
        "description": "Service the caller asked about"
      },
      {
        "name": "callback",
        "type": "boolean",
        "required": true,
        "description": "Does the caller want a callback?"
      }
    ]
  }
}

Після дзвінка поля заповнює та сама модель, що пише підсумок. Значення, про які в розмові не йшлося, дорівнюють null. Ви отримуєте їх як extracted у GET /v1/calls/{room} і в списку дзвінків, а також у тілі вебхука call.ended.

JSON
{
  "extracted": {
    "appointment_date": "2026-11-12",
    "service": "cleaning",
    "callback": true
  }
}

В експорті CSV кожне поле подано окремим стовпцем.

Вебхуки дзвінків

Вебхук надсилає у вашу систему подію call.ended, щойно дзвінок завершено, а його картку разом із підсумком сформовано. З його допомогою можна створити заявку, оновити контакт у CRM або надіслати клієнтові повідомлення після дзвінка.

Якщо оцінку якості дзвінків увімкнено, можна також підписатися на call.quality_low. Його надсилають один раз за дзвінок, коли оцінка падає нижче порогу вашої організації.

Додайте до 5 вебхуків на організацію в панелі керування або через POST /v1/webhooks. Якщо ви не передасте secret, його буде згенеровано й повернуто лише в цій відповіді. URL має бути https; локальні й приватні адреси відхиляються.

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

Подія

data — той самий об’єкт дзвінка, який повертає GET /v1/calls/{room}, разом із розшифровкою й підсумком:

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": "Example Clinic",
    "channel": "web",
    "profile": "Front desk",
    "engine": "live",
    "lang": "en",
    "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": "The caller asked for an appointment on Thursday afternoon; a message was taken for a callback.",
    "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": "Clean: no loss, no gaps.",
      "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": "The caller asked for an appointment on Thursday afternoon; a message was taken for a callback.",
      "language": "en",
      "outcome": "message_taken",
      "sentiment": "neutral",
      "topics": [
        "appointment"
      ],
      "action_items": [
        "Call the caller back on Thursday morning"
      ]
    },
    "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": "Hello, thanks for calling the clinic. How can I help you today?",
        "t": 2.4,
        "interrupted": false
      },
      {
        "role": "user",
        "text": "I would like an appointment on Thursday afternoon.",
        "t": 9.8
      }
    ]
  }
}

Запити підписуються так само, як запити інструментів: X-VT-Timestamp і X-VT-Signature. Додаються ще два заголовки: X-VT-Event (тип події) і X-VT-Delivery (унікальний id цієї доставки).

Повторні спроби

Доставку зараховано, якщо протягом 10 секунд надійшла будь-яка відповідь 2xx. Інакше ми повторюємо спробу:

  1. через 1 хвилину
  2. через 5 хвилин
  3. через 30 хвилин
  4. через 2 години

Після 5 спроб загалом доставку позначають як failed. Усі спроби можна переглянути через GET /v1/webhooks/{id}/deliveries, а невдалу доставку знову поставити в чергу через POST /v1/webhooks/deliveries/{id}/retry.

Обробка дублікатів

Повторна спроба може надійти, навіть якщо вашу попередню відповідь просто втрачено дорогою, тож та сама подія інколи приходить кілька разів. Зберігайте id з X-VT-Delivery і пропускайте ті, що вже обробили. Відповідайте швидко, а повільну роботу виконуйте у фоні.

Node (Express)
// Спершу перевірте підпис (той самий код, що й для інструментів).
app.post("/hooks/voice", express.raw({ type: "*/*" }), async (req, res) => {
  if (!verify(req)) return res.sendStatus(401);

  // Цю доставку вже оброблено? Підтвердьте отримання й завершіть.
  const delivery = req.get("X-VT-Delivery");
  if (await db.deliveries.exists(delivery)) return res.sendStatus(200);
  await db.deliveries.insert(delivery);

  // Відповідайте швидко; повільну роботу виконуйте у фоні.
  res.sendStatus(200);
  const event = JSON.parse(req.body.toString("utf8"));
  queue.add("call-ended", event.data);
});

POST /v1/webhooks/{id}/test надсилає тестову подію call.ended і повертає статус, час виконання й тіло відповіді.

Підтримувані мови розмови

Якими мовами може розмовляти кожен рушій. Мова агента задається в полі lang кодами з таблиці нижче.

  • Підтримується
  • Експериментально Рушій офіційно не заявляє підтримку цієї мови. Вона працює, якщо вказати її в інструкціях, але якість ми не вимірювали.
Мова lang GPT-Live 1Gemini LiveOpenAI RealtimeКласичний
Türkçetr tr Експериментально Підтримується Експериментально Підтримується
Englishen en Підтримується Підтримується Експериментально Підтримується
Deutschde de Підтримується Підтримується Експериментально Підтримується
Françaisfr fr Підтримується Підтримується Експериментально Підтримується
Españoles es Експериментально Підтримується Експериментально Підтримується
Italianoit it Експериментально Підтримується Експериментально Підтримується
Portuguêspt pt Підтримується Підтримується Експериментально Підтримується
Nederlandsnl nl Експериментально Підтримується Експериментально Підтримується
Русскийru ru Експериментально Підтримується Експериментально Підтримується
Українськаuk uk Експериментально Підтримується Експериментально Підтримується
Polskipl pl Експериментально Підтримується Експериментально Підтримується
Românăro ro Експериментально Підтримується Експериментально Підтримується
Ελληνικάel el Експериментально Підтримується Експериментально Підтримується
Българскиbg bg Експериментально Підтримується Експериментально Підтримується
Češtinacs cs Експериментально Підтримується Експериментально Підтримується
Slovenčinask sk Експериментально Підтримується Експериментально Підтримується
Magyarhu hu Експериментально Підтримується Експериментально Підтримується
Hrvatskihr hr Експериментально Підтримується Експериментально Підтримується
Српскиsr sr Експериментально Підтримується Експериментально Підтримується
Bosanskibs bs Експериментально Підтримується Експериментально Підтримується
Slovenščinasl sl Експериментально Підтримується Експериментально Підтримується
Македонскиmk mk Експериментально Підтримується Експериментально Підтримується
Svenskasv sv Експериментально Підтримується Експериментально Підтримується
Danskda da Експериментально Підтримується Експериментально Підтримується
Norskno no Експериментально Підтримується Експериментально Підтримується
Suomifi fi Експериментально Підтримується Експериментально Підтримується
Eestiet et Експериментально Підтримується Експериментально Підтримується
Latviešulv lv Експериментально Підтримується Експериментально Підтримується
Lietuviųlt lt Експериментально Підтримується Експериментально Підтримується
Catalàca ca Експериментально Підтримується Експериментально Підтримується

Експериментальні мови варто перевірити в браузері, перш ніж запускати агента на телефонній лінії.

Ліміти й помилки

Ліміт — 120 запитів на хвилину на ключ. Понад нього ви отримаєте 429 із заголовком Retry-After (у секундах). Помилки приходять з HTTP-кодом статусу і завжди з однаковим тілом; detail — пояснення, зрозуміле людині.

402
{
  "detail": "Monthly spending limit reached"
}
КодЗначенняКоли виникає
400Некоректний запитНе вдалося прочитати тіло, бракує обов’язкового поля або значення неприпустиме (наприклад, номер не у форматі E.164 чи спроба видалити останнього агента).
401Не авторизованоНемає заголовка Authorization, ключ не починається з vt_live_ або його відкликано.
402Ліміт вичерпаноОрганізація досягла місячного ліміту витрат; нові дзвінки не починаються.
403ПризупиненоРоботу організації призупинено; нові дзвінки не починаються.
404Не знайденоТакого агента, дзвінка, номера чи ендпоінта не існує.
409КонфліктPOST /v1/agents надіслано з id, який уже існує.
429Забагато запитівПонад 120 запитів на хвилину з одного ключа. Зачекайте стільки секунд, скільки вказано в Retry-After.
502Помилка провайдераПровайдер телефонії відхилив вихідний дзвінок або не відповів.
500Помилка сервераСталося щось неочікуване. Повторіть запит; якщо помилка не зникає, напишіть нам.

Повторюйте лише запити, на які прийшла відповідь 429 або 5xx. Код 4xx означає проблему в самому запиті: повторне надсилання результату не змінить.

Повтор з урахуванням Retry-After
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;
}