Довідник 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 | тіло | string | Необов’язково. Малі латинські літери, цифри й дефіси, до 48 символів. Якщо не вказано, генерується з назви. |
nameобов’язковий | тіло | string | Назва для відображення, до 60 символів. |
langобов’язковий | тіло | string | Код мови розмови, наприклад tr, en, de. Повний список — у GET /v1/voices. |
engineобов’язковий | тіло | "live" | "gemini" | "realtime" | "classic" | live: GPT-Live 1, gemini: Gemini Live, realtime: OpenAI Realtime, classic: STT → LLM → TTS. |
instructions | тіло | string | Системні інструкції, до 8000 символів. |
greeting | тіло | string | Що агент каже першим, до 400 символів. |
voice | тіло | object | voice (назва з /v1/voices), speed (лише classic і realtime; для live і gemini фіксовано 1.0), tone (манера мовлення довільним текстом, до 600 символів). |
tools | тіло | object | builtins (end_call, take_message) і webhooks (id інструментів); загалом до 16 інструментів. Порожнє або відсутнє поле означає, що інструментів немає. mcp: [{server_id, tools}] додає інструменти з ваших MCP-серверів; вони входять у той самий ліміт. |
turn | тіло | 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 | тіло | object | recording: {enabled (за замовчуванням false), announce (за замовчуванням true), announce_text?}. Якщо запис увімкнено, агент повідомляє про нього перед привітанням. |
post_call | тіло | object | post_call.summary: складати підсумок після завершення дзвінка (за замовчуванням true). post_call.fields: до 24 полів, які заповнюються після дзвінка ({name, type, description, enum?, required?}). |
knowledge | тіло | object | doc_ids (до 50 документів бази знань) і mode ("tool"). Тоді агент отримує інструмент search_knowledge. |
calendar | тіло | 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обов’язковий | шлях | 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обов’язковий | шлях | string | Id агента, наприклад front-desk. |
nameобов’язковий | тіло | string | Назва для відображення, до 60 символів. |
langобов’язковий | тіло | string | Код мови розмови, наприклад tr, en, de. Повний список — у GET /v1/voices. |
engineобов’язковий | тіло | "live" | "gemini" | "realtime" | "classic" | live: GPT-Live 1, gemini: Gemini Live, realtime: OpenAI Realtime, classic: STT → LLM → TTS. |
instructions | тіло | string | Системні інструкції, до 8000 символів. |
greeting | тіло | string | Що агент каже першим, до 400 символів. |
voice | тіло | object | voice (назва з /v1/voices), speed (лише classic і realtime; для live і gemini фіксовано 1.0), tone (манера мовлення довільним текстом, до 600 символів). |
tools | тіло | object | builtins (end_call, take_message) і webhooks (id інструментів); загалом до 16 інструментів. Порожнє або відсутнє поле означає, що інструментів немає. mcp: [{server_id, tools}] додає інструменти з ваших MCP-серверів; вони входять у той самий ліміт. |
turn | тіло | 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 | тіло | object | recording: {enabled (за замовчуванням false), announce (за замовчуванням true), announce_text?}. Якщо запис увімкнено, агент повідомляє про нього перед привітанням. |
post_call | тіло | object | post_call.summary: складати підсумок після завершення дзвінка (за замовчуванням true). post_call.fields: до 24 полів, які заповнюються після дзвінка ({name, type, description, enum?, required?}). |
knowledge | тіло | object | doc_ids (до 50 документів бази знань) і mode ("tool"). Тоді агент отримує інструмент search_knowledge. |
calendar | тіло | 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обов’язковий | шлях | 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обов’язковий | шлях | 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обов’язковий | шлях | string | Id агента, наприклад front-desk. |
nобов’язковий | шлях | 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обов’язковий | шлях | string | Id агента, наприклад front-desk. |
note | тіло | 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обов’язковий | шлях | string | Id агента, наприклад front-desk. |
versionобов’язковий | тіло | 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 | тіло | file | multipart/form-data: pdf, docx, txt, md або html, до 10 МБ. |
title | тіло | string | Заголовок, разом із text. |
text | тіло | string | Вміст звичайним текстом. |
url | тіло | 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обов’язковий | шлях | 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обов’язковий | шлях | 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обов’язковий | тіло | string | Що шукати. |
doc_ids | тіло | 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
Вебхук-інструменти вашої організації. Секрет ніколи не повертається; 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}
Один вебхук-інструмент. Секрет ніколи не повертається.
| Назва | Де | Тип | Опис |
|---|---|---|---|
idобов’язковий | шлях | 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
Створює вебхук-інструмент для організації. Щоб ним скористатися, додайте його id у tools.webhooks агента. URL має бути https і не може вказувати на локальну чи приватну адресу.
| Назва | Де | Тип | Опис |
|---|---|---|---|
nameобов’язковий | тіло | string | Малі латинські літери, цифри й підкреслення, першим має бути літера (3–41 символ). Унікальна в межах організації; end_call і take_message зарезервовано. |
descriptionобов’язковий | тіло | string | Пояснює моделі, коли і як використовувати інструмент. |
kindобов’язковий | тіло | "read" | "write" | read виконується одразу; write спершу просить абонента підтвердити. |
urlобов’язковий | тіло | string | Ваш ендпоінт. Лише https; локальні й приватні адреси відхиляються. |
parameters | тіло | object | Об’єкт JSON Schema: до 20 полів типів string, number, integer або boolean (можна enum). |
timeout_s | тіло | integer | Від 2 до 15 секунд. |
enabled | тіло | boolean | Вимкнені інструменти моделі не пропонуються. |
secret | тіло | 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обов’язковий | шлях | string | Id інструмента. |
nameобов’язковий | тіло | string | Малі латинські літери, цифри й підкреслення, першим має бути літера (3–41 символ). Унікальна в межах організації; end_call і take_message зарезервовано. |
descriptionобов’язковий | тіло | string | Пояснює моделі, коли і як використовувати інструмент. |
kindобов’язковий | тіло | "read" | "write" | read виконується одразу; write спершу просить абонента підтвердити. |
urlобов’язковий | тіло | string | Ваш ендпоінт. Лише https; локальні й приватні адреси відхиляються. |
parameters | тіло | object | Об’єкт JSON Schema: до 20 полів типів string, number, integer або boolean (можна enum). |
timeout_s | тіло | integer | Від 2 до 15 секунд. |
enabled | тіло | boolean | Вимкнені інструменти моделі не пропонуються. |
secret | тіло | 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обов’язковий | шлях | 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обов’язковий | шлях | string | Id інструмента. |
arguments | тіло | 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обов’язковий | тіло | string | Коротка назва; слугує префіксом у назвах інструментів (<name>.<tool>). |
urlобов’язковий | тіло | string | MCP-ендпоінт сервера. Лише https; локальні й приватні адреси відхиляються. |
transport | тіло | string | streamable_http (за замовчуванням) або sse. |
auth | тіло | string | none, bearer або header. |
secret | тіло | string | Токен або значення заголовка. Зберігається в зашифрованому вигляді й ніколи не повертається; "-" видаляє його. |
header_name | тіло | 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обов’язковий | шлях | string | Id MCP-сервера. |
name | тіло | string | Коротка назва; слугує префіксом у назвах інструментів (<name>.<tool>). |
url | тіло | string | MCP-ендпоінт сервера. Лише https; локальні й приватні адреси відхиляються. |
enabled | тіло | boolean | Вимкнений сервер агентам не пропонується. |
auth | тіло | string | none, bearer або header. |
secret | тіло | string | Токен або значення заголовка. Зберігається в зашифрованому вигляді й ніколи не повертається; "-" видаляє його. |
tools | тіло | 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обов’язковий | шлях | 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обов’язковий | шлях | 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обов’язковий | шлях | string | Id MCP-сервера. |
toolобов’язковий | тіло | string | Назва інструмента, який треба викликати. |
arguments | тіло | 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 немає.
Вебхуки
Список вебхуків
GET /v1/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
}
}
] Створення вебхука
POST /v1/webhooks
Підписує URL на події. Наразі подія лише одна — call.ended. До 5 вебхуків на організацію. Якщо не вказати secret, його буде згенеровано й повернуто лише в цій відповіді.
| Назва | Де | Тип | Опис |
|---|---|---|---|
urlобов’язковий | тіло | string | Ваш ендпоінт. Лише https; локальні й приватні адреси відхиляються. |
eventsобов’язковий | тіло | string[] | Події, які потрібно отримувати. Наразі ["call.ended"]. |
enabled | тіло | boolean | Вимкнені вебхуки нічого не отримують. |
secret | тіло | 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 вебхуків.
Оновлення вебхука
PUT /v1/webhooks/{id}
Змінює URL, події або вмикає й вимикає вебхук. Надішліть secret: "-", щоб згенерувати новий секрет; його буде повернуто у відповіді.
| Назва | Де | Тип | Опис |
|---|---|---|---|
idобов’язковий | шлях | string | Id вебхука. |
url | тіло | string | Ваш ендпоінт. Лише https; локальні й приватні адреси відхиляються. |
events | тіло | string[] | Події, які потрібно отримувати. Наразі ["call.ended"]. |
enabled | тіло | boolean | Вимкнені вебхуки нічого не отримують. |
secret | тіло | 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, вказує на приватну адресу, або подія невідома.404Вебхука з таким id немає.
Видалення вебхука
DELETE /v1/webhooks/{id}
Видаляє вебхук. Доставки, що очікують на надсилання, скасовуються.
| Назва | Де | Тип | Опис |
|---|---|---|---|
idобов’язковий | шлях | string | Id вебхука. |
Запит
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"
} Помилки
404Вебхука з таким id немає.
Тест вебхука
POST /v1/webhooks/{id}/test
Надсилає на URL тестову подію call.ended і повертає статус, час виконання й тіло відповіді.
| Назва | Де | Тип | Опис |
|---|---|---|---|
idобов’язковий | шлях | string | Id вебхука. |
Запит
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}"
} Помилки
404Вебхука з таким id немає.
Список доставок
GET /v1/webhooks/{id}/deliveries
Останні 50 доставок вебхука, від найновіших: статус (pending, ok, failed), кількість спроб, HTTP-статус, помилка й час наступної спроби.
| Назва | Де | Тип | Опис |
|---|---|---|---|
idобов’язковий | шлях | string | Id вебхука. |
Запит
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
}
] Помилки
404Вебхука з таким id немає.
Повторення доставки
POST /v1/webhooks/deliveries/{id}/retry
Знову ставить невдалу доставку в чергу.
| Назва | Де | Тип | Опис |
|---|---|---|---|
idобов’язковий | шлях | 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 | тіло | string | Id збереженого агента. Обов’язковий, якщо не надіслано agent. |
agent | тіло | object | Повний об’єкт агента (ті самі поля, що й у POST /v1/agents) лише для цього дзвінка. |
version | тіло | 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обов’язковий | тіло | string | Один із номерів вашої організації у форматі E.164 (+90…). |
toобов’язковий | тіло | string | Номер, на який треба зателефонувати, у форматі E.164. |
agent_id | тіло | string | Агент для дзвінка. Якщо не вказано, говорить агент, призначений номеру from. |
version | тіло | 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обов’язковий | шлях | 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обов’язковий | шлях | 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обов’язковий | шлях | 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обов’язковий | шлях | string | Номер у форматі E.164, закодований для URL. |
agent_id | тіло | string | null | Агент, який відповідає на вхідні дзвінки. |
label | тіло | 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": {}
}