Aller au contenu
Voxiora

Pour les développeurs

Connectez Voxiora aux outils que vous utilisez déjà

Récupérez appels, transcriptions, rendez-vous et contacts via une API REST, recevez des webhooks signés dès que quelque chose se produit, laissez l’assistant consulter vos données en direct pendant l’appel et ajoutez un assistant vocal à votre site en une ligne de code.

L’API REST et les webhooks sont inclus dans tous les forfaits, dès Essential ; les forfaits supérieurs ont des limites plus élevées. Pendant l’essai gratuit, vous avez un accès de test : 1 clé API, 20 requêtes par minute.

URL de base de l’API
https://voxiora.com/api/v1
curl · Tester la clé
curl https://voxiora.com/api/v1/me \
  -H "Authorization: Bearer tf_live_…"
200 OK
{
  "object": "account",
  "id": "1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a1b",
  "name": "Smile Dental",
  "timezone": "Europe/Warsaw",
  "locale": "en",
  "country": "PL",
  "currency": "PLN",
  "status": "active",
  "plan": {
    "id": "team",
    "billing_cycle": "month",
    "trial_ends_at": null,
    "period_start": "2026-10-01T00:00:00Z",
    "period_end": "2026-11-01T00:00:00Z",
    "recording_history_days": 365
  },
  "minutes": {
    "included": 3000,
    "extra": 100,
    "total": 3100,
    "used": 412.5,
    "remaining": 2687.5
  },
  "api_key_id": "2a3b4c5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
  "scopes": [
    "read:calls",
    "read:appointments",
    "read:contacts",
    "read:tasks"
  ],
  "limits": {
    "plan": "team",
    "requests_per_minute": 180,
    "requests_per_day": 50000,
    "max_api_keys": 10,
    "max_webhook_endpoints": 10
  }
}

Démarrage rapide

Votre première requête en deux minutes

  1. 1

    Créez une clé API

    Dans le tableau de bord, ouvrez Développeurs → Clés API, nommez la clé et choisissez ses autorisations. La clé (tf_live_…) n’est affichée qu’une fois : enregistrez-la tout de suite dans votre gestionnaire de secrets.

  2. 2

    Testez la connexion

    Appelez GET /me. Cela fonctionne avec toute clé valide et renvoie votre compte, les autorisations de la clé et les limites de votre forfait.

  3. 3

    Lisez les données ou abonnez-vous aux événements

    Listez appels, rendez-vous et contacts, ou ajoutez un point de réception dans Développeurs → Webhooks pour recevoir les événements en direct.

curl · Tester la clé
curl https://voxiora.com/api/v1/me \
  -H "Authorization: Bearer tf_live_…"
JavaScript : tous les appels avec réservation, page par page
const API = "https://voxiora.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.VOXIORA_API_KEY}` };

// All calls booked since 1 October, page by page
let cursor = null;
while (true) {
  const url = new URL(`${API}/calls`);
  url.searchParams.set("outcome", "booked");
  url.searchParams.set("since", "2026-10-01T00:00:00Z");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);

  const res = await fetch(url, { headers });
  if (res.status === 429) {
    // rate limited: wait for the number of seconds in Retry-After, then retry the same page
    await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 1) * 1000));
    continue;
  }
  if (!res.ok) throw new Error((await res.json()).error.message);

  const page = await res.json();
  for (const call of page.data) console.log(call.started_at, call.from, call.summary);
  if (!page.has_more) break;
  cursor = page.next_cursor;
}

API REST v1

Appels, rendez-vous et contacts via HTTPS

Du JSON en entrée comme en sortie. Chaque requête est authentifiée par une clé API et limitée selon votre forfait. Les requêtes apparaissent dans le tableau de bord (méthode, chemin, statut et durée — jamais le contenu des requêtes ni des réponses).

Authentification

Créez une clé dans Clés API et envoyez-la comme jeton Bearer. Les clés commencent par tf_live_ et sont liées à un seul compte.

Authorization: Bearer tf_live_…

Pagination

Les endpoints de liste renvoient jusqu’à limit éléments (1–100, 25 par défaut) dans un ordre stable. Si has_more vaut true, passez next_cursor tel quel comme cursor (avec les mêmes filtres) pour obtenir la page suivante. Un curseur invalide renvoie 400 invalid_request.

Erreurs

Les erreurs utilisent les codes de statut HTTP standard et un corps JSON contenant un type et un message lisible.

{ "error": { "type": "…", "message": "…" } }

Points de terminaison

  • GET/meAutorisation: toute clé

    Récupérer le compte actuel

    Renvoie le compte auquel appartient la clé API. Pratique pour « tester la connexion ».

  • GET/assistantsAutorisation: toute clé

    Lister les assistants

    Vos assistants avec leur langue, leur statut et les numéros de téléphone attribués. Utilisez l’id comme assistant_id pour filtrer les appels.

  • GET/assistants/{id}Autorisation: toute clé

    Récupérer un assistant

    Un assistant avec sa langue, son statut et ses numéros. Avec le scope read:assistants (ou write:assistants), renvoie aussi le prompt : instructions, greeting, company_profile et updated_at.

  • PATCH/assistants/{id}Autorisation: write:assistants

    Modifier le prompt de l’assistant

    Modifie les instructions, l’accueil ou le profil de l’entreprise. Les champs omis restent inchangés. Mêmes limites que dans le tableau de bord : instructions jusqu’à 40 000 caractères, profil jusqu’à 10 000, accueil de 5 à 600 caractères qui doit indiquer que l’appelant parle à une IA (sinon 400 avec code ai_disclosure). Chaque modification est enregistrée dans l’historique des versions (source API) et s’applique dès le prochain appel. Les règles d’honnêteté de la plateforme (mention de l’IA et de l’enregistrement) s’appliquent toujours.

  • GET/phone-numbersAutorisation: toute clé

    Lister les numéros de téléphone

    Les numéros de téléphone de votre compte avec pays, type, statut et l’assistant qui y répond.

  • GET/callsAutorisation: read:calls

    Lister les appels

    Les appels, du plus récent au plus ancien, sans transcription. Les appels de démo ne sont jamais inclus.

  • GET/calls/{id}Autorisation: read:calls

    Récupérer un appel

    Un appel avec sa transcription complète et les rendez-vous pris pendant l’appel.

  • GET/calls/{id}/recordingAutorisation: read:calls

    Obtenir l’enregistrement d’un appel

    Liens signés (valables 1 heure) pour écouter et télécharger l’enregistrement MP3 (stéréo : appelant à gauche, assistant à droite). Un enregistrement plus ancien que l’historique de votre forfait renvoie 403 plan_required avec recording_history_days ; un appel sans enregistrement renvoie 404.

  • GET/appointmentsAutorisation: read:appointments

    Lister les rendez-vous

    Les rendez-vous à venir, par ordre chronologique (à partir de maintenant par défaut).

  • GET/appointments/{id}Autorisation: read:appointments

    Récupérer un rendez-vous

    Un rendez-vous de votre compte.

  • POST/appointments/{id}/cancelAutorisation: write:appointments

    Annuler un rendez-vous

    Annule un rendez-vous à venir, le retire du calendrier Google connecté et envoie le webhook appointment.canceled. calendar_sync indique si le calendrier a été mis à jour.

  • POST/appointments/{id}/rescheduleAutorisation: write:appointments

    Déplacer un rendez-vous

    Déplace un rendez-vous à venir vers un nouvel horaire. Répond 409 si le créneau chevauche un autre rendez-vous ou une période occupée du calendrier connecté. Sans ends_at, la durée reste la même. Envoie le webhook appointment.rescheduled.

  • GET/contactsAutorisation: read:contacts

    Lister les contacts

    Vos appelants, du plus récent au plus ancien. L’assistant s’en souvient d’un appel à l’autre.

  • GET/contacts/{id}Autorisation: read:contacts

    Récupérer un contact

    Un contact avec nom, e-mail, tags, notes et l’origine du nom et de l’e-mail (call, system ou manual).

  • POST/contactsAutorisation: write:contacts

    Créer un contact

    Ajoute un contact, par exemple depuis votre CRM, pour que l’assistant connaisse le nom de l’appelant. Le nom et l’e-mail enregistrés via l’API sont marqués manual et l’assistant ne les écrase jamais. Renvoie 409 avec contact_id si le numéro existe déjà. Envoie le webhook contact.created.

  • PATCH/contacts/{id}Autorisation: write:contacts

    Modifier un contact

    Modifie le nom, l’e-mail, les notes ou le blocage. Les champs omis restent inchangés et null vide un champ. Un nom ou un e-mail modifié est marqué manual, l’assistant ne l’écrasera donc pas. Le numéro de téléphone ne peut pas être modifié.

  • GET/tasksAutorisation: read:tasks

    Lister les tâches

    Demandes de rappel, messages et autres tâches issues des appels, des plus récentes aux plus anciennes.

  • GET/tasks/{id}Autorisation: read:tasks

    Récupérer une tâche

    Une tâche de votre compte.

  • PATCH/tasks/{id}Autorisation: write:tasks

    Modifier une tâche

    Passe le statut de la tâche à open, done ou dismissed, par exemple une fois que votre équipe a rappelé le client.

  • POST/calls/outboundAutorisation: write:calls

    Créer un appel sortant

    Votre assistant appelle une personne depuis le numéro de votre entreprise avec l’objectif que vous indiquez, comme une étape d’une séquence d’onboarding. L’appel part maintenant ou à call_at, toujours pendant les horaires d’appel (lun–sam, 9 h–20 h dans le fuseau horaire de votre compte ; en dehors, il est reporté au prochain créneau autorisé et call_at indique quand). Sans réponse, il réessaie jusqu’à 3 fois, à 2 heures d’intervalle. Renvoie 202 avec l’appel au statut scheduled. Erreurs : 400 consent_missing, invalid_phone, number_not_callable, international ou invalid_call_at ; 409 opted_out (la personne ne souhaite pas être appelée), already_scheduled (avec existing_id), assistant_not_live, over_quota ou no_outbound_number ; 429 daily_outbound_limit avec limit (200 appels par 24 heures par défaut). Le résultat arrive dans les webhooks call.completed et outbound_call.finished avec votre external_id.

  • GET/calls/outbound/{id}Autorisation: write:calls

    Récupérer un appel sortant

    Statut d’un appel sortant planifié : scheduled, dialing, done (avec le call_id de la conversation), failed (last_error, p. ex. no_answer après toutes les tentatives) ou canceled. Fonctionne aussi avec le callback_id des webhooks pour les appels d’onboarding et les rappels.

  • DELETE/calls/outbound/{id}Autorisation: write:calls

    Annuler un appel sortant

    Annule un appel sortant créé via l’API qui est encore planifié. Peut être répété sans risque (un appel déjà annulé renvoie 200). Renvoie 409 not_cancelable si l’appel est en cours de numérotation ou terminé, ou pour les appels d’onboarding (retirez plutôt la personne avec POST /api/outreach/unenroll).

  • GET/messagesAutorisation: read:messages

    Lister les SMS

    SMS envoyés par votre compte et réponses des clients, du plus récent au plus ancien. phone est le numéro du client (destinataire ou expéditeur d’une réponse). generated_by : template, ai ou manual ; charged_from : plan ou credits.

  • POST/messagesAutorisation: write:messages

    Envoyer un SMS

    Envoie un SMS à l’un de vos contacts ou appelants : votre propre texte, ou un modèle de votre compte avec vars (date, time, name, service, link, amount). Compte dans le forfait SMS de votre offre et les SMS supplémentaires. Erreurs : 400 invalid_request (consent_missing, invalid_phone, too_long, missing_vars) ; 409 conflict (opted_out — le client a répondu STOP, over_quota, recipient_not_allowed — créez d’abord le contact, blocked_destination) ; 429 rate_limited.

Autorisations

Chaque clé a les portées choisies lors de sa création. Les portées ne peuvent pas être modifiées ensuite : créez une nouvelle clé si vous avez besoin de plus. Si la clé n’a pas la portée requise, les endpoints répondent 403 insufficient_scope. GET /me, /assistants et /phone-numbers fonctionnent avec toute clé valide.

  • read:callsLire les appels, résumés et transcriptions
  • read:appointmentsLire les rendez-vous
  • write:appointmentsAnnuler et déplacer des rendez-vous
  • read:contactsLire les contacts
  • write:contactsCréer et modifier des contacts
  • read:tasksLire les tâches et demandes de rappel
  • write:tasksModifier le statut des tâches
  • write:callsLancer des appels sortants, les consulter et les annuler
  • read:messagesLire les SMS et les réponses des clients
  • write:messagesEnvoyer des SMS à vos contacts
  • read:assistantsLire le prompt de l’assistant (instructions, accueil, profil de l’entreprise)
  • write:assistantsModifier le prompt de l’assistant

Erreurs

  • 400invalid_request — un paramètre est manquant ou invalide
  • 401unauthorized — clé API manquante, invalide ou révoquée
  • 403insufficient_scope — la clé n’a pas la portée requise par l’endpoint ; plan_required — le compte n’a pas de forfait actif ou l’enregistrement est plus ancien que l’historique d’enregistrements du forfait
  • 404not_found — l’objet n’existe pas dans votre compte
  • 409conflict — le créneau est déjà pris, le rendez-vous ne peut pas être modifié, un contact avec ce numéro existe déjà, ou un appel sortant ne peut pas être passé (code : opted_out, already_scheduled, assistant_not_live, over_quota, no_outbound_number, not_cancelable)
  • 429rate_limited — trop de requêtes ; réessayez après le délai indiqué par l’en-tête Retry-After
  • 500server_error — une erreur s’est produite de notre côté
  • 503service_unavailable — problème temporaire ; réessayez après le délai indiqué dans l’en-tête Retry-After

La référence complète de l’API, avec paramètres et exemples de réponses, se trouve dans le tableau de bord : Développeurs → Référence de l’API.

Webhooks

Des événements envoyés à votre serveur en temps réel

Ajoutez une adresse https dans le tableau de bord, choisissez les événements, et nous envoyons une enveloppe JSON signée (POST) dès que quelque chose se produit. « Envoyer un test » livre un exemple de votre premier événement, pratique pour mapper les champs dans Zapier ou Make.

Événements

  • call.completedAppel terminé

    Résumé, résultat, ressenti, transcription, contact et rendez-vous ; pour les appels sortants, également la séquence ou la requête API (callback_id, external_id, outreach).

  • call.startedAppel commencé

    Un appel a été décroché (entrant, sortant ou web).

  • appointment.bookedRendez-vous pris

    L’assistant a pris un rendez-vous.

  • appointment.canceledRendez-vous annulé

    Un rendez-vous a été annulé par téléphone ou via l’API.

  • appointment.rescheduledRendez-vous déplacé

    Un rendez-vous a été déplacé à un nouvel horaire (avec l’horaire précédent).

  • task.createdRappel / tâche créé

    Un appelant a demandé à être rappelé ou a laissé une tâche.

  • contact.createdNouveau contact

    Un nouvel appelant a été enregistré comme contact.

  • outbound_call.finishedAppel sortant terminé

    Un appel sortant planifié (API, séquence d’onboarding ou rappel) est terminé ou a échoué — y compris quand personne n’a répondu après toutes les tentatives.

En-têtes de chaque livraison

  • Voxiora-SignatureSignature : t=<temps unix>,v1=<HMAC-SHA256 hex>
  • Voxiora-EventType d’événement, p. ex. appointment.booked
  • Voxiora-DeliveryIdentifiant unique de la livraison
  • User-AgentVoxiora-Webhooks/1.0 · Identifie notre expéditeur de webhooks

Livraison et nouvelles tentatives

  • Répondez avec n’importe quel statut 2xx en moins de 10 secondes. Les redirections ne sont pas suivies.
  • Les livraisons échouées sont relancées après 1 min, 5 min, 30 min, 2 h, 6 h et 12 h (7 tentatives, environ 21 heures).
  • Un même événement peut arriver plusieurs fois. Utilisez l’id de l’événement pour ignorer les doublons.
  • Seules les adresses https publiques sont acceptées. Après 50 échecs consécutifs, le point de réception est mis en pause et le tableau de bord en indique la raison.
  • Le tableau de bord conserve un journal des livraisons avec codes de statut et réponses, et permet de relancer une livraison à la main.
Exemple de livraison : call.completed
{
  "id": "evt_3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c1e",
  "type": "call.completed",
  "created_at": "2026-10-01T09:17:45Z",
  "account_id": "1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a1b",
  "data": {
    "id": "5b1f0c6e-2a8d-4f3e-9d51-7c0a4e2b9f10",
    "object": "call",
    "assistant_id": "0e6f4c8a-3b2d-4a1e-8f7c-5d9b2a1c3e4f",
    "phone_number_id": "4d3c2b1a-0f9e-4d8c-b7a6-958473625140",
    "contact_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
    "direction": "inbound",
    "status": "completed",
    "from": "+48601234567",
    "to": "+48221234567",
    "started_at": "2026-10-01T09:14:03Z",
    "ended_at": "2026-10-01T09:17:41Z",
    "duration_sec": 218,
    "summary": "Anna Kowalska booked a first consultation for Friday 10:00.",
    "outcome": "booked",
    "sentiment": "positive",
    "tags": [
      "new-customer"
    ],
    "has_recording": true,
    "end_reason": "caller_hangup",
    "page_url": null,
    "page_title": null,
    "continued_from": null,
    "callback_id": null,
    "external_id": null,
    "outreach": null,
    "callback_return": false,
    "caller_spoke": true,
    "caller_words": 6,
    "transcript": [
      {
        "role": "caller",
        "text": "I'd like to book a consultation this week.",
        "at": 4.1
      }
    ],
    "contact": {
      "id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
      "object": "contact",
      "phone": "+48601234567",
      "name": "Anna Kowalska",
      "name_source": "call",
      "email": "anna.kowalska@example.com",
      "email_source": "call",
      "notes": null,
      "tags": [
        "customer"
      ],
      "blocked": false,
      "calls_count": 3,
      "last_call_at": "2026-10-01T09:14:03Z",
      "created_at": "2026-08-12T15:02:11Z"
    },
    "appointments": [
      {
        "id": "c3d2e1f0-a9b8-4c7d-8e6f-5a4b3c2d1e0f",
        "object": "appointment",
        "call_id": "5b1f0c6e-2a8d-4f3e-9d51-7c0a4e2b9f10",
        "contact_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
        "customer_name": "Anna Kowalska",
        "customer_phone": "+48601234567",
        "customer_email": "anna.kowalska@example.com",
        "service": "First consultation",
        "starts_at": "2026-10-03T08:00:00Z",
        "ends_at": "2026-10-03T08:30:00Z",
        "status": "booked",
        "external_id": "google:7h3k9s2l1m0n",
        "created_at": "2026-10-01T09:16:20Z"
      }
    ]
  }
}

Vérifier les signatures. Chaque envoi comporte un en-tête Voxiora-Signature au format t=timestamp,v1=signature. Calculez le HMAC-SHA256 de « timestamp.raw_body » avec votre secret de signature et comparez-le à v1. Rejetez les horodatages de plus de 5 minutes.

Node.js · Vérifier la signature
import crypto from "node:crypto";

// Express: app.post("/webhook", express.raw({ type: "application/json" }), handler)
export function verify(rawBody, signatureHeader, secret) {
  // Voxiora-Signature: t=1727774265,v1=5f2c…
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const given = Buffer.from(parts.v1 ?? "");
  // timingSafeEqual needs buffers of the same length
  return fresh && given.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), given);
}
Python · Vérifier la signature
import hmac, hashlib, time

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(expected, parts.get("v1", ""))
Exemple de récepteur (Express)
import express from "express";
import { verify } from "./verify.js";

const app = express();
const seen = new Set(); // use your database in production

app.post("/voxiora/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verify(raw, req.get("Voxiora-Signature") ?? "", process.env.VOXIORA_WEBHOOK_SECRET)) return res.sendStatus(401);

  const event = JSON.parse(raw);
  if (seen.has(event.id)) return res.sendStatus(200); // retried delivery, already handled
  seen.add(event.id);

  if (event.type === "appointment.booked") {
    const a = event.data;
    console.log("New booking:", a.customer_name, a.service, a.starts_at);
  }
  res.sendStatus(200); // answer with 2xx within 10 seconds
});

app.listen(3000);

Valeurs stables

Ces valeurs dans les appels (API et webhooks call.*) et dans les appels sortants (outbound_call.finished, /calls/outbound) constituent un contrat stable : nous pouvons ajouter de nouvelles valeurs, mais nous ne modifierons ni ne supprimerons celles-ci. Les tentatives sortantes sans réponse, occupées ou rejetées ne créent jamais d’appel, il n’y a donc pas de call.completed pour elles : vous recevez outbound_call.finished avec last_error no_answer après la dernière tentative. Un appel pris par une messagerie vocale compte comme décroché (généralement un appel court avec outcome other).

Statut de l’appel

in_progress
L’appel est en cours.
completed
La personne a parlé avec l’assistant.
missed
Personne n’a parlé, ou l’assistant n’a pas pu prendre l’appel (voir end_reason).
failed
L’appel n’a pas pu être traité à cause d’une erreur.

Résultat de l’appel

booked
Un rendez-vous a été pris.
transferred
L’appel a été transféré à une personne.
message
Un message, une demande de rappel, une commande ou un suivi a été enregistré.
info
La personne a obtenu des informations ; rien d’autre n’était nécessaire.
spam
Spam, démarchage ou numéro bloqué.
other
Tout autre cas, p. ex. un appel très court ou une messagerie vocale.

Motif de fin d’appel (end_reason)

caller_hangup
L’autre personne a raccroché.
caller_hangup_greeting
Appel sortant : la personne a raccroché pendant l’accueil ou juste après, sans dire un mot (appel de 45 s maximum). caller_spoke vaut alors false.
agent_hangup
L’assistant a mis fin à l’appel après avoir pris congé.
transferred
L’appel a été transféré à une personne.
silence
Terminé après un long silence.
max_duration
La durée maximale d’appel a été atteinte.
spam
Terminé comme spam.
blocked
Le numéro est bloqué dans les contacts.
busy
Toutes les lignes du compte étaient occupées.
over_quota
Plus de minutes d’appel.
trial_expired
L’essai gratuit est terminé.
inactive
Aucun forfait actif.
assistant_not_live
L’assistant est en pause.
no_assistant
Aucun assistant ne répond sur ce numéro.
ai_budget
Traité sans IA en raison d’une limite temporaire ; l’équipe a été invitée à rappeler.
error
Une erreur technique a mis fin à l’appel.

Statut de l’appel sortant

scheduled
En attente de son horaire (ou de la prochaine tentative).
dialing
Appel en cours de numérotation.
done
Décroché ; call_id correspond à la conversation.
failed
Sans réponse après toutes les tentatives, ou impossible à passer (voir last_error).
canceled
Annulé (API, personne retirée de la séquence ou séquence désactivée).

last_error de l’appel sortant

no_answer
Personne n’a répondu (y compris ligne occupée ou appel rejeté).
failed
L’assistant n’était pas disponible au moment de l’appel (forfait, minutes ou pause).
no_result
Aucun résultat de l’appel dans les 15 minutes.
expired
L’appel avait plus de 2 heures de retard, il n’a donc pas été passé.
outside_window
Reporté aux prochains horaires d’appel (le statut reste scheduled).
dial_error
Le réseau téléphonique a refusé l’appel (envoyé sous la forme dial_<reason>).
over_quota
Plus de minutes d’appel.
inactive
Aucun forfait actif.
assistant_not_live
L’assistant est en pause.
no_outbound_number
Aucun numéro d’entreprise pour appeler.
canceled_by_api
Annulé avec DELETE /calls/outbound/{id}.
unenrolled
La personne a été retirée de la séquence (POST /api/outreach/unenroll).
opt_out
La personne ne souhaite pas être appelée.
replaced
Remplacé par un rappel plus récent vers le même numéro.

Données en direct via MCP

L’assistant consulte votre système pendant l’appel

Connectez un serveur MCP distant (Model Context Protocol) : catalogue de votre boutique, stock, système de réservation ou de commandes. Vous choisissez précisément les outils et ressources (en lecture seule) que l’assistant peut utiliser.

Apprendre à l’avance

Pour les contenus qui changent peu : tarifs, descriptions de produits, conditions. Les ressources et outils en lecture seule choisis sont importés dans les connaissances de l’assistant toutes les 1 à 168 heures ou à la demande. Aucun délai pendant l’appel.

Vérifier en direct pendant l’appel

Pour ce qui change : stock, disponibilités, statut de commande. L’assistant appelle l’outil pendant la conversation avec un bref « un instant, je vérifie ». Si votre serveur ne répond pas en 2,5 secondes environ, il indique ne pas pouvoir confirmer pour l’instant et propose un rappel de votre équipe.

Ce qu’il vous faut

  • Un serveur MCP accessible en https (Streamable HTTP, p. ex. https://mcp.example.com/mcp ; les anciens serveurs HTTP+SSE sont détectés automatiquement).
  • Authentification : aucune, un jeton Bearer ou un en-tête personnalisé comme X-API-Key. Les secrets sont chiffrés (AES-256-GCM) et jamais envoyés au navigateur.
  • Des outils en lecture seule. Les outils marqués comme destructifs sont bloqués ; pour ceux qui ne sont pas marqués en lecture seule, vous confirmez qu’ils ne font que lire des données.
  • Jusqu’à 5 sources et 50 outils en direct par assistant. Les vérifications en direct doivent répondre en 2 secondes environ.
  • En option pour le widget du site : sur un outil « Au début de l’appel », choisissez l’argument qui reçoit le jeton d’identité du visiteur connecté (voir Widget vocal pour votre site).

Les arguments sont validés selon le schéma de l’outil, les résultats sont raccourcis et traités strictement comme des données (jamais comme des instructions), et chaque vérification est journalisée sans données de l’appelant. Les adresses privées et internes sont refusées.

Serveur MCP pour agents IA

Donnez à votre agent IA l’accès à Voxiora

Voxiora propose un serveur MCP distant (Model Context Protocol). Connectez Claude, ChatGPT, Cursor ou votre propre agent : il lit vos appels, transcriptions, rendez-vous, contacts et tâches et, avec votre accord, ajoute des contacts, lance des appels sortants et envoie des SMS.

  • Point d’accès : https://voxiora.com/api/mcp (Streamable HTTP, JSON-RPC 2.0, sans état).
  • Authentification : la même clé API que pour l’API REST (Authorization: Bearer tf_live_…) ou un jeton d’accès OAuth valable 1 heure obtenu par client credentials. Sans elle, le serveur répond 401 avec un lien vers les métadonnées OAuth.
  • L’agent ne voit que les outils autorisés par les scopes de la clé, et chaque appel passe par les mêmes contrôles, limites et journal des requêtes que l’API REST.
  • Les appels sortants et les SMS exigent consent: true, comme dans l’API. Les résultats des outils sont des données de vos appelants, jamais des instructions.

OAuth pour les agents

Échangez une clé API contre un jeton d’accès de courte durée sur https://voxiora.com/api/oauth/token : client_id est l’identifiant de la clé (api_key_id de GET /me) ou son préfixe, client_secret est la clé. Le jeton reçoit les scopes demandés que la clé possède. Révoquer la clé dans le tableau de bord invalide tous les jetons émis avec elle.

Les agents peuvent aussi demander l’accès eux-mêmes : POST https://voxiora.com/api/agent-auth/register renvoie un code, le propriétaire du compte l’approuve dans le tableau de bord (Approuver un agent IA) et l’agent reçoit une seule fois sa propre clé API. Rien n’est accordé avant l’approbation ; détails dans auth.md.

Outils

  • get_accounttoute clé
  • list_assistantstoute clé
  • list_phone_numberstoute clé
  • list_callsread:calls
  • get_callread:calls
  • list_contactsread:contacts
  • get_contactread:contacts
  • create_contactwrite:contacts
  • list_appointmentsread:appointments
  • get_appointmentread:appointments
  • list_tasksread:tasks
  • list_messagesread:messages
  • start_outbound_callwrite:calls
  • get_outbound_callwrite:calls
  • send_messagewrite:messages

Documents de découverte

curl · Connexion : initialize
curl https://voxiora.com/api/mcp \
  -H "Authorization: Bearer tf_live_…" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}'
curl · Jeton d’accès : client credentials
curl https://voxiora.com/api/oauth/token \
  -u "<api_key_id>:tf_live_…" \
  -d grant_type=client_credentials \
  -d scope="read:calls read:contacts"

Widget vocal pour votre site

Votre assistant sur votre site, en une ligne de code

Les visiteurs cliquent sur un bouton et parlent, dans le navigateur, avec le même assistant qui répond à votre téléphone. Les appels web utilisent les minutes de votre forfait, sans frais de téléphonie.

Dès Business

  • Environ 21 Ko, sans dépendances, isolé dans un Shadow DOM : les styles de votre site ne peuvent pas le casser.
  • Parle la langue de votre page (<html lang>) ou celle définie avec data-lang="de".
  • Sous-titres en direct ; si le micro est bloqué, le visiteur peut écrire et l’assistant répond à voix haute.
  • Limitez le widget à vos domaines : il ne démarrera que sur ces sites.
Code d’intégration (votre clé est dans le tableau de bord)
<script src="https://voxiora.com/widget.js" data-key="tfw_…" async></script>
API JavaScript
// optional: open the widget from your own button
document.querySelector("#talk-to-us").addEventListener("click", () => window.VoxioraWidget?.open());
// open and start right away: { mode: "voice" } or { mode: "chat" } (text only, no microphone)
window.VoxioraWidget?.open({ mode: "chat" });

window.VoxioraWidget?.close();   // close the panel
window.VoxioraWidget?.destroy(); // remove the widget from the page

Content Security Policy stricte ? Autorisez script-src https://voxiora.com https://cdn.jsdelivr.net, connect-src https://voxiora.com wss://*.livekit.cloud https://*.livekit.cloud et media-src blob:.

Utilisateurs connectés (jeton d’identité)

Quand vos visiteurs sont connectés sur votre site, l’assistant peut savoir qui parle sans demander son e-mail.

  • Votre serveur émet un jeton signé et de courte durée pour l’utilisateur connecté (p. ex. HMAC, valable 15 minutes). Ne placez jamais d’adresse e-mail ni d’identifiant utilisateur dans le navigateur : n’importe qui pourrait saisir celui d’un autre dans la console.
  • Appelez VoxioraWidget.identify({ token }) à tout moment ; un appel ultérieur remplace le jeton et identify(null) l’efface. Avant le chargement de widget.js, définissez window.VoxioraIdentity = { token, expires_at } : il est lu au début de la conversation et un jeton expiré est ignoré.
  • Dans le tableau de bord, choisissez l’argument du jeton d’identité de n’importe quel outil MCP en direct : consultation, action ou votre outil « Au début de l’appel ». Voxiora transmet le jeton tel quel uniquement à cet argument, ne le stocke ni ne l’affiche jamais et le tient à l’écart des transcriptions, résumés, webhooks et journaux ; l’assistant ne le voit jamais.
  • De 8 à 400 caractères : lettres, chiffres et . _ ~ + / = - (p. ex. base64url). Renouvelez-le avant son expiration, par exemple toutes les 10 minutes. Votre serveur MCP le vérifie ; un jeton invalide ou expiré doit donner le même résultat qu’aucun jeton. Les appels téléphoniques et les conversations sans jeton fonctionnent comme avant.
Jeton d’identité
// signed-in user: a short-lived, signed token issued by YOUR server (never an e-mail or user ID)
const { token, expires_at } = await fetch("/api/voxiora-identity").then((r) => r.json());

window.VoxioraIdentity = { token, expires_at };       // read when the conversation starts, also before widget.js loads
window.VoxioraWidget?.identify({ token, expires_at }); // replaces the token at any time

// refresh before it expires (e.g. every 10 minutes); on sign-out:
window.VoxioraIdentity = null;
window.VoxioraWidget?.identify(null);

Mode chat et bouton personnalisé

Ouvrez l’assistant depuis votre propre interface — un bouton, un menu ou un accueil dans une scène 3D — à la voix ou en chat texte.

  • data-launcher="none" masque le bouton flottant : seul votre code ouvre le panneau avec VoxioraWidget.open() et le ferme avec close(). Le focus revient là où il était (par exemple le canvas de votre jeu). close() masque seulement le panneau et la conversation continue ; VoxioraWidget.endCall() ou close({ end: true }) y met fin (le widget reste).
  • open({ mode: "voice" }) ou open({ mode: "chat" }) ouvre le panneau et lance aussitôt la conversation. data-mode="chat" fait du chat le mode par défaut du bouton de démarrage.
  • Le mode chat ne demande jamais le micro : le visiteur écrit et l’assistant répond par écrit. C’est le même assistant, avec les mêmes connaissances et les mêmes outils.
  • Toutes les méthodes fonctionnent avant le chargement du widget : les appels sont mis en file d’attente et exécutés dès qu’il est prêt. Avant même le chargement de widget.js, vous pouvez les placer dans window.VoxioraWidget = { q: [["open", { mode: "chat" }]] }.
Bouton personnalisé et mode chat
<!-- no floating button: the panel opens only from your code -->
<script src="https://voxiora.com/widget.js" data-key="tfw_…" data-launcher="none" data-mode="chat" async></script>

<script>
  // calls made before widget.js has loaded are queued and run when it starts
  window.VoxioraWidget = window.VoxioraWidget || { q: [] };
  const voxiora = (method, ...args) =>
    window.VoxioraWidget.q ? window.VoxioraWidget.q.push([method, ...args]) : window.VoxioraWidget[method](...args);

  // e.g. the reception desk in your 3D gallery
  document.querySelector("#talk").addEventListener("click", () => voxiora("open", { mode: "voice" })); // asks for the microphone
  document.querySelector("#chat").addEventListener("click", () => voxiora("open", { mode: "chat" }));  // text only, no microphone

  // later:
  // VoxioraWidget.close();   hide the panel (the conversation continues)
  // VoxioraWidget.isOpen();  true | false
  // VoxioraWidget.state();   { ready, open, mode, call: "idle" | "connecting" | "active", agent_state }
  // VoxioraWidget.init({ launcher: false, mode: "chat", lang: "de" }); // the same options in code
</script>

Événements

Réagissez à la conversation sur votre page : animez un avatar pendant que l’assistant parle, affichez vos propres bulles de chat ou journalisez les appels.

  • Chaque événement est un CustomEvent de window nommé voxiora:<nom>, que vous pouvez aussi écouter avec VoxioraWidget.on(nom, fn) / off(nom, fn).
  • ready { key, assistant, available } · open · close · call_started { mode, roomName } · call_ended { reason } · agent_state { state: listening | thinking | speaking } · error { code }
  • speaking { speaking, level } : niveau de la voix de l’assistant de 0 à 1, jusqu’à 20 fois par seconde pendant qu’il parle et à chaque changement — même si le visiteur préfère réduire les animations.
  • message { role: agent | user, text, final, id } : le texte au fil de l’eau (final: false) puis complet (final: true), pour votre propre interface de chat. tool : voir les outils côté page ci-dessous. Une intervention = un id : plusieurs événements arrivent avec le même id et le texte final a final: true — regroupez par id.
Animer un avatar avec voxiora:speaking
const avatar = document.querySelector("#receptionist");

// { speaking: boolean, level: 0..1 } — up to 20 times per second while the assistant speaks
window.addEventListener("voxiora:speaking", (e) => {
  avatar.classList.toggle("talking", e.detail.speaking);
  avatar.style.setProperty("--mouth", e.detail.level); // e.g. scale the mouth or a glow
  // Unity WebGL: unityInstance.SendMessage("Receptionist", "SetVoiceLevel", e.detail.level);
});

// the same events through the widget API (name with or without the "voxiora:" prefix)
VoxioraWidget.on("call_started", ({ mode, roomName }) => console.log("started", mode, roomName));
VoxioraWidget.on("call_ended", ({ reason }) => console.log("ended", reason)); // user | disconnected | …
VoxioraWidget.on("agent_state", ({ state }) => console.log(state));           // listening | thinking | speaking
VoxioraWidget.on("message", ({ role, text, final }) => final && console.log(role, text)); // your own chat UI
VoxioraWidget.on("error", ({ code }) => console.warn("Voxiora:", code));      // rate_limited, assistant_offline, …

Contexte de la page

Indiquez à l’assistant où se trouve le visiteur — quelle galerie, quelle boutique, quel produit — pour qu’il réponde sans poser la question.

  • VoxioraWidget.setContext({ … }) ou data-context='{"gallery":"POL"}' sur la balise script ; setContext(null) l’efface. Pendant une conversation, le nouveau contexte parvient aussitôt à l’assistant, avec l’adresse et le titre actuels de la page (y compris après une navigation sans rechargement) ; sinon, il s’applique à la conversation suivante.
  • Jusqu’à 20 clés (minuscules, chiffres et _, commençant par une lettre) ; valeurs : texte de 200 caractères maximum, nombres ou true/false, 2000 octets au total au maximum. Un contexte invalide est ignoré avec un avertissement dans la console et le précédent est conservé.
  • Dans les instructions de l’assistant, utilisez {{context.gallery}}, {{context.store}}, etc. ; l’assistant voit aussi le contexte comme une information sur la page.
  • Votre serveur MCP le reçoit dans l’en-tête Voxiora-Context (JSON compact, caractères non ASCII en \uXXXX, jusqu’à 1000 octets ; au-delà, seules les clés qui tiennent restent, les secret_ d’abord) à côté de Voxiora-Call-Id. Traitez-le comme une donnée non fiable : c’est le navigateur du visiteur qui le définit. Les clés commençant par secret_ (par ex. secret_session) ne vont qu’à votre serveur MCP : l’assistant ne les voit jamais, elles ne sont jamais stockées, journalisées ni affichées, et si votre serveur en répète une, elle est remplacée par [secret].
Contexte de la page
// what the visitor is looking at; used by the next conversation
VoxioraWidget.setContext({ gallery: "POL", store: "Lumi Shoes", floor: 2, logged_in: true });
VoxioraWidget.setContext(null); // clear

// or on the script tag: data-context='{"gallery":"POL"}'

// assistant instructions:
//   The visitor is in gallery {{context.gallery}}, at the store {{context.store}}.

// every live lookup on your MCP server during that conversation carries:
//   Voxiora-Call-Id: 5b1f0c6e-…
//   Voxiora-Context: {"gallery":"POL","store":"Lumi Shoes","floor":2,"logged_in":true}
// (non-ASCII as \uXXXX — JSON.parse restores it; set by the visitor's browser, so treat it as untrusted)

Outils côté page

Laissez l’assistant agir dans le navigateur du visiteur — montrer une boutique, ouvrir un produit, lire le panier — grâce à des fonctions que vous enregistrez sur la page.

  • VoxioraWidget.registerTools([...]) : jusqu’à 10 outils avec name (minuscules, chiffres, _), description (ce qu’il fait et quand l’utiliser, 500 caractères maximum) et parameters en JSON Schema de type object (4000 octets maximum). La liste s’applique à la conversation suivante.
  • Quand l’assistant appelle un outil, votre handler(args, { waitForUser }) s’exécute (il peut renvoyer une valeur ou une Promise) et un événement voxiora:tool est émis avec name, args, respond(résultat), reject(erreur) et waitForUser(). La première réponse l’emporte.
  • Répondez en moins de 9 secondes, sinon l’assistant apprend que l’outil n’a pas répondu. Si l’action attend le visiteur (par ex. la confirmation d’un paiement), appelez d’abord waitForUser() : l’assistant indique qu’il attend et vous disposez de 29 secondes au maximum. Les résultats sont limités à 15 000 octets de JSON ; les outils inconnus sont refusés.
  • Les arguments viennent du modèle d’IA : validez-les comme toute saisie utilisateur et n’exécutez jamais sans demander une action qui nécessite la confirmation du visiteur.
Outils côté page
VoxioraWidget.registerTools([
  {
    name: "show_store",
    description: "Moves the visitor's view to a store in the gallery. Use it when they ask where a store is.",
    parameters: { type: "object", properties: { store_id: { type: "string" } }, required: ["store_id"] },
    // runs in the visitor's browser; the returned value (or Promise) goes back to the assistant
    handler: async ({ store_id }) => {
      await gallery.flyTo(String(store_id)); // your code — validate arguments like any user input
      return { shown: true };
    },
  },
  { name: "cart_count", description: "Number of items in the visitor's cart.", parameters: { type: "object", properties: {} } },
]);

// or answer from an event listener — the first answer wins; no answer within 9 s = "timeout"
window.addEventListener("voxiora:tool", (e) => {
  const { name, args, respond, reject } = e.detail;
  if (name === "cart_count") respond({ items: cart.length });
});

Plusieurs assistants sur une page

Chaque assistant a sa propre clé de widget. Passez de l’un à l’autre sans recharger la page — par exemple entre l’accueil et l’assistant d’une boutique.

  • VoxioraWidget.switchTo({ key }) termine la conversation en cours, retire le widget et le relance avec l’autre clé ; un open() appelé juste après attend que le nouvel assistant soit prêt. Le contexte (setContext), les outils (registerTools) et les écouteurs (on) sont conservés : inutile de les réenregistrer ; changez seulement le contexte si l'autre assistant en a besoin d'un autre (p. ex. place).
  • Vos écouteurs d’événements, le contexte de la page et les outils sont conservés ; chaque clé garde son propre état de reprise de conversation. Ajoutez votre domaine aux domaines autorisés de chaque clé.
  • VoxioraWidget.destroy() retire entièrement le widget : interface, écouteurs, conversation et état de reprise enregistré.
Changer d’assistant
const RECEPTION = "tfw_…"; // widget key of the gallery reception
const STORE = "tfw_…";     // widget key of a store's own assistant

document.querySelector("#store-desk").addEventListener("click", () => {
  VoxioraWidget.switchTo({ key: STORE }); // ends the conversation, reloads the widget with the other assistant
  VoxioraWidget.open({ mode: "voice" });  // queued until the new assistant is ready
});

VoxioraWidget.on("ready", ({ key, assistant }) => console.log("now:", assistant, key === RECEPTION ? "reception" : "store"));

VoxioraWidget.destroy(); // remove the widget completely (interface, listeners, conversation, resume state)

Onboarding téléphonique

Vos nouveaux clients reçoivent automatiquement un appel de bienvenue

Quand quelqu’un s’inscrit dans votre système, ajoutez-le à une séquence d’appels. L’assistant l’appelle depuis le numéro de votre entreprise, l’aide à démarrer et reprend contact quelques jours plus tard. Les appels ont lieu pendant les horaires d’appel de la séquence : par défaut du lundi au samedi, de 9 h à 20 h dans votre fuseau horaire, ou aux jours et heures que vous avez définis.

POST/api/outreach/enroll

Authentifiez-vous avec la clé de la séquence (tlo_…) du tableau de bord, en jeton Bearer ou dans l’en-tête X-Voxiora-Key. Chaque séquence a sa propre clé.

Corps de la requête

  • phonestringobligatoire
    Numéro de téléphone, idéalement au format E.164. Les numéros nationaux sont lus avec l’indicatif du pays de votre compte.
  • consenttrueobligatoire
    Doit valoir true : la personne a accepté d’être contactée par téléphone, p. ex. dans votre formulaire d’inscription.
  • namestringfacultatif
    Nom, 120 caractères maximum.
  • emailstringfacultatif
    Adresse e-mail.
  • contextstringfacultatif
    Ce que l’assistant doit savoir sur cette personne, 1 000 caractères maximum (p. ex. le forfait choisi).
  • external_idstring | numberfacultatif
    L’identifiant de la personne dans votre système (texte ou nombre). Un même identifiant n’est inscrit qu’une fois.
curl
curl -X POST "https://voxiora.com/api/outreach/enroll" \
  -H "Authorization: Bearer tlo_…" \
  -H "Content-Type: application/json" \
  -d '{"phone":"+48601234567","name":"Anna Kowalska","email":"anna@example.com","context":"Signed up today, Start plan","external_id":"user_123","consent":true}'
201 Created
{
  "ok": true,
  "enrollment_id": "8f7e6d5c-4b3a-4c2d-9e1f-0a9b8c7d6e5f",
  "external_id": "user_123",
  "status": "active",
  "next_call_at": "2026-10-09T10:20:00Z",
  "limits": {
    "per_day": 500,
    "remaining_today": 499
  }
}

Réponses

  • 201 : inscrite. enrollment_id, external_id, status, next_call_at (heure du premier appel), intro_sms_at (heure d’envoi du SMS avant le premier appel, ou null) et limits { per_day, remaining_today }
  • 200 : déjà inscrite (même external_id, ou ce numéro est déjà en cours dans la séquence), avec duplicate: true
  • 400 : invalid_request, invalid_phone ou consent_required
  • 401 : invalid_key
  • 409 : sequence_disabled, consent_missing ou opted_out (la personne ne souhaite pas être appelée)
  • 429 : limit, la limite quotidienne d’inscriptions de la séquence est atteinte (500 nouvelles personnes par 24 heures ; le corps contient limit). rate_limited : plus de 120 requêtes par minute avec cette clé (en-tête Retry-After)

Horaires d’appel : dans le tableau de bord, définissez pour chaque séquence les jours et une plage horaire entre 7 h et 21 h. Sans réponse, l’assistant réessaie jusqu’à 3 fois, à 2 heures d’intervalle, pendant ces horaires.

SMS avant l’appel : s’il est activé pour la séquence (tableau de bord), la personne reçoit un court message depuis le numéro qui servira à l’appeler, par défaut 20 minutes avant le premier appel (5–120 min). Si le premier appel devait avoir lieu plus tôt, il est décalé pour que le SMS parte toujours en premier, pendant les horaires d’appel. Un SMS en échec (la personne a répondu STOP, le quota SMS est épuisé) n’empêche jamais l’appel. Chaque SMS est décompté de votre quota SMS et apparaît dans le tableau de bord, rubrique SMS.

Accueil : dans le tableau de bord, vous définissez la première phrase de l’appel pour chaque étape (ou par défaut pour toute la séquence) — un texte fixe avec les variables {first_name}, {first_name_vocative} (vocatif polonais, p. ex. Krystianie), {assistant_name}, {brand} (nom de l’entreprise dans les appels, réglages de l’assistant) et {name}, avec une variante pour les inscriptions sans nom — ou un mode où l’assistant rédige la première phrase à partir de name, context, de l’objectif de l’étape et du résultat des outils « Au début de l’appel ». Le fait qu’un assistant IA appelle et que l’appel est enregistré est toujours ajouté à la première phrase si le texte ne le dit pas.

Retirer une personne de la séquence

POST/api/outreach/unenroll

Quand quelqu’un n’a plus besoin des appels (p. ex. après sa première vente, ou s’il a résilié), arrêtez sa séquence avec la même clé. Les appels planifiés sont annulés immédiatement. Un appel déjà en cours n’est pas interrompu, mais aucun autre appel ne suit.

  • external_id | enrollment_id | phoneobligatoire
    Exactement un parmi : external_id (tel qu’envoyé lors de l’inscription), enrollment_id (issu de la réponse d’inscription) ou phone.
  • reasonfacultatif
    Motif facultatif, 200 caractères maximum, conservé dans le journal d’audit de votre compte.
  • 200 : arrêtée (changed: true) ou déjà terminée : status stopped, completed ou failed (changed: false). Peut être répété sans risque.
  • 400 : invalid_request (aucun identifiant ou plusieurs) ou invalid_phone
  • 401 : invalid_key
  • 404 : not_found, aucune inscription correspondante dans cette séquence
  • 429 : rate_limited
curl
curl -X POST "https://voxiora.com/api/outreach/unenroll" \
  -H "Authorization: Bearer tlo_…" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"user_123","reason":"first_sale"}'
200 OK
{
  "ok": true,
  "enrollment_id": "8f7e6d5c-4b3a-4c2d-9e1f-0a9b8c7d6e5f",
  "external_id": "user_123",
  "status": "stopped",
  "changed": true
}

Résultats des appels dans les webhooks

call.started et call.completed pour les appels d’une séquence contiennent callback_id, external_id et outreach { sequence_id, enrollment_id, external_id, step } (tous null pour les appels entrants). Les tentatives sans réponse ne créent pas d’appel, il n’y a donc pas de call.completed pour elles : après la dernière tentative, vous recevez outbound_call.finished avec status failed et last_error no_answer. Les nouvelles tentatives d’une même étape comptent pour un seul résultat. call.completed contient aussi caller_spoke et caller_words (nombre de mots prononcés par la personne). Si quelqu’un a décroché puis raccroché pendant l’accueil ou juste après sans un mot, end_reason vaut caller_hangup_greeting. Si la personne rappelle le numéro de l’entreprise dans les 3 jours suivant notre tentative, l’appel entrant a callback_return: true et les mêmes callback_id, external_id et outreach que l’appel auquel elle répond. Les appels depuis le widget du site ont direction widget, page_url et page_title (page de l’appel) et continued_from (partie précédente après rechargement de la page) ; un test navigateur depuis le panneau est direction web. Chaque requête vers votre serveur MCP pendant un appel porte l’en-tête Voxiora-Call-Id (identique à l’id de l’appel), Voxiora-Caller pour les appels téléphoniques et Voxiora-Outreach-External-Id pour les appels de séquence ou d’API.

call.completed pour un appel de séquence
{
  "id": "5b1f0c6e-2a8d-4f3e-9d51-7c0a4e2b9f10",
  "object": "call",
  "assistant_id": "0e6f4c8a-3b2d-4a1e-8f7c-5d9b2a1c3e4f",
  "phone_number_id": "4d3c2b1a-0f9e-4d8c-b7a6-958473625140",
  "contact_id": "9a8b7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d",
  "direction": "outbound",
  "status": "completed",
  "from": "+48732127980",
  "to": "+48601234567",
  "started_at": "2026-10-01T09:14:03Z",
  "ended_at": "2026-10-01T09:17:41Z",
  "duration_sec": 218,
  "summary": "Jan added the company name and tax ID; asked for a call tomorrow at 10:00 about payments.",
  "outcome": "message",
  "sentiment": "positive",
  "tags": [],
  "has_recording": true,
  "end_reason": "agent_hangup",
  "page_url": null,
  "page_title": null,
  "continued_from": null,
  "callback_id": "0f1e2d3c-4b5a-4968-8776-655443322110",
  "external_id": "gapli-evt-1842",
  "outreach": {
    "sequence_id": "11111111-1111-4111-8111-111111111111",
    "enrollment_id": "8f7e6d5c-4b3a-4c2d-9e1f-0a9b8c7d6e5f",
    "external_id": "gapli-evt-1842",
    "step": 1
  },
  "callback_return": false,
  "caller_spoke": true,
  "caller_words": 112
}

Agenda, CRM et automatisation

Des intégrations prêtes à l’emploi, sans code

  • Google Agenda

    L’assistant vérifie les disponibilités réelles et prend, déplace et annule les rendez-vous dans votre agenda. Il ne lit que les créneaux occupés, jamais les titres des événements.

  • HubSpot

    Chaque appel est consigné sur le contact, les réservations deviennent des réunions et les rappels des tâches. Création de contacts et de transactions en option.

  • Pipedrive

    Appels en activités ou en notes, réservations en réunions, rappels en activités. Création de personnes, d’affaires ou de prospects en option.

  • Zapier, Make et n8n

    Guides pas à pas dans le tableau de bord. Ils reposent sur les webhooks et l’API, inclus dans tous les forfaits.

Forfaits et limites

Quel forfait choisir

Limites par compte : requêtes par minute (aussi par clé, fenêtre glissante) et par jour (UTC). Au-delà d’une limite, vous recevez 429 rate_limited avec un en-tête Retry-After.

OffreRequêtes / minRequêtes / jourClés APIWebhooks
Essai gratuit (accès de test)201 00011
Essential302 00022
Pro6010 00055
Business18050 0001010
Enterprise600200 0002520

Widget vocal pour votre site : à partir du forfait Business.

Besoin de limites plus élevées ? Contactez-nous, nous pouvons les augmenter pour votre compte.

Commencez aujourd’hui

Prêt à connecter ?

Lancez l’essai gratuit, créez une clé API de test et envoyez votre première requête en quelques minutes. L’API est incluse dans tous les forfaits, dès Essential.