Référence API
Tous les endpoints de l'API v1. Exemples en curl, JavaScript (fetch) et Python (requests) ; le langage choisi est conservé sur toute la page.
| URL de base | https://hubvocal.com/v1 |
|---|---|
| Auth | Authorization: Bearer vt_live_… |
| Limite de débit | 120 requêtes/min par clé · 429 + Retry-After |
| Scopes | read · write · callsChaque clé dispose de read, write et/ou calls. read couvre les requêtes GET, write les modifications, calls les appels sortants. Une requête hors des scopes de la clé reçoit 403. |
| Corps d'erreur | {"detail": "..."} |
| OpenAPI | GET /v1/openapi.json |
Pour l'authentification, l'intégration et les codes d'erreur, voir Développeurs.
Agents
Les conversations se déroulent en mode texte : elles testent les instructions et l'utilisation des outils, pas la voix ni la latence. Les simulations se lancent depuis le tableau de bord ; il n'existe pas encore d'endpoint /v1 pour elles.
Lister les agents
GET /v1/agents
Renvoie tous les agents de votre organisation, dans l'ordre du tableau de bord.
Requête
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()) Réponse · 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
}
] Créer un agent
POST /v1/agents
Crée un agent. Chaque champ est validé sur le serveur ; les valeurs qui dépassent les limites sont tronquées. Si vous passez un id qui existe déjà, la requête échoue avec 409. Utilisez PUT pour mettre à jour.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
id | body | string | Facultatif. Lettres minuscules, chiffres et tirets, jusqu'à 48 caractères. Généré à partir du nom s'il est omis. |
nameobligatoire | body | string | Nom affiché, jusqu'à 60 caractères. |
langobligatoire | body | string | Code de la langue de conversation, par ex. tr, en, de. La liste complète est dans GET /v1/voices. |
engineobligatoire | body | "live" | "gemini" | "realtime" | "classic" | live : GPT-Live 1, gemini : Gemini Live, realtime : OpenAI Realtime, classic : STT → LLM → TTS. |
instructions | body | string | Instructions système, jusqu'à 8000 caractères. |
greeting | body | string | Ce que l'agent dit en premier, jusqu'à 400 caractères. |
voice | body | object | voice (un nom issu de /v1/voices), speed (classic et realtime uniquement ; fixé à 1.0 pour live et gemini), tone (style d'élocution en texte libre, jusqu'à 600 caractères). |
tools | body | object | builtins (end_call, take_message) et webhooks (id d'outils) ; 16 outils au maximum au total. Vide ou absent : aucun outil. mcp: [{server_id, tools}] ajoute des outils de vos serveurs MCP ; ils comptent dans la même limite. |
turn | body | object | Réglages de prise de parole : sensitivity (low, balanced, high ; moteur classic uniquement), first_speaker (agent ou caller), silence_wait_s (1–5), idle_check_s (0 ou 10–120), agent_timeout_s (0 ou 10–60). Les champs vides prennent les valeurs par défaut. |
recording | body | object | recording : {enabled (false par défaut), announce (true par défaut), announce_text?}. Lorsqu'il est activé, l'agent annonce l'enregistrement avant le message d'accueil. |
post_call | body | object | post_call.summary : rédiger un résumé à la fin de l'appel (true par défaut). post_call.fields : jusqu'à 24 champs à remplir après l'appel ({name, type, description, enum?, required?}). |
knowledge | body | object | doc_ids (jusqu'à 50 documents de la base de connaissances) et mode ("tool"). L'agent reçoit alors l'outil search_knowledge. |
calendar | body | object | integration_id et calendar_id issus du tableau de bord, duration_min (15–240), buffer_min (0–60), timezone, working_hours, min_notice_h (0–72), max_days_ahead (1–90). Active les outils de rendez-vous. |
Requête
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()) Réponse · 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
} Erreurs
409Un agent avec cet id existe déjà.
Récupérer un agent
GET /v1/agents/{id}
Renvoie un seul agent. published_version est le numéro de la version en production (null s'il n'a jamais été publié) et draft_changed indique si le brouillon en diffère.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
Requête
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()) Réponse · 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
} Erreurs
404Aucun agent avec cet id.
Mettre à jour un agent
PUT /v1/agents/{id}
Remplace l'agent par l'objet envoyé. Envoyez l'objet complet : les champs omis reprennent leur valeur par défaut. L'id du chemin l'emporte sur tout id présent dans le corps.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
nameobligatoire | body | string | Nom affiché, jusqu'à 60 caractères. |
langobligatoire | body | string | Code de la langue de conversation, par ex. tr, en, de. La liste complète est dans GET /v1/voices. |
engineobligatoire | body | "live" | "gemini" | "realtime" | "classic" | live : GPT-Live 1, gemini : Gemini Live, realtime : OpenAI Realtime, classic : STT → LLM → TTS. |
instructions | body | string | Instructions système, jusqu'à 8000 caractères. |
greeting | body | string | Ce que l'agent dit en premier, jusqu'à 400 caractères. |
voice | body | object | voice (un nom issu de /v1/voices), speed (classic et realtime uniquement ; fixé à 1.0 pour live et gemini), tone (style d'élocution en texte libre, jusqu'à 600 caractères). |
tools | body | object | builtins (end_call, take_message) et webhooks (id d'outils) ; 16 outils au maximum au total. Vide ou absent : aucun outil. mcp: [{server_id, tools}] ajoute des outils de vos serveurs MCP ; ils comptent dans la même limite. |
turn | body | object | Réglages de prise de parole : sensitivity (low, balanced, high ; moteur classic uniquement), first_speaker (agent ou caller), silence_wait_s (1–5), idle_check_s (0 ou 10–120), agent_timeout_s (0 ou 10–60). Les champs vides prennent les valeurs par défaut. |
recording | body | object | recording : {enabled (false par défaut), announce (true par défaut), announce_text?}. Lorsqu'il est activé, l'agent annonce l'enregistrement avant le message d'accueil. |
post_call | body | object | post_call.summary : rédiger un résumé à la fin de l'appel (true par défaut). post_call.fields : jusqu'à 24 champs à remplir après l'appel ({name, type, description, enum?, required?}). |
knowledge | body | object | doc_ids (jusqu'à 50 documents de la base de connaissances) et mode ("tool"). L'agent reçoit alors l'outil search_knowledge. |
calendar | body | object | integration_id et calendar_id issus du tableau de bord, duration_min (15–240), buffer_min (0–60), timezone, working_hours, min_notice_h (0–72), max_days_ahead (1–90). Active les outils de rendez-vous. |
Requête
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()) Réponse · 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
} Erreurs
404Aucun agent avec cet id. Créez-le d'abord avec POST.
Supprimer un agent
DELETE /v1/agents/{id}
Supprime l'agent. Une organisation conserve toujours au moins un agent : la suppression du dernier échoue donc.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
Requête
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()) Réponse · 200
{
"deleted": "front-desk"
} Erreurs
400C'est le dernier agent de l'organisation.404Aucun agent avec cet id.
Lister les versions
GET /v1/agents/{id}/versions
Les versions publiées de l'agent, de la plus récente à la plus ancienne. is_live signale celle qu'utilisent actuellement les appels.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
Requête
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()) Réponse · 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
}
] Erreurs
404Aucun agent avec cet id.
Récupérer une version
GET /v1/agents/{id}/versions/{n}
L'agent complet, tel qu'il était dans cette version.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
nobligatoire | path | integer | Numéro de version. |
Requête
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()) Réponse · 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
} Erreurs
404Agent ou version introuvable.
Publier le brouillon
POST /v1/agents/{id}/publish
Enregistre le brouillon actuel comme nouvelle version et la met en production. Les appels déjà en cours conservent leur version.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
note | body | string | Une courte note sur ce qui a changé. |
Requête
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()) Réponse · 200
{
"version": 4
} Erreurs
404Aucun agent avec cet id.
Revenir à une version antérieure
POST /v1/agents/{id}/rollback
Copie une version antérieure dans le brouillon et la publie comme nouvelle version.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'agent, par ex. front-desk. |
versionobligatoire | body | integer | La version à rétablir. |
Requête
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()) Réponse · 200
{
"version": 5
} Erreurs
404Agent ou version introuvable.
Base de connaissances
Lister les documents
GET /v1/knowledge
Les documents de votre organisation, avec leur statut de traitement, leur taille, leur nombre d'extraits et les agents qui les utilisent.
Requête
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()) Réponse · 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"
]
}
] Ajouter un document
POST /v1/knowledge
Importez un fichier en multipart/form-data (champ file), envoyez du texte en JSON {title, text} ou une seule page web en JSON {url}. Renvoie 201 avec le statut processing ; interrogez le document jusqu'à ce qu'il soit ready ou failed.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
file | body | file | multipart/form-data : pdf, docx, txt, md ou html, 10 Mo maximum. |
title | body | string | Titre, avec text. |
text | body | string | Contenu en texte brut. |
url | body | string | Ou une seule page https à récupérer à la place. |
Requête
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()) Réponse · 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": []
} Erreurs
400Type de fichier non pris en charge, fichier de plus de 10 Mo, URL non https ou pointant vers une adresse privée, ou limite de l'organisation atteinte (200 documents, 200 Mo).
Récupérer un document
GET /v1/knowledge/{id}
Le document, avec un aperçu de ses 2000 premiers caractères.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du document. |
Requête
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()) Réponse · 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…"
} Erreurs
404Aucun document avec cet id.
Supprimer un document
DELETE /v1/knowledge/{id}
Supprime le document et le retire de tous les agents qui l'utilisaient.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du document. |
Requête
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()) Réponse · 200
{
"deleted": "kb_7d21e4"
} Erreurs
404Aucun document avec cet id.
Rechercher
POST /v1/knowledge/search
Exécute la même recherche que l'agent et renvoie les extraits les plus pertinents avec un score. Pratique pour vérifier vos documents avant la mise en production.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
queryobligatoire | body | string | Ce que vous recherchez. |
doc_ids | body | string[] | Limiter la recherche à ces documents. |
Requête
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()) Réponse · 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
}
] Outils
Outils intégrés
Ces outils ne nécessitent aucune définition. Activez-les dans tools.builtins de l'agent. search_knowledge s'ajoute lorsque knowledge est défini. Les outils de rendez-vous s'ajoutent lorsque calendar est défini.
| Nom | Rôle |
|---|---|
end_call | L'agent dit au revoir et raccroche 0,75 s après avoir fini de parler. Si l'appelant reprend la parole entre-temps, le raccrochage est annulé. La fiche d'appel indique ended_by: "agent". |
take_message | Recueille name, phone, message et callback_requested et les enregistre dans outcomes[] de la fiche d'appel. Aucune confirmation n'est demandée. |
search_knowledge | Ajouté automatiquement lorsque l'agent dispose de documents dans knowledge. Effectue la recherche et enregistre les sources dans la fiche d'appel. |
check_availability | Lecture. Renvoie les créneaux libres entre date_from et date_to, dans les horaires de travail de l'agent. Ajouté lorsque calendar est défini. |
book_appointment | Écriture, soumise à l'étape de confirmation. Réserve start pour name et phone, avec une note facultative. La réservation est ajoutée à outcomes[]. |
cancel_appointment | Écriture, soumise à l'étape de confirmation. Annule une réservation à partir de sa booking_ref. |
Les intégrations d'agenda (Google Calendar) se connectent uniquement dans le tableau de bord ; il n'existe pas d'endpoint /v1 pour elles. Reprenez les id d'intégration et d'agenda affichés dans le tableau de bord dans le champ calendar de l'agent.
Lister les outils
GET /v1/tools
Les outils webhook de votre organisation. Le secret n'est jamais renvoyé ; has_secret indique s'il est défini.
Requête
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()) Réponse · 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
}
] Récupérer un outil
GET /v1/tools/{id}
Un seul outil webhook. Le secret n'est jamais renvoyé.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'outil. |
Requête
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()) Réponse · 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
} Erreurs
404Aucun outil avec cet id.
Créer un outil
POST /v1/tools
Définit un outil webhook pour l'organisation. Pour l'utiliser, ajoutez son id à tools.webhooks d'un agent. L'URL doit être en https et ne peut pas pointer vers une adresse locale ou privée.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
nameobligatoire | body | string | Lettres minuscules, chiffres et tirets bas, commençant par une lettre (3–41 caractères). Unique dans l'organisation ; end_call et take_message sont réservés. |
descriptionobligatoire | body | string | Indique au modèle quand et comment utiliser l'outil. |
kindobligatoire | body | "read" | "write" | read s'exécute immédiatement ; write demande d'abord confirmation à l'appelant. |
urlobligatoire | body | string | Votre endpoint. https uniquement ; les adresses locales et privées sont refusées. |
parameters | body | object | Objet JSON Schema : jusqu'à 20 champs de type string, number, integer ou boolean (enum autorisé). |
timeout_s | body | integer | De 2 à 15 secondes. |
enabled | body | boolean | Les outils désactivés ne sont pas proposés au modèle. |
secret | body | string | Sert à signer les requêtes. En écriture seule ; lors d'une mise à jour, une valeur vide conserve le secret actuel et "-" le supprime. |
Requête
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()) Réponse · 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
} Erreurs
400Un champ ne respecte pas les règles : format du nom, URL https, adresse privée, plus de 20 paramètres ou délai hors limites.409Un outil portant ce nom existe déjà, ou ce nom est réservé.
Mettre à jour un outil
PUT /v1/tools/{id}
Remplace l'outil par l'objet envoyé. Laissez secret vide ou omettez-le pour conserver le secret actuel ; envoyez "-" pour le supprimer.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'outil. |
nameobligatoire | body | string | Lettres minuscules, chiffres et tirets bas, commençant par une lettre (3–41 caractères). Unique dans l'organisation ; end_call et take_message sont réservés. |
descriptionobligatoire | body | string | Indique au modèle quand et comment utiliser l'outil. |
kindobligatoire | body | "read" | "write" | read s'exécute immédiatement ; write demande d'abord confirmation à l'appelant. |
urlobligatoire | body | string | Votre endpoint. https uniquement ; les adresses locales et privées sont refusées. |
parameters | body | object | Objet JSON Schema : jusqu'à 20 champs de type string, number, integer ou boolean (enum autorisé). |
timeout_s | body | integer | De 2 à 15 secondes. |
enabled | body | boolean | Les outils désactivés ne sont pas proposés au modèle. |
secret | body | string | Sert à signer les requêtes. En écriture seule ; lors d'une mise à jour, une valeur vide conserve le secret actuel et "-" le supprime. |
Requête
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()) Réponse · 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
} Erreurs
400Un champ ne respecte pas les règles : format du nom, URL https, adresse privée, plus de 20 paramètres ou délai hors limites.404Aucun outil avec cet id.
Supprimer un outil
DELETE /v1/tools/{id}
Supprime l'outil et le retire de tous les agents qui l'utilisaient.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'outil. |
Requête
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()) Réponse · 200
{
"deleted": "tl_8f3a2c"
} Erreurs
404Aucun outil avec cet id.
Tester un outil
POST /v1/tools/{id}/test
Envoie une vraie requête à l'URL de l'outil avec les arguments fournis et renvoie le statut, le temps de réponse et jusqu'à 4 Ko du corps de la réponse.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de l'outil. |
arguments | body | object | Arguments d'exemple, conformes à parameters. |
Requête
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()) Réponse · 200
{
"ok": true,
"status": 200,
"ms": 412,
"body": "{\"order_id\":\"A-10234\",\"status\":\"shipped\",\"eta\":\"2026-11-07\"}"
} Erreurs
404Aucun outil avec cet id.
Serveurs MCP
Lister les serveurs MCP
GET /v1/mcp-servers
Les serveurs MCP de votre organisation, avec leur état et les outils synchronisés. auth_set indique si un secret est enregistré ; le secret lui-même n'est jamais renvoyé.
Requête
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()) Réponse · 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"
}
]
}
] Ajouter un serveur MCP
POST /v1/mcp-servers
Enregistre le serveur et récupère sa liste d'outils. Les nouveaux outils sont désactivés. Jusqu'à 10 serveurs par organisation.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
nameobligatoire | body | string | Nom court ; sert de préfixe aux noms d'outils (<name>.<tool>). |
urlobligatoire | body | string | L'endpoint MCP du serveur. https uniquement ; les adresses locales et privées sont refusées. |
transport | body | string | streamable_http (par défaut) ou sse. |
auth | body | string | none, bearer ou header. |
secret | body | string | Jeton ou valeur d'en-tête. Stocké chiffré, jamais renvoyé ; "-" le supprime. |
header_name | body | string | Nom de l'en-tête quand auth vaut header. |
Requête
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()) Réponse · 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"
}
]
} Erreurs
400L'URL n'est pas en https ou pointe vers une adresse privée, ou la liste d'outils du serveur n'a pas pu être récupérée.409L'organisation a déjà 10 serveurs MCP.
Modifier un serveur MCP
PUT /v1/mcp-servers/{id}
Modifie le nom, l'URL, l'authentification ou l'activation du serveur, ainsi que les outils activés et leur accès, read ou write. Envoyez secret: "-" pour supprimer le secret.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du serveur MCP. |
name | body | string | Nom court ; sert de préfixe aux noms d'outils (<name>.<tool>). |
url | body | string | L'endpoint MCP du serveur. https uniquement ; les adresses locales et privées sont refusées. |
enabled | body | boolean | Un serveur désactivé n'est pas proposé aux agents. |
auth | body | string | none, bearer ou header. |
secret | body | string | Jeton ou valeur d'en-tête. Stocké chiffré, jamais renvoyé ; "-" le supprime. |
tools | body | object[] | [{name, enabled, access}] pour chaque outil ; access vaut read ou write. |
Requête
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()) Réponse · 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"
}
]
} Erreurs
400L'URL n'est pas en https ou pointe vers une adresse privée, ou la liste d'outils du serveur n'a pas pu être récupérée.404Aucun serveur MCP avec cet id.
Supprimer un serveur MCP
DELETE /v1/mcp-servers/{id}
Supprime le serveur et retire ses outils de tous les agents.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du serveur MCP. |
Requête
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()) Réponse · 200
{
"deleted": "mcp_2b8e41"
} Erreurs
404Aucun serveur MCP avec cet id.
Synchroniser les outils
POST /v1/mcp-servers/{id}/sync
Récupère à nouveau la liste d'outils. Les nouveaux outils arrivent désactivés, avec access à write.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du serveur MCP. |
Requête
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()) Réponse · 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"
}
]
} Erreurs
400L'URL n'est pas en https ou pointe vers une adresse privée, ou la liste d'outils du serveur n'a pas pu être récupérée.404Aucun serveur MCP avec cet id.
Tester un outil
POST /v1/mcp-servers/{id}/test
Appelle un outil du serveur avec les arguments fournis et renvoie la durée de l'appel, jusqu'à 4 Ko de contenu et l'indication d'une éventuelle erreur signalée par l'outil.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du serveur MCP. |
toolobligatoire | body | string | Nom de l'outil à appeler. |
arguments | body | object | Arguments de l'outil, conformes à son input_schema. |
Requête
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()) Réponse · 200
{
"ok": true,
"ms": 236,
"content": "{\"customer_id\":\"C-1042\",\"name\":\"Ayşe Yılmaz\",\"open_tickets\":0}",
"is_error": false
} Erreurs
404Aucun serveur MCP avec cet id.
Webhooks
Lister les webhooks
GET /v1/webhooks
Les webhooks de votre organisation, avec le résultat de leur dernière livraison. Le secret n'est jamais renvoyé ici ; secret_set indique s'il est défini.
Requête
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()) Réponse · 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
}
}
] Créer un webhook
POST /v1/webhooks
Abonne une URL à des événements. Pour l'instant, le seul événement est call.ended. Jusqu'à 5 webhooks par organisation. Si vous omettez secret, il est généré et renvoyé uniquement dans cette réponse.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
urlobligatoire | body | string | Votre endpoint. https uniquement ; les adresses locales et privées sont refusées. |
eventsobligatoire | body | string[] | Événements à recevoir. Pour l'instant ["call.ended"]. |
enabled | body | boolean | Un webhook désactivé ne reçoit rien. |
secret | body | string | Secret de signature. Généré s'il est omis ; renvoyé une seule fois. |
Requête
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()) Réponse · 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…"
} Erreurs
400L'URL n'est pas en https, pointe vers une adresse privée, ou un événement est inconnu.409L'organisation a déjà 5 webhooks.
Mettre à jour un webhook
PUT /v1/webhooks/{id}
Modifie l'URL, les événements ou l'état d'activation. Envoyez secret: "-" pour générer un nouveau secret ; il est renvoyé dans la réponse.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du webhook. |
url | body | string | Votre endpoint. https uniquement ; les adresses locales et privées sont refusées. |
events | body | string[] | Événements à recevoir. Pour l'instant ["call.ended"]. |
enabled | body | boolean | Un webhook désactivé ne reçoit rien. |
secret | body | string | "-" génère un nouveau secret. Omettez-le pour conserver le secret actuel. |
Requête
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()) Réponse · 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
}
} Erreurs
400L'URL n'est pas en https, pointe vers une adresse privée, ou un événement est inconnu.404Aucun webhook avec cet id.
Supprimer un webhook
DELETE /v1/webhooks/{id}
Supprime le webhook. Les livraisons en attente sont abandonnées.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du webhook. |
Requête
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()) Réponse · 200
{
"deleted": "wh_3c9d1e"
} Erreurs
404Aucun webhook avec cet id.
Tester un webhook
POST /v1/webhooks/{id}/test
Envoie un exemple d'événement call.ended à l'URL et renvoie le statut, le temps de réponse et le corps de la réponse.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du webhook. |
Requête
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()) Réponse · 200
{
"ok": true,
"http_status": 200,
"ms": 184,
"body": "{\"received\":true}"
} Erreurs
404Aucun webhook avec cet id.
Lister les livraisons
GET /v1/webhooks/{id}/deliveries
Les 50 dernières livraisons d'un webhook, de la plus récente à la plus ancienne, avec le statut (pending, ok, failed), le nombre de tentatives, le statut HTTP, l'erreur et l'heure de la prochaine tentative.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id du webhook. |
Requête
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()) Réponse · 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
}
] Erreurs
404Aucun webhook avec cet id.
Relancer une livraison
POST /v1/webhooks/deliveries/{id}/retry
Remet une livraison échouée dans la file d'attente.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
idobligatoire | path | string | Id de la livraison, issu de la liste des livraisons. |
Requête
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()) Réponse · 200
{
"ok": true
} Erreurs
404Aucune livraison avec cet id, ou elle n'a pas échoué.
Voix et moteurs
Lister les moteurs, voix et limites
GET /v1/voices
Tout ce qu'il faut pour construire un formulaire d'agent : noms des moteurs, voix valides par moteur, plages de vitesse, langues, préréglages de ton, modèle derrière chaque moteur et longueurs maximales des champs. L'exemple ci-dessous est abrégé.
Requête
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()) Réponse · 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
}
} Appels
La durée de conservation se définit par organisation dans le tableau de bord : recordings_days (30 par défaut), transcripts_days (365 par défaut) et pii_redaction. Une tâche quotidienne supprime les enregistrements et transcriptions expirés ; la ligne de l'appel et son coût sont conservés.
Widget web : les clés publiques (pk_…) se créent par agent dans le tableau de bord. Le script du widget appelle POST /api/public/web-calls ; voir le guide sur la page Développeurs.
Lancer un appel web
POST /v1/web-calls
Ouvre une salle pour un appel depuis le navigateur et renvoie un jeton d'accès à durée de vie courte. Passez server_url et token à livekit-client dans le navigateur ; l'agent rejoint la salle de lui-même. Appelez cet endpoint depuis votre serveur pour que la clé API n'atteigne jamais le navigateur. À la place d'agent_id, vous pouvez envoyer un objet agent complet pour tester un brouillon sans l'enregistrer.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
agent_id | body | string | Id d'un agent enregistré. Obligatoire sauf si vous envoyez agent. |
agent | body | object | Un objet agent complet (mêmes champs que POST /v1/agents), utilisé pour cet appel uniquement. |
version | body | string | integer | Version à utiliser : "live" (par défaut), "draft" ou un numéro de version. |
Requête
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()) Réponse · 200
{
"server_url": "wss://hubvocal.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"room": "demo-3f9a1c2e",
"max_seconds": 300
} Erreurs
400Ni agent_id ni agent n'a été envoyé.402L'organisation a atteint son plafond de dépenses mensuel.403L'organisation est suspendue.404Aucun agent avec cet id.
Passer un appel sortant
POST /v1/calls
Compose un numéro depuis l'un des numéros de votre organisation et connecte l'agent dès que l'on décroche. Le fournisseur (DIDWW, Twilio ou votre propre trunk SIP) est déterminé par le numéro from.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
fromobligatoire | body | string | L'un des numéros de votre organisation, au format E.164 (+90…). |
toobligatoire | body | string | Le numéro à appeler, au format E.164. |
agent_id | body | string | Agent à utiliser. S'il est omis, c'est l'agent associé au numéro from qui répond. |
version | body | string | integer | Version à utiliser : "live" (par défaut), "draft" ou un numéro de version. |
Requête
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()) Réponse · 200
{
"room": "tel-out-9c41e0b7",
"provider": "didww",
"sip_call_id": "SCL_7mVt2kQb9xRa"
} Erreurs
400Un numéro n'est pas au format international, ou from ne fait pas partie de vos numéros.402L'organisation a atteint son plafond de dépenses mensuel.502Le fournisseur de téléphonie a refusé l'appel.
Lister les appels
GET /v1/calls
Les appels du plus récent au plus ancien, web et téléphone. quality est la synthèse des mesures côté navigateur ; il vaut null pour les appels téléphoniques. ended_by vaut caller, agent, max_call_seconds, caller_unresponsive, agent_unresponsive, no_answer ou unknown. channel vaut web, phone_in ou phone_out. Chaque ligne contient aussi outcome et summary_text (les 160 premiers caractères du résumé). ended_by vaut supervisor lorsqu'un superviseur a mis fin à l'appel depuis la console en direct.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
limit | query | integer | 50 par défaut, 500 au maximum. |
agent | query | string | Uniquement les appels de cet agent (nom de l'agent). |
outcome | query | string | Uniquement les appels ayant cette issue. |
Requête
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()) Réponse · 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
}
}
] Récupérer un appel
GET /v1/calls/{room}
La fiche d'appel complète : détail des coûts par fournisseur, transcription, délai du message d'accueil, coupures à la source côté agent et chronologie de qualité du navigateur par fenêtres de 2 secondes. Les champs de coût dépendent du moteur (live, llm, stt, tts, realtime…) ; chacun contient usd. source_gaps.events liste des paires [second, ms]. summary contient text, outcome (resolved, message_taken, transferred, unresolved, no_conversation, spam), sentiment, topics et action_items. supervisors indique qui a rejoint l'appel depuis la console en direct, et dans quels modes. recording contient available, duration_s et size_bytes. extracted contient les valeurs des post_call.fields de l'agent (null si elles n'ont pas été mentionnées).
| Nom | Emplacement | Type | Description |
|---|---|---|---|
roomobligatoire | path | string | Nom de la salle, issu de la liste des appels ou de POST /v1/web-calls. |
Requête
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()) Réponse · 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"
}
]
}
} Erreurs
404Appel introuvable.
Télécharger l'enregistrement
GET /v1/calls/{room}/recording
L'audio de l'appel au format OGG/Opus. Prend en charge les requêtes Range. 404 si l'appel n'a pas été enregistré ou si l'enregistrement a expiré.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
roomobligatoire | path | string | Nom de la salle, issu de la liste des appels ou de POST /v1/web-calls. |
Requête
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) Réponse · 200
HTTP/1.1 200 OK
Content-Type: audio/ogg
Accept-Ranges: bytes
Content-Length: 201344
<OGG/Opus> Erreurs
404L'appel n'a pas été enregistré ou l'enregistrement a expiré.
Supprimer un appel
DELETE /v1/calls/{room}
Supprime immédiatement l'enregistrement, la transcription et le résumé de l'appel, par exemple pour une demande au titre de la KVKK ou du GDPR. La ligne de l'appel et son coût sont conservés.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
roomobligatoire | path | string | Nom de la salle, issu de la liste des appels ou de POST /v1/web-calls. |
Requête
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()) Réponse · 200
{
"deleted": "demo-8585522d"
} Erreurs
404Appel introuvable.
Exporter les appels (CSV)
GET /v1/calls/export
Les appels d'une période au format CSV, une ligne par appel, avec les champs structurés en colonnes supplémentaires.
| Nom | Emplacement | Type | Description |
|---|---|---|---|
formatobligatoire | query | string | csv uniquement. |
from | query | date | YYYY-MM-DD, inclus. |
to | query | date | YYYY-MM-DD, inclus. |
agent_id | query | string | Uniquement les appels de cet agent. |
Requête
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) Réponse · 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 Numéros de téléphone
Lister les numéros de téléphone
GET /v1/numbers
Les numéros de votre organisation et l'agent qui répond sur chacun (profile_id), ainsi que l'état de préparation de chaque fournisseur de téléphonie. Les numéros sont ajoutés par l'équipe plateforme ; c'est vous qui associez les agents.
Requête
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()) Réponse · 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
}
}
}
} Associer un agent à un numéro
PUT /v1/numbers/{number}
Choisit l'agent qui répond aux appels entrants sur ce numéro. Envoyez agent_id: null pour retirer l'association. Encodez le + du numéro en %2B dans le chemin (le préfixe 00 fonctionne aussi).
| Nom | Emplacement | Type | Description |
|---|---|---|---|
numberobligatoire | path | string | Le numéro au format E.164, encodé pour l'URL. |
agent_id | body | string | null | Agent qui répond aux appels entrants. |
label | body | string | Une note pour votre équipe, jusqu'à 60 caractères. |
Requête
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()) Réponse · 200
{
"ok": true
} Erreurs
400L'agent n'existe pas.404Le numéro n'appartient pas à votre organisation.
Consommation
Consommation
GET /v1/usage
Appels, minutes et coût fournisseur de votre organisation, par fournisseur et par jour (Europe/Istanbul). Par défaut, le mois en cours. kind vaut llm, tts, stt ou voice (moteurs speech-to-speech).
| Nom | Emplacement | Type | Description |
|---|---|---|---|
from | query | date | YYYY-MM-DD, inclus. |
to | query | date | YYYY-MM-DD, inclus. |
Requête
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()) Réponse · 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
}
]
} Spécification
Spécification OpenAPI
GET /v1/openapi.json Aucune clé requise
Le document OpenAPI 3.1 de cette API. Aucune clé requise. Importez-le dans Postman, Insomnia ou un générateur de client.
Requête
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()) Réponse · 200
{
"openapi": "3.1.0",
"info": {
"title": "HubVocal API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://hubvocal.com/v1"
}
],
"paths": {}
}