Справочник API
Все эндпоинты API v1. Примеры на curl, JavaScript (fetch) и Python (requests); выбранный язык запоминается для всей страницы.
| Базовый URL | https://hubvocal.com/v1 |
|---|---|
| Авторизация | Authorization: Bearer vt_live_… |
| Лимит запросов | 120 запросов/мин на ключ · 429 + Retry-After |
| Области доступа | read · write · callsУ каждого ключа есть read, write и/или calls. read даёт доступ к GET-запросам, write — к изменениям, calls — к исходящим звонкам. Запрос за пределами областей доступа ключа получает 403. |
| Тело ошибки | {"detail": "..."} |
| OpenAPI | GET /v1/openapi.json |
Об аутентификации, встраивании и кодах ошибок — в разделе Разработчикам.
Агенты
Разговоры идут в текстовом режиме, поэтому проверяются инструкции и использование инструментов, а не голос или задержка. Симуляции запускаются из панели управления; эндпоинта /v1 для них пока нет.
Список агентов
GET /v1/agents
Возвращает всех агентов вашей организации в том порядке, в каком они показаны в панели управления.
Запрос
curl "https://hubvocal.com/v1/agents" \
-H "Authorization: Bearer $VT_API_KEY" const 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); import 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()) Ответ · 200
[
{
"id": "front-desk",
"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"
},
"published_version": 3,
"draft_changed": false
}
] Создать агента
POST /v1/agents
Создаёт агента. Каждое поле проверяется на сервере; значения сверх лимитов обрезаются. Если передать уже существующий id, запрос завершится ошибкой 409. Для обновления используйте PUT.
| Имя | Где | Тип | Описание |
|---|---|---|---|
id | body | string | Необязательно. Строчные латинские буквы, цифры и дефисы, до 48 символов. Если не указан, создаётся из имени. |
nameобязательно | body | string | Отображаемое имя, до 60 символов. |
langобязательно | body | string | Код языка разговора, например tr, en, de. Полный список — в GET /v1/voices. |
engineобязательно | body | "live" | "gemini" | "realtime" | "classic" | live: GPT-Live 1, gemini: Gemini Live, realtime: OpenAI Realtime, classic: STT → LLM → TTS. |
instructions | body | string | Системные инструкции, до 8000 символов. |
greeting | body | string | Первая фраза агента, до 400 символов. |
voice | body | object | voice (имя из /v1/voices), speed (только classic и realtime; для live и gemini фиксировано на 1.0), tone (манера речи свободным текстом, до 600 символов). |
tools | body | object | builtins (end_call, take_message) и webhooks (id инструментов); всего до 16 инструментов. Если поле пустое или отсутствует, инструментов нет. mcp: [{server_id, tools}] добавляет инструменты с ваших MCP-серверов; они входят в тот же лимит. |
turn | body | object | Настройки очерёдности реплик: sensitivity (low, balanced, high; только движок classic), first_speaker (agent или caller), silence_wait_s (1–5), idle_check_s (0 или 10–120), agent_timeout_s (0 или 10–60). Для пустых полей используются значения по умолчанию. |
recording | body | object | recording: {enabled (по умолчанию false), announce (по умолчанию true), announce_text?}. Если запись включена, агент сообщает о ней перед приветствием. |
post_call | body | object | post_call.summary: составлять резюме после завершения звонка (по умолчанию true). post_call.fields: до 24 полей, которые заполняются после звонка ({name, type, description, enum?, required?}). |
knowledge | body | object | doc_ids (до 50 документов из базы знаний) и mode ("tool"). Тогда агент получает инструмент search_knowledge. |
calendar | body | object | integration_id и calendar_id из панели управления, duration_min (15–240), buffer_min (0–60), timezone, working_hours, min_notice_h (0–72), max_days_ahead (1–90). Включает инструменты записи на приём. |
Запрос
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"
}
}' const 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": "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"
}
}),
});
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",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"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",
},
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 201
{
"id": "front-desk",
"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"
},
"published_version": null,
"draft_changed": true
} Ошибки
409Агент с таким id уже существует.
Получить агента
GET /v1/agents/{id}
Возвращает одного агента. published_version — номер опубликованной версии (null, если агент ни разу не публиковался), а draft_changed показывает, отличается ли от неё черновик.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
Запрос
curl "https://hubvocal.com/v1/agents/front-desk" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/agents/front-desk", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/agents/front-desk",
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()) Ответ · 200
{
"id": "front-desk",
"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"
},
"published_version": 3,
"draft_changed": false
} Ошибки
404Агента с таким id нет.
Обновить агента
PUT /v1/agents/{id}
Заменяет агента отправленным объектом. Отправляйте объект целиком: пропущенные поля получат значения по умолчанию. Id в пути важнее id в теле.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
nameобязательно | body | string | Отображаемое имя, до 60 символов. |
langобязательно | body | string | Код языка разговора, например tr, en, de. Полный список — в GET /v1/voices. |
engineобязательно | body | "live" | "gemini" | "realtime" | "classic" | live: GPT-Live 1, gemini: Gemini Live, realtime: OpenAI Realtime, classic: STT → LLM → TTS. |
instructions | body | string | Системные инструкции, до 8000 символов. |
greeting | body | string | Первая фраза агента, до 400 символов. |
voice | body | object | voice (имя из /v1/voices), speed (только classic и realtime; для live и gemini фиксировано на 1.0), tone (манера речи свободным текстом, до 600 символов). |
tools | body | object | builtins (end_call, take_message) и webhooks (id инструментов); всего до 16 инструментов. Если поле пустое или отсутствует, инструментов нет. mcp: [{server_id, tools}] добавляет инструменты с ваших MCP-серверов; они входят в тот же лимит. |
turn | body | object | Настройки очерёдности реплик: sensitivity (low, balanced, high; только движок classic), first_speaker (agent или caller), silence_wait_s (1–5), idle_check_s (0 или 10–120), agent_timeout_s (0 или 10–60). Для пустых полей используются значения по умолчанию. |
recording | body | object | recording: {enabled (по умолчанию false), announce (по умолчанию true), announce_text?}. Если запись включена, агент сообщает о ней перед приветствием. |
post_call | body | object | post_call.summary: составлять резюме после завершения звонка (по умолчанию true). post_call.fields: до 24 полей, которые заполняются после звонка ({name, type, description, enum?, required?}). |
knowledge | body | object | doc_ids (до 50 документов из базы знаний) и mode ("tool"). Тогда агент получает инструмент search_knowledge. |
calendar | body | object | integration_id и calendar_id из панели управления, duration_min (15–240), buffer_min (0–60), timezone, working_hours, min_notice_h (0–72), max_days_ahead (1–90). Включает инструменты записи на приём. |
Запрос
curl -X PUT "https://hubvocal.com/v1/agents/front-desk" \
-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": "cedar",
"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 res = await fetch("https://hubvocal.com/v1/agents/front-desk", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"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": "cedar",
"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); import os
import requests
resp = requests.put(
"https://hubvocal.com/v1/agents/front-desk",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"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": "cedar",
"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()) Ответ · 200
{
"id": "front-desk",
"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": "cedar",
"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"
},
"published_version": 3,
"draft_changed": true
} Ошибки
404Агента с таким id нет. Сначала создайте его через POST.
Удалить агента
DELETE /v1/agents/{id}
Удаляет агента. В организации всегда остаётся хотя бы один агент, поэтому удалить последнего нельзя.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
Запрос
curl -X DELETE "https://hubvocal.com/v1/agents/front-desk" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/agents/front-desk", {
method: "DELETE",
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); import os
import requests
resp = requests.delete(
"https://hubvocal.com/v1/agents/front-desk",
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()) Ответ · 200
{
"deleted": "front-desk"
} Ошибки
400Это последний агент в организации.404Агента с таким id нет.
Список версий
GET /v1/agents/{id}/versions
Опубликованные версии агента, от новых к старым. is_live отмечает ту, которую сейчас используют звонки.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
Запрос
curl "https://hubvocal.com/v1/agents/front-desk/versions" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/agents/front-desk/versions", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/agents/front-desk/versions",
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()) Ответ · 200
[
{
"version": 3,
"note": "Shorter greeting",
"published_at": 1793829000,
"published_by": "ayse@example.com",
"is_live": true
},
{
"version": 2,
"note": "Added price list",
"published_at": 1793740000,
"published_by": "ayse@example.com",
"is_live": false
}
] Ошибки
404Агента с таким id нет.
Получить версию
GET /v1/agents/{id}/versions/{n}
Полный объект агента в том виде, в каком он был в этой версии.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
nобязательно | path | integer | Номер версии. |
Запрос
curl "https://hubvocal.com/v1/agents/front-desk/versions/2" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/agents/front-desk/versions/2", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/agents/front-desk/versions/2",
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()) Ответ · 200
{
"id": "front-desk",
"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"
},
"version": 2
} Ошибки
404Такого агента или такой версии нет.
Опубликовать черновик
POST /v1/agents/{id}/publish
Сохраняет текущий черновик как новую версию, и звонки начинают использовать её. Уже идущие звонки остаются на своей версии.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
note | body | string | Короткая заметка о том, что изменилось. |
Запрос
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"
}' const res = await fetch("https://hubvocal.com/v1/agents/front-desk/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/front-desk/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()) Ответ · 200
{
"version": 4
} Ошибки
404Агента с таким id нет.
Откатить версию
POST /v1/agents/{id}/rollback
Копирует одну из прежних версий в черновик и публикует её как новую версию.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id агента, например front-desk. |
versionобязательно | body | integer | Версия, которую нужно вернуть. |
Запрос
curl -X POST "https://hubvocal.com/v1/agents/front-desk/rollback" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"version": 2
}' const res = await fetch("https://hubvocal.com/v1/agents/front-desk/rollback", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"version": 2
}),
});
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/front-desk/rollback",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"version": 2,
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"version": 5
} Ошибки
404Такого агента или такой версии нет.
База знаний
Список документов
GET /v1/knowledge
Документы вашей организации: статус обработки, размер, число фрагментов и агенты, которые их используют.
Запрос
curl "https://hubvocal.com/v1/knowledge" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/knowledge", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/knowledge",
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()) Ответ · 200
[
{
"id": "kb_7d21e4",
"title": "Price list 2026",
"kind": "file",
"filename": "price-list-2026.pdf",
"url": null,
"size_bytes": 248113,
"chunks": 42,
"status": "ready",
"error": null,
"created_at": 1793740000,
"used_by": [
"front-desk"
]
}
] Добавить документ
POST /v1/knowledge
Загрузите файл как multipart/form-data (поле file), отправьте текст как JSON {title, text} или одну веб-страницу как JSON {url}. Возвращает 201 со статусом processing; запрашивайте документ, пока статус не станет ready или failed.
| Имя | Где | Тип | Описание |
|---|---|---|---|
file | body | file | multipart/form-data: pdf, docx, txt, md или html, до 10 МБ. |
title | body | string | Заголовок, вместе с text. |
text | body | string | Содержимое обычным текстом. |
url | body | string | Вместо файла или текста: одна страница по https, которую нужно загрузить. |
Запрос
curl -X POST "https://hubvocal.com/v1/knowledge" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Opening hours",
"text": "We are open 09:00–18:00 on weekdays and 10:00–14:00 on Saturdays. Closed on Sundays."
}' const res = await fetch("https://hubvocal.com/v1/knowledge", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "Opening hours",
"text": "We are open 09:00–18:00 on weekdays and 10:00–14:00 on Saturdays. Closed on Sundays."
}),
});
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",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"title": "Opening hours",
"text": "We are open 09:00–18:00 on weekdays and 10:00–14:00 on Saturdays. Closed on Sundays.",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 201
{
"id": "kb_9f03b1",
"title": "Opening hours",
"kind": "text",
"filename": null,
"url": null,
"size_bytes": 96,
"chunks": 0,
"status": "processing",
"error": null,
"created_at": 1793740000,
"used_by": []
} Ошибки
400Неподдерживаемый тип файла, файл больше 10 МБ, URL не https или указывает на частный адрес, либо достигнут лимит организации (200 документов, 200 МБ).
Получить документ
GET /v1/knowledge/{id}
Документ с предпросмотром первых 2000 символов.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id документа. |
Запрос
curl "https://hubvocal.com/v1/knowledge/kb_7d21e4" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/knowledge/kb_7d21e4", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/knowledge/kb_7d21e4",
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()) Ответ · 200
{
"id": "kb_7d21e4",
"title": "Price list 2026",
"kind": "file",
"filename": "price-list-2026.pdf",
"url": null,
"size_bytes": 248113,
"chunks": 42,
"status": "ready",
"error": null,
"created_at": 1793740000,
"used_by": [
"front-desk"
],
"preview": "Price list 2026. Check-up 1,500 TRY, scale and polish 2,000 TRY…"
} Ошибки
404Документа с таким id нет.
Удалить документ
DELETE /v1/knowledge/{id}
Удаляет документ и убирает его у всех агентов, которые его использовали.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id документа. |
Запрос
curl -X DELETE "https://hubvocal.com/v1/knowledge/kb_7d21e4" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/knowledge/kb_7d21e4", {
method: "DELETE",
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); import os
import requests
resp = requests.delete(
"https://hubvocal.com/v1/knowledge/kb_7d21e4",
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()) Ответ · 200
{
"deleted": "kb_7d21e4"
} Ошибки
404Документа с таким id нет.
Поиск
POST /v1/knowledge/search
Выполняет тот же поиск, что и агент, и возвращает самые подходящие фрагменты с оценкой. Удобно, чтобы проверить документы перед запуском.
| Имя | Где | Тип | Описание |
|---|---|---|---|
queryобязательно | body | string | Что искать. |
doc_ids | body | string[] | Искать только в этих документах. |
Запрос
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"
}' 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": "how much is a scale and polish"
}),
});
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": "how much is a scale and polish",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
[
{
"doc_id": "kb_7d21e4",
"title": "Price list 2026",
"chunk_index": 3,
"text": "Scale and polish: 2,000 TRY, about 30 minutes.",
"score": 0.82
}
] Инструменты
Встроенные инструменты
Эти инструменты не нужно описывать. Включите их в tools.builtins агента. search_knowledge добавляется, когда задано knowledge. Инструменты записи на приём добавляются, когда задано calendar.
| Имя | Что делает |
|---|---|
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. |
Интеграции с календарями (Google Calendar) подключаются только в панели управления; эндпоинта /v1 для них нет. В поле calendar агента укажите id интеграции и календаря из панели управления.
Список инструментов
GET /v1/tools
Инструменты-webhooks вашей организации. Секрет никогда не возвращается; has_secret показывает, задан ли он.
Запрос
curl "https://hubvocal.com/v1/tools" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/tools", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/tools",
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()) Ответ · 200
[
{
"id": "tl_8f3a2c",
"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,
"has_secret": true,
"created_at": 1793830000,
"updated_at": 1793830000
}
] Получить инструмент
GET /v1/tools/{id}
Один инструмент-webhook. Секрет никогда не возвращается.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id инструмента. |
Запрос
curl "https://hubvocal.com/v1/tools/tl_8f3a2c" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/tools/tl_8f3a2c", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/tools/tl_8f3a2c",
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()) Ответ · 200
{
"id": "tl_8f3a2c",
"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,
"has_secret": true,
"created_at": 1793830000,
"updated_at": 1793830000
} Ошибки
404Инструмента с таким id нет.
Создать инструмент
POST /v1/tools
Создаёт инструмент-webhook для организации. Чтобы использовать его, добавьте его id в tools.webhooks агента. URL должен быть https и не может указывать на локальный или частный адрес.
| Имя | Где | Тип | Описание |
|---|---|---|---|
nameобязательно | body | string | Строчные латинские буквы, цифры и подчёркивания, начинается с буквы (3–41 символ). Уникально в организации; end_call и take_message зарезервированы. |
descriptionобязательно | body | string | Объясняет модели, когда и как использовать инструмент. |
kindобязательно | body | "read" | "write" | read выполняется сразу; write сначала просит абонента подтвердить. |
urlобязательно | body | string | Ваш эндпоинт. Только https; локальные и частные адреса отклоняются. |
parameters | body | object | Объект JSON Schema: до 20 полей типа string, number, integer или boolean (можно enum). |
timeout_s | body | integer | От 2 до 15 секунд. |
enabled | body | boolean | Отключённые инструменты модели не предлагаются. |
secret | body | string | Используется для подписи запросов. Только для записи; при обновлении пустое значение сохраняет текущий секрет, а "-" удаляет его. |
Запрос
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…"
}' 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": "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…"
}),
});
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": "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…",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 201
{
"id": "tl_8f3a2c",
"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,
"has_secret": true,
"created_at": 1793830000,
"updated_at": 1793830000
} Ошибки
400Поле нарушает правила: формат имени, URL не https, частный адрес, больше 20 параметров или тайм-аут вне диапазона.409Инструмент с таким именем уже есть, или это имя зарезервировано.
Обновить инструмент
PUT /v1/tools/{id}
Заменяет инструмент отправленным объектом. Чтобы сохранить текущий secret, оставьте поле пустым или не передавайте его; чтобы удалить, отправьте "-".
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id инструмента. |
nameобязательно | body | string | Строчные латинские буквы, цифры и подчёркивания, начинается с буквы (3–41 символ). Уникально в организации; end_call и take_message зарезервированы. |
descriptionобязательно | body | string | Объясняет модели, когда и как использовать инструмент. |
kindобязательно | body | "read" | "write" | read выполняется сразу; write сначала просит абонента подтвердить. |
urlобязательно | body | string | Ваш эндпоинт. Только https; локальные и частные адреса отклоняются. |
parameters | body | object | Объект JSON Schema: до 20 полей типа string, number, integer или boolean (можно enum). |
timeout_s | body | integer | От 2 до 15 секунд. |
enabled | body | boolean | Отключённые инструменты модели не предлагаются. |
secret | body | string | Используется для подписи запросов. Только для записи; при обновлении пустое значение сохраняет текущий секрет, а "-" удаляет его. |
Запрос
curl -X PUT "https://hubvocal.com/v1/tools/tl_8f3a2c" \
-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": 10,
"enabled": true,
"secret": ""
}' const res = await fetch("https://hubvocal.com/v1/tools/tl_8f3a2c", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"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": 10,
"enabled": true,
"secret": ""
}),
});
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/tools/tl_8f3a2c",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"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": 10,
"enabled": True,
"secret": "",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"id": "tl_8f3a2c",
"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": 10,
"enabled": true,
"has_secret": true,
"created_at": 1793830000,
"updated_at": 1793833600
} Ошибки
400Поле нарушает правила: формат имени, URL не https, частный адрес, больше 20 параметров или тайм-аут вне диапазона.404Инструмента с таким id нет.
Удалить инструмент
DELETE /v1/tools/{id}
Удаляет инструмент и убирает его у всех агентов, которые его использовали.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id инструмента. |
Запрос
curl -X DELETE "https://hubvocal.com/v1/tools/tl_8f3a2c" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/tools/tl_8f3a2c", {
method: "DELETE",
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); import os
import requests
resp = requests.delete(
"https://hubvocal.com/v1/tools/tl_8f3a2c",
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()) Ответ · 200
{
"deleted": "tl_8f3a2c"
} Ошибки
404Инструмента с таким id нет.
Проверить инструмент
POST /v1/tools/{id}/test
Отправляет настоящий запрос на URL инструмента с переданными аргументами и возвращает статус, время выполнения и до 4 КБ тела ответа.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id инструмента. |
arguments | body | object | Примерные аргументы в соответствии с parameters. |
Запрос
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()) Ответ · 200
{
"ok": true,
"status": 200,
"ms": 412,
"body": "{\"order_id\":\"A-10234\",\"status\":\"shipped\",\"eta\":\"2026-11-07\"}"
} Ошибки
404Инструмента с таким id нет.
MCP-серверы
Список MCP-серверов
GET /v1/mcp-servers
MCP-серверы вашей организации с их статусом и синхронизированными инструментами. auth_set показывает, сохранён ли секрет; сам секрет никогда не возвращается.
Запрос
curl "https://hubvocal.com/v1/mcp-servers" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/mcp-servers", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/mcp-servers",
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()) Ответ · 200
[
{
"id": "mcp_2b8e41",
"name": "crm",
"url": "https://mcp.example.com/mcp",
"transport": "streamable_http",
"auth": "bearer",
"auth_set": true,
"enabled": true,
"status": "ok",
"error": null,
"tools_synced_at": 1793831000,
"tools": [
{
"name": "find_customer",
"description": "Find a customer by phone number.",
"input_schema": {
"type": "object",
"properties": {
"phone": {
"type": "string"
}
},
"required": [
"phone"
]
},
"enabled": true,
"access": "read"
},
{
"name": "create_ticket",
"description": "Open a support ticket.",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"subject"
]
},
"enabled": false,
"access": "write"
}
]
}
] Добавить MCP-сервер
POST /v1/mcp-servers
Сохраняет сервер и загружает список его инструментов. Новые инструменты выключены. До 10 серверов на организацию.
| Имя | Где | Тип | Описание |
|---|---|---|---|
nameобязательно | body | string | Короткое имя; служит префиксом в именах инструментов (<name>.<tool>). |
urlобязательно | body | string | MCP-эндпоинт сервера. Только https; локальные и частные адреса отклоняются. |
transport | body | string | streamable_http (по умолчанию) или sse. |
auth | body | string | none, bearer или header. |
secret | body | string | Токен или значение заголовка. Хранится в зашифрованном виде и никогда не возвращается; "-" удаляет его. |
header_name | body | string | Имя заголовка, если auth — header. |
Запрос
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()) Ответ · 201
{
"id": "mcp_2b8e41",
"name": "crm",
"url": "https://mcp.example.com/mcp",
"transport": "streamable_http",
"auth": "bearer",
"auth_set": true,
"enabled": true,
"status": "ok",
"error": null,
"tools_synced_at": 1793831000,
"tools": [
{
"name": "find_customer",
"description": "Find a customer by phone number.",
"input_schema": {
"type": "object",
"properties": {
"phone": {
"type": "string"
}
},
"required": [
"phone"
]
},
"enabled": false,
"access": "write"
},
{
"name": "create_ticket",
"description": "Open a support ticket.",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"subject"
]
},
"enabled": false,
"access": "write"
}
]
} Ошибки
400URL не https или указывает на частный адрес, либо не удалось получить список инструментов сервера.409У организации уже есть 10 MCP-серверов.
Обновить MCP-сервер
PUT /v1/mcp-servers/{id}
Меняет имя, URL, аутентификацию или состояние (включён/выключен), а также то, какие инструменты включены и относятся ли они к read или write. Чтобы удалить секрет, отправьте secret: "-".
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id MCP-сервера. |
name | body | string | Короткое имя; служит префиксом в именах инструментов (<name>.<tool>). |
url | body | string | MCP-эндпоинт сервера. Только https; локальные и частные адреса отклоняются. |
enabled | body | boolean | Выключенный сервер агентам не предлагается. |
auth | body | string | none, bearer или header. |
secret | body | string | Токен или значение заголовка. Хранится в зашифрованном виде и никогда не возвращается; "-" удаляет его. |
tools | body | object[] | [{name, enabled, access}] для каждого инструмента; access — read или write. |
Запрос
curl -X PUT "https://hubvocal.com/v1/mcp-servers/mcp_2b8e41" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tools": [
{
"name": "find_customer",
"enabled": true,
"access": "read"
},
{
"name": "create_ticket",
"enabled": true,
"access": "write"
}
]
}' const res = await fetch("https://hubvocal.com/v1/mcp-servers/mcp_2b8e41", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"tools": [
{
"name": "find_customer",
"enabled": true,
"access": "read"
},
{
"name": "create_ticket",
"enabled": true,
"access": "write"
}
]
}),
});
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/mcp-servers/mcp_2b8e41",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"tools": [
{
"name": "find_customer",
"enabled": True,
"access": "read",
},
{
"name": "create_ticket",
"enabled": True,
"access": "write",
},
],
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"id": "mcp_2b8e41",
"name": "crm",
"url": "https://mcp.example.com/mcp",
"transport": "streamable_http",
"auth": "bearer",
"auth_set": true,
"enabled": true,
"status": "ok",
"error": null,
"tools_synced_at": 1793831000,
"tools": [
{
"name": "find_customer",
"description": "Find a customer by phone number.",
"input_schema": {
"type": "object",
"properties": {
"phone": {
"type": "string"
}
},
"required": [
"phone"
]
},
"enabled": true,
"access": "read"
},
{
"name": "create_ticket",
"description": "Open a support ticket.",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"subject"
]
},
"enabled": true,
"access": "write"
}
]
} Ошибки
400URL не https или указывает на частный адрес, либо не удалось получить список инструментов сервера.404MCP-сервера с таким id нет.
Удалить MCP-сервер
DELETE /v1/mcp-servers/{id}
Удаляет сервер и убирает его инструменты у всех агентов.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id MCP-сервера. |
Запрос
curl -X DELETE "https://hubvocal.com/v1/mcp-servers/mcp_2b8e41" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/mcp-servers/mcp_2b8e41", {
method: "DELETE",
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); import os
import requests
resp = requests.delete(
"https://hubvocal.com/v1/mcp-servers/mcp_2b8e41",
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()) Ответ · 200
{
"deleted": "mcp_2b8e41"
} Ошибки
404MCP-сервера с таким id нет.
Синхронизировать инструменты
POST /v1/mcp-servers/{id}/sync
Заново загружает список инструментов. Новые инструменты появляются выключенными, с доступом write.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id MCP-сервера. |
Запрос
curl -X POST "https://hubvocal.com/v1/mcp-servers/mcp_2b8e41/sync" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/mcp-servers/mcp_2b8e41/sync", {
method: "POST",
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); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/mcp-servers/mcp_2b8e41/sync",
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()) Ответ · 200
{
"id": "mcp_2b8e41",
"name": "crm",
"url": "https://mcp.example.com/mcp",
"transport": "streamable_http",
"auth": "bearer",
"auth_set": true,
"enabled": true,
"status": "ok",
"error": null,
"tools_synced_at": 1793831000,
"tools": [
{
"name": "find_customer",
"description": "Find a customer by phone number.",
"input_schema": {
"type": "object",
"properties": {
"phone": {
"type": "string"
}
},
"required": [
"phone"
]
},
"enabled": true,
"access": "read"
},
{
"name": "create_ticket",
"description": "Open a support ticket.",
"input_schema": {
"type": "object",
"properties": {
"subject": {
"type": "string"
},
"body": {
"type": "string"
}
},
"required": [
"subject"
]
},
"enabled": false,
"access": "write"
}
]
} Ошибки
400URL не https или указывает на частный адрес, либо не удалось получить список инструментов сервера.404MCP-сервера с таким id нет.
Проверить инструмент
POST /v1/mcp-servers/{id}/test
Вызывает один инструмент на сервере с переданными аргументами и возвращает время выполнения, до 4 КБ содержимого и признак того, сообщил ли инструмент об ошибке.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id MCP-сервера. |
toolобязательно | body | string | Имя вызываемого инструмента. |
arguments | body | object | Аргументы инструмента в соответствии с его input_schema. |
Запрос
curl -X POST "https://hubvocal.com/v1/mcp-servers/mcp_2b8e41/test" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "find_customer",
"arguments": {
"phone": "+905321234567"
}
}' const res = await fetch("https://hubvocal.com/v1/mcp-servers/mcp_2b8e41/test", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"tool": "find_customer",
"arguments": {
"phone": "+905321234567"
}
}),
});
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/mcp_2b8e41/test",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"tool": "find_customer",
"arguments": {
"phone": "+905321234567",
},
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"ok": true,
"ms": 236,
"content": "{\"customer_id\":\"C-1042\",\"name\":\"Ayşe Yılmaz\",\"open_tickets\":0}",
"is_error": false
} Ошибки
404MCP-сервера с таким id нет.
Webhooks
Список webhooks
GET /v1/webhooks
Webhooks вашей организации с результатом последней доставки. Секрет здесь никогда не возвращается; secret_set показывает, задан ли он.
Запрос
curl "https://hubvocal.com/v1/webhooks" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/webhooks", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/webhooks",
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()) Ответ · 200
[
{
"id": "wh_3c9d1e",
"url": "https://crm.example.com/hooks/voice",
"events": [
"call.ended"
],
"enabled": true,
"secret_set": true,
"created_at": 1793830000,
"last_delivery": {
"status": "ok",
"at": 1793832440.2,
"http_status": 200
}
}
] Создать webhook
POST /v1/webhooks
Подписывает URL на события. Сейчас событие одно — call.ended. До 5 webhooks на организацию. Если не указать secret, он будет сгенерирован и вернётся только в этом ответе.
| Имя | Где | Тип | Описание |
|---|---|---|---|
urlобязательно | body | string | Ваш эндпоинт. Только https; локальные и частные адреса отклоняются. |
eventsобязательно | body | string[] | События, которые нужно получать. Сейчас ["call.ended"]. |
enabled | body | boolean | Отключённые webhooks ничего не получают. |
secret | body | string | Секрет для подписи. Если не указан, генерируется автоматически; возвращается только один раз. |
Запрос
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()) Ответ · 201
{
"id": "wh_3c9d1e",
"url": "https://crm.example.com/hooks/voice",
"events": [
"call.ended"
],
"enabled": true,
"secret_set": true,
"created_at": 1793830000,
"last_delivery": null,
"secret": "whsec_9QmZ…"
} Ошибки
400URL не https, указывает на частный адрес, или событие неизвестно.409У организации уже есть 5 webhooks.
Обновить webhook
PUT /v1/webhooks/{id}
Меняет URL, события или включает и отключает webhook. Отправьте secret: "-", чтобы сгенерировать новый секрет; он вернётся в ответе.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id webhook. |
url | body | string | Ваш эндпоинт. Только https; локальные и частные адреса отклоняются. |
events | body | string[] | События, которые нужно получать. Сейчас ["call.ended"]. |
enabled | body | boolean | Отключённые webhooks ничего не получают. |
secret | body | string | "-" генерирует новый секрет. Не передавайте поле, чтобы сохранить текущий. |
Запрос
curl -X PUT "https://hubvocal.com/v1/webhooks/wh_3c9d1e" \
-H "Authorization: Bearer $VT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}' const res = await fetch("https://hubvocal.com/v1/webhooks/wh_3c9d1e", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.VT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"enabled": false
}),
});
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/webhooks/wh_3c9d1e",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"enabled": False,
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"id": "wh_3c9d1e",
"url": "https://crm.example.com/hooks/voice",
"events": [
"call.ended"
],
"enabled": false,
"secret_set": true,
"created_at": 1793830000,
"last_delivery": {
"status": "ok",
"at": 1793832440.2,
"http_status": 200
}
} Ошибки
400URL не https, указывает на частный адрес, или событие неизвестно.404Webhook с таким id нет.
Удалить webhook
DELETE /v1/webhooks/{id}
Удаляет webhook. Доставки, ожидающие отправки, отменяются.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id webhook. |
Запрос
curl -X DELETE "https://hubvocal.com/v1/webhooks/wh_3c9d1e" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/webhooks/wh_3c9d1e", {
method: "DELETE",
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); import os
import requests
resp = requests.delete(
"https://hubvocal.com/v1/webhooks/wh_3c9d1e",
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()) Ответ · 200
{
"deleted": "wh_3c9d1e"
} Ошибки
404Webhook с таким id нет.
Проверить webhook
POST /v1/webhooks/{id}/test
Отправляет на URL тестовое событие call.ended и возвращает статус, время ответа и тело ответа.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id webhook. |
Запрос
curl -X POST "https://hubvocal.com/v1/webhooks/wh_3c9d1e/test" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/webhooks/wh_3c9d1e/test", {
method: "POST",
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); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/webhooks/wh_3c9d1e/test",
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()) Ответ · 200
{
"ok": true,
"http_status": 200,
"ms": 184,
"body": "{\"received\":true}"
} Ошибки
404Webhook с таким id нет.
Список доставок
GET /v1/webhooks/{id}/deliveries
Последние 50 доставок webhook, от новых к старым: статус (pending, ok, failed), число попыток, HTTP-статус, ошибка и время следующей попытки.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id webhook. |
Запрос
curl "https://hubvocal.com/v1/webhooks/wh_3c9d1e/deliveries" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/webhooks/wh_3c9d1e/deliveries", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/webhooks/wh_3c9d1e/deliveries",
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()) Ответ · 200
[
{
"id": "dl_51b7a9",
"event": "call.ended",
"call_id": "demo-8585522d",
"status": "ok",
"attempts": 1,
"http_status": 200,
"error": null,
"created_at": 1793832440.2,
"next_attempt_at": null
},
{
"id": "dl_51a3c4",
"event": "call.ended",
"call_id": "tel-6b1f04d2",
"status": "pending",
"attempts": 2,
"http_status": 503,
"error": "HTTP 503",
"created_at": 1793832101.7,
"next_attempt_at": 1793833901.7
}
] Ошибки
404Webhook с таким id нет.
Повторить доставку
POST /v1/webhooks/deliveries/{id}/retry
Снова ставит неудачную доставку в очередь.
| Имя | Где | Тип | Описание |
|---|---|---|---|
idобязательно | path | string | Id доставки из списка доставок. |
Запрос
curl -X POST "https://hubvocal.com/v1/webhooks/deliveries/dl_51a3c4/retry" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/webhooks/deliveries/dl_51a3c4/retry", {
method: "POST",
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); import os
import requests
resp = requests.post(
"https://hubvocal.com/v1/webhooks/deliveries/dl_51a3c4/retry",
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()) Ответ · 200
{
"ok": true
} Ошибки
404Доставки с таким id нет, или она не завершилась ошибкой.
Голоса и движки
Список движков, голосов и лимитов
GET /v1/voices
Всё, что нужно для формы агента: названия движков, допустимые голоса для каждого движка, диапазоны скорости, языки, предустановки тона, модель за каждым движком и лимиты длины полей. Пример ниже сокращён.
Запрос
curl "https://hubvocal.com/v1/voices" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/voices", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/voices",
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()) Ответ · 200
{
"engines": {
"live": "GPT-Live 1 (OpenAI full-duplex ses modeli)",
"gemini": "Gemini Live (Google ses-sese model)",
"realtime": "Realtime (OpenAI ses-sese model)",
"classic": "Klasik (Deepgram + OpenAI LLM + OpenAI TTS)"
},
"voices": {
"live": [
"marin",
"cedar",
"gleam",
"meridian"
],
"gemini": [
"Kore",
"Aoede",
"Charon",
"Puck"
],
"realtime": [
"marin",
"cedar",
"alloy"
],
"classic": [
"alloy",
"coral",
"sage"
]
},
"speed": {
"live": {
"min": 1,
"max": 1
},
"gemini": {
"min": 1,
"max": 1
},
"realtime": {
"min": 0.25,
"max": 1.5
},
"classic": {
"min": 0.25,
"max": 4
}
},
"langs": {
"tr": "Türkçe",
"en": "English"
},
"presets": [
{
"id": "calm",
"label": "Sakin ve güven veren",
"text": "Sakin, yumuşak ve güven veren bir tonla konuş."
}
],
"models": {
"live": {
"model": "gpt-live-1",
"backend": "gpt-5.6-luna"
},
"gemini": {
"model": "gemini-3.8-live",
"ready": true
},
"realtime": {
"model": "gpt-realtime-2.1-mini"
},
"classic": {
"stt": "nova-3",
"llm": "gpt-4.1-mini",
"tts": "gpt-4o-mini-tts"
}
},
"limits": {
"tone": 600,
"instructions": 8000,
"greeting": 400
}
} Звонки
Сроки хранения задаются для организации в панели управления: recordings_days (по умолчанию 30), transcripts_days (по умолчанию 365) и pii_redaction. Ежедневная задача удаляет записи и расшифровки с истёкшим сроком хранения; строка звонка и его стоимость сохраняются.
Веб-виджет: публичные ключи (pk_…) создаются для каждого агента в панели управления. Скрипт виджета вызывает POST /api/public/web-calls; подробнее — в руководстве на странице «Разработчикам».
Начать веб-звонок
POST /v1/web-calls
Открывает комнату для звонка из браузера и возвращает короткоживущий токен доступа. Передайте server_url и token в livekit-client в браузере; агент подключится к комнате сам. Вызывайте этот метод со своего сервера, чтобы API-ключ не попал в браузер. Вместо agent_id можно отправить полный объект agent и проверить черновик без сохранения.
| Имя | Где | Тип | Описание |
|---|---|---|---|
agent_id | body | string | Id сохранённого агента. Обязателен, если не отправлен agent. |
agent | body | object | Полный объект агента (те же поля, что в POST /v1/agents), который используется только для этого звонка. |
version | body | string | integer | Какую версию использовать: "live" (по умолчанию), "draft" или номер версии. |
Запрос
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"
}' const 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": "front-desk"
}),
});
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/web-calls",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
json={
"agent_id": "front-desk",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"server_url": "wss://hubvocal.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"room": "demo-3f9a1c2e",
"max_seconds": 300
} Ошибки
400Не отправлены ни agent_id, ни agent.402Организация достигла месячного лимита расходов.403Работа организации приостановлена.404Агента с таким id нет.
Совершить исходящий звонок
POST /v1/calls
Звонит на номер телефона с одного из номеров вашей организации и подключает агента, когда трубку возьмут. Провайдер (DIDWW, Twilio или ваш SIP-транк) определяется по номеру from.
| Имя | Где | Тип | Описание |
|---|---|---|---|
fromобязательно | body | string | Один из номеров вашей организации в формате E.164 (+90…). |
toобязательно | body | string | Номер, на который нужно позвонить, в формате E.164. |
agent_id | body | string | Какой агент будет говорить. Если не указан, отвечает агент, назначенный на номер from. |
version | body | string | integer | Какую версию использовать: "live" (по умолчанию), "draft" или номер версии. |
Запрос
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"
}' 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": "front-desk"
}),
});
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": "front-desk",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"room": "tel-out-9c41e0b7",
"provider": "didww",
"sip_call_id": "SCL_7mVt2kQb9xRa"
} Ошибки
400Номер не в международном формате или from не входит в ваши номера.402Организация достигла месячного лимита расходов.502Провайдер телефонии отклонил звонок.
Список звонков
GET /v1/calls
Звонки от новых к старым, веб и телефонные. quality — сводка измерений на стороне браузера; для телефонных звонков null. ended_by: caller, agent, max_call_seconds, caller_unresponsive, agent_unresponsive, no_answer или unknown. channel: web, phone_in или phone_out. В каждой строке также есть outcome и summary_text (первые 160 символов резюме). ended_by равен supervisor, если звонок завершил супервизор из консоли реального времени.
| Имя | Где | Тип | Описание |
|---|---|---|---|
limit | query | integer | По умолчанию 50, максимум 500. |
agent | query | string | Только звонки этого агента (имя агента). |
outcome | query | string | Только звонки с этим итогом. |
Запрос
curl "https://hubvocal.com/v1/calls?limit=20" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/calls?limit=20", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/calls",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
params={
"limit": 20,
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
[
{
"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": 0,
"total_ms": 0,
"max_ms": 0
},
"quality": {
"verdict": "Clean: no loss, no gaps.",
"protocol": "UDP",
"loss_pct": 0,
"conceal_pct": 0,
"jitter_ms": 4,
"rtt_ms": 38
}
}
] Получить звонок
GET /v1/calls/{room}
Полная карточка звонка: разбивка стоимости по провайдерам, расшифровка, время до приветствия, пропуски на стороне агента и шкала качества в браузере окнами по 2 секунды. Поля стоимости зависят от движка (live, llm, stt, tts, realtime…); в каждом есть usd. source_gaps.events содержит пары [секунда, мс]. В summary — text, outcome (resolved, message_taken, transferred, unresolved, no_conversation, spam), sentiment, topics и action_items. В supervisors — кто подключался к звонку из консоли реального времени и в каких режимах. В recording — available, duration_s и size_bytes. В extracted — значения post_call.fields агента (null, если значение не упоминалось).
| Имя | Где | Тип | Описание |
|---|---|---|---|
roomобязательно | path | string | Имя комнаты из списка звонков или из POST /v1/web-calls. |
Запрос
curl "https://hubvocal.com/v1/calls/demo-8585522d" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/calls/demo-8585522d", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/calls/demo-8585522d",
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()) Ответ · 200
{
"call": {
"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
}
]
},
"quality": {
"summary": {
"verdict": "Clean: no loss, no gaps.",
"protocol": "UDP",
"rtt_ms": 38,
"protocols_seen": [
"UDP"
],
"loss_pct": 0,
"conceal_pct": 0,
"jitter_ms": 4,
"buffer_ms": 62
},
"timeline": [
{
"t": 2,
"loss": 0,
"conceal": 0,
"jitter": 3,
"buffer": 60,
"proto": "UDP"
},
{
"t": 4,
"loss": 0,
"conceal": 0,
"jitter": 4,
"buffer": 62,
"proto": "UDP"
}
]
}
} Ошибки
404Звонок не найден.
Скачать запись
GET /v1/calls/{room}/recording
Аудио звонка в формате OGG/Opus. Поддерживаются Range-запросы. 404, если звонок не записывался или срок хранения записи истёк.
| Имя | Где | Тип | Описание |
|---|---|---|---|
roomобязательно | path | string | Имя комнаты из списка звонков или из POST /v1/web-calls. |
Запрос
curl "https://hubvocal.com/v1/calls/demo-8585522d/recording" \
-H "Authorization: Bearer $VT_API_KEY" \
-o call.ogg import fs from "node:fs";
const res = await fetch("https://hubvocal.com/v1/calls/demo-8585522d/recording", {
headers: { Authorization: `Bearer ${process.env.VT_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
fs.writeFileSync("call.ogg", Buffer.from(await res.arrayBuffer())); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/calls/demo-8585522d/recording",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
timeout=60,
)
resp.raise_for_status()
with open("call.ogg", "wb") as f:
f.write(resp.content) Ответ · 200
HTTP/1.1 200 OK
Content-Type: audio/ogg
Accept-Ranges: bytes
Content-Length: 201344
<OGG/Opus> Ошибки
404Звонок не записывался, или срок хранения записи истёк.
Удалить звонок
DELETE /v1/calls/{room}
Сразу удаляет запись, расшифровку и резюме звонка, например по запросу в рамках KVKK или GDPR. Строка звонка и его стоимость сохраняются.
| Имя | Где | Тип | Описание |
|---|---|---|---|
roomобязательно | path | string | Имя комнаты из списка звонков или из POST /v1/web-calls. |
Запрос
curl -X DELETE "https://hubvocal.com/v1/calls/demo-8585522d" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/calls/demo-8585522d", {
method: "DELETE",
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); import os
import requests
resp = requests.delete(
"https://hubvocal.com/v1/calls/demo-8585522d",
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()) Ответ · 200
{
"deleted": "demo-8585522d"
} Ошибки
404Звонок не найден.
Экспорт звонков (CSV)
GET /v1/calls/export
Звонки за период в формате CSV, по одной строке на звонок; структурированные поля идут дополнительными столбцами.
| Имя | Где | Тип | Описание |
|---|---|---|---|
formatобязательно | query | string | Только csv. |
from | query | date | YYYY-MM-DD, включительно. |
to | query | date | YYYY-MM-DD, включительно. |
agent_id | query | string | Только звонки этого агента. |
Запрос
curl "https://hubvocal.com/v1/calls/export?format=csv&from=2026-11-01&to=2026-11-05" \
-H "Authorization: Bearer $VT_API_KEY" \
-o calls.csv import fs from "node:fs";
const res = await fetch("https://hubvocal.com/v1/calls/export?format=csv&from=2026-11-01&to=2026-11-05", {
headers: { Authorization: `Bearer ${process.env.VT_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).detail}`);
fs.writeFileSync("calls.csv", Buffer.from(await res.arrayBuffer())); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/calls/export",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
params={
"format": "csv",
"from": "2026-11-01",
"to": "2026-11-05",
},
timeout=60,
)
resp.raise_for_status()
with open("calls.csv", "wb") as f:
f.write(resp.content) Ответ · 200
room,started_at,agent,channel,duration_s,total_usd,outcome,appointment_date,callback
demo-8585522d,2026-11-05T09:46:49Z,front-desk,web,25.1,0.021215,message_taken,2026-11-12,true Номера телефонов
Список номеров телефонов
GET /v1/numbers
Номера телефонов вашей организации и агент, который отвечает на каждом (profile_id), а также готовность каждого провайдера телефонии. Номера добавляет команда платформы, агентов назначаете вы.
Запрос
curl "https://hubvocal.com/v1/numbers" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/numbers", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/numbers",
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()) Ответ · 200
{
"numbers": [
{
"number": "+908501234567",
"provider": "didww",
"org_id": "6f1c2a40-3b7e-4d0c-9a51-2f8e7c1d9b03",
"org_name": "Example Clinic",
"profile_id": "front-desk",
"label": "Main line"
}
],
"sip": {
"enabled": true,
"room_prefix": "tel-",
"providers": {
"didww": {
"inbound_ips": "46.19.209.14,46.19.210.14",
"inbound_auth": false,
"outbound_host": "trunk.example.com",
"outbound_auth": true,
"outbound_ready": true,
"inbound_ready": true
}
}
}
} Назначить агента на номер
PUT /v1/numbers/{number}
Выбирает агента, который отвечает на входящие звонки на этот номер. Чтобы снять назначение, отправьте agent_id: null. Знак + в номере кодируйте в пути как %2B (префикс 00 тоже подходит).
| Имя | Где | Тип | Описание |
|---|---|---|---|
numberобязательно | path | string | Номер в формате E.164, закодированный для URL. |
agent_id | body | string | null | Агент, который отвечает на входящие звонки. |
label | body | string | Заметка для команды, до 60 символов. |
Запрос
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"
}' 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": "front-desk",
"label": "Main line"
}),
});
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": "front-desk",
"label": "Main line",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"ok": true
} Ошибки
400Такого агента нет.404Номер не принадлежит вашей организации.
Потребление
Потребление
GET /v1/usage
Звонки, минуты и затраты на провайдеров для вашей организации — по провайдерам и по дням (Europe/Istanbul). По умолчанию — текущий месяц. kind: llm, tts, stt или voice (движки «речь в речь»).
| Имя | Где | Тип | Описание |
|---|---|---|---|
from | query | date | YYYY-MM-DD, включительно. |
to | query | date | YYYY-MM-DD, включительно. |
Запрос
curl "https://hubvocal.com/v1/usage?from=2026-11-01&to=2026-11-05" \
-H "Authorization: Bearer $VT_API_KEY" const res = await fetch("https://hubvocal.com/v1/usage?from=2026-11-01&to=2026-11-05", {
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); import os
import requests
resp = requests.get(
"https://hubvocal.com/v1/usage",
headers={"Authorization": f"Bearer {os.environ['VT_API_KEY']}"},
params={
"from": "2026-11-01",
"to": "2026-11-05",
},
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"from": "2026-11-01",
"to": "2026-11-05",
"totals": {
"calls": 42,
"minutes": 81.2,
"usd": 3.94
},
"by_provider": [
{
"provider": "openai",
"model": "gpt-live-1",
"kind": "voice",
"calls": 30,
"input_tokens": 0,
"cached_tokens": 0,
"output_tokens": 0,
"audio_s": 2900.1,
"usd": 2.41
}
],
"by_day": [
{
"date": "2026-11-05",
"usd": 1.2,
"minutes": 20.5,
"calls": 9
}
],
"by_org": [
{
"org_id": "6f1c2a40-3b7e-4d0c-9a51-2f8e7c1d9b03",
"org_name": "Example Clinic",
"usd": 3.94,
"minutes": 81.2,
"calls": 42
}
]
} Спецификация
Спецификация OpenAPI
GET /v1/openapi.json Ключ не нужен
Документ OpenAPI 3.1 для этого API. Ключ не нужен. Импортируйте его в Postman, Insomnia или генератор клиентов.
Запрос
curl "https://hubvocal.com/v1/openapi.json" const res = await fetch("https://hubvocal.com/v1/openapi.json");
const data = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${data.detail}`);
console.log(data); import requests
resp = requests.get(
"https://hubvocal.com/v1/openapi.json",
timeout=30,
)
if not resp.ok:
raise RuntimeError(f"{resp.status_code}: {resp.json()['detail']}")
print(resp.json()) Ответ · 200
{
"openapi": "3.1.0",
"info": {
"title": "HubVocal API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://hubvocal.com/v1"
}
],
"paths": {}
}