Appels WebRTC
Voix en temps réel entre le navigateur d'un utilisateur (ou une app mobile) et un agent Aethex. Aucun numéro de téléphone requis ; juste une poignée de main HTTP et un chemin audio direct. La latence reste dans la même plage qu'un appel téléphonique.
Pourquoi WebRTC
Pour toute expérience in-app, dans le navigateur ou mobile, WebRTC est le bon transport pour la voix en direct. Trois raisons :
- API native du navigateur.
RTCPeerConnectiongère déjà la capture du micro, l'encodage, la mise en tampon de gigue, la récupération de perte de paquets et l'annulation d'écho. Vous n'avez pas besoin d'une bibliothèque audio personnalisée. - Traversée NAT. ICE gère les pare-feux d'entreprise, les réseaux d'hôtels et les routeurs domestiques. Les appels se connectent depuis des endroits où un WebSocket brut ne peut pas.
- Débit adaptatif. Opus suit la qualité réseau en temps réel et se dégrade gracieusement. Les utilisateurs sur un mauvais WiFi peuvent encore avoir une conversation ; ils ne coupent pas.
Comment fonctionne un appel
Trois acteurs. La configuration passe par votre serveur pour que votre clé API reste en place ; une fois la poignée de main terminée, l'audio circule directement entre le navigateur et Aethex.
- Le navigateur demande à votre serveur de démarrer un appel.
- Votre serveur appelle POST/conversation/connect avec votre clé API.
- Aethex retourne un
session_idet unice_config. - Votre serveur les transmet au navigateur.
- Le navigateur construit une offre SDP, la poste via votre serveur vers POST/conversation/{session_id}/offer ; votre serveur retourne la réponse SDP. Le navigateur la définit et l'audio commence à circuler.
Après l'étape 5, votre serveur est complètement hors du chemin audio. Il ne rentre dans la boucle que pour retourner un /tool-result quand l'agent invoque un outil, ou pour /end l'appel.
Où ouvrir la session
Par défaut, les sessions sont ouvertes depuis votre serveur, qui gère également la signalisation SDP — le navigateur envoie son offre à votre serveur, qui la transmet à Aethex avec votre clé API et retourne la réponse SDP. Une fois le handshake terminé, l'audio circule directement entre le navigateur et Aethex. La seule exception est le flux de jeton d'appel éphémère (ci-dessous) : le navigateur communique directement avec Aethex à l'aide d'un jeton de courte durée, et votre serveur se contente de générer le jeton.
/conversation/connectAppelez le point d'accès avec votre clé API, puis transmettez le session_id et l'ice_config retournés au navigateur.
/conversation/connect par votre serveur et transmettez les détails de la session au navigateur.Sans proxy : jetons d'appel éphémères
Si vous préférez ne pas relayer chaque requête de signalisation, générez un jeton d'appel de courte durée sur votre serveur et transmettez-le au navigateur. Le navigateur communique alors directement avec Aethex — le jeton remplace votre clé API sur /connect, /offer, /ice, /status et /end. Votre seul code côté serveur est une route de génération d'une ligne, et la clé ae_live_ n'atteint jamais le navigateur.
- Votre serveur appelle
POST /conversation/tokenavec votre clé API et unagent_id, puis renvoie le jeton au navigateur. - Le navigateur appelle lui-même
/connect,/offer,/ice,/statuset/enden envoyant l'en-têteAuthorization: Bearer <token>— sans aller-retour par requête via votre serveur. - Le jeton est limité à ce seul agent et vit juste assez longtemps pour l'appel — connexion, ICE en trickle, interrogation de
/statuset raccrochage avec/end(sa durée de vie correspond par défaut à la durée maximale d'appel de l'agent plus une courte marge). Éphémère et limité à un seul agent, donc une fuite reste contenue ; générez-en un nouveau par appel.
/connect, /offer, /ice, /status, /end) ; rien d'autre dans l'API. /tool-result est la seule exception : il n'est pas authentifié par jeton, donc les agents à outils côté client continuent de le relayer via votre serveur. Il ne porte que les portées calls de la clé qui l'a généré : c'est l'équivalent sûr pour le navigateur de votre clé API côté serveur.Exemple complet
La configuration minimale fonctionnelle : votre serveur génère une session et gère la signalisation SDP, votre navigateur construit l'offre et lit l'audio de réponse.
import os, requests
from flask import Flask, jsonify, request as req
app = Flask(__name__)
@app.post("/api/start-call")
def start_call():
"""Browser POSTs here; we mint a session for an agent."""
resp = requests.post(
"https://api.aethexai.com/api/v1/conversation/connect",
headers={"X-API-Key": os.environ["AETHEX_API_KEY"]},
json={"agent_id": "8a1b2c3d-4e5f-6789-abcd-ef0123456789"},
)
return jsonify(resp.json()), resp.status_code # session_id + ice_config
@app.post("/api/send-offer")
def send_offer():
"""Browser POSTs its SDP offer here; we forward it to Aethex with the API key."""
data = req.json
resp = requests.post(
f"https://api.aethexai.com/api/v1/conversation/{data['session_id']}/offer",
headers={"X-API-Key": os.environ["AETHEX_API_KEY"]},
json={"sdp": data["sdp"], "type": data["type"]},
)
return jsonify(resp.json()), resp.status_code # sdp (answer) + type + pc_idLe côté navigateur se connecte avec l'ID de session et la configuration ICE retournés par le serveur :
async function startCall() {
// 1. Get session details from your server
const session = await fetch("/api/start-call", { method: "POST" }).then(r => r.json())
// 2. Build the peer connection with Aethex's ICE config
const pc = new RTCPeerConnection(session.ice_config)
const appChannel = pc.createDataChannel("aethex-app", { ordered: true })
let pingTimer: ReturnType<typeof setInterval> | null = null
const stopPing = () => {
if (pingTimer) {
clearInterval(pingTimer)
pingTimer = null
}
}
appChannel.onopen = () => {
const sendPing = () => {
if (appChannel.readyState === "open") appChannel.send(`ping:${Date.now()}`)
}
sendPing()
pingTimer = setInterval(sendPing, 1000)
}
appChannel.onclose = stopPing
appChannel.onerror = stopPing
pc.ontrack = (e) => (document.querySelector("audio")!.srcObject = e.streams[0])
// 3. Add the user's mic
const mic = await navigator.mediaDevices.getUserMedia({ audio: true })
mic.getTracks().forEach((t) => pc.addTrack(t, mic))
// 4. Build the offer and wait for ICE gathering to complete before sending.
// All candidates land in pc.localDescription.sdp so Aethex can open the
// media path immediately — no trickle handshake needed for this demo.
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
await new Promise(resolve => {
if (pc.iceGatheringState === "complete") return resolve()
pc.onicegatheringstatechange = () => {
if (pc.iceGatheringState === "complete") resolve()
}
})
// 5. Exchange SDP through your server proxy (it calls Aethex with your API key)
const answer = await fetch(
"/api/send-offer",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: session.session_id, sdp: pc.localDescription.sdp, type: pc.localDescription.type }),
},
).then(r => r.json())
await pc.setRemoteDescription(answer)
// Audio is flowing. The agent speaks its first message; the user can interrupt.
}Piloter l'appel en cours de conversation
Une fois l'audio établi, deux points d'accès permettent à votre serveur de reprendre la boucle :
- POST/conversation/{session_id}/tool-result — retourner un résultat d'outil côté client à l'agent pour qu'il puisse continuer. Consultez Outils pour le contrat requête/réponse.
- POST/conversation/{session_id}/end — raccrocher. Draine l'audio en cours et exécute les hooks post-appel avant de fermer.
Après la fin de l'appel
La transcription et l'enregistrement (quand activés sur l'agent) se stabilisent en quelques secondes. Récupérez-les avec GET/conversations/{id}/transcript et GET/recordings/{id}/audio. Le session_id retourné par POST/conversation/connect fait office d'ID de conversation et d'ID d'appel, donc un seul UUID corrèle tout. Consultez Enregistrement & transcription pour ce qui est capturé.
Capacité et facturation
Chaque session WebRTC compte dans la capacité d'appels actifs de votre compte, au même titre qu'un appel téléphonique. Si vous atteignez la limite, POST/conversation/connect retourne 429 avec un Retry-After en-tête ; respectez-le et faites une pause. Les appels WebRTC sont facturés à la minute de temps actif de l'agent, au même tarif que les appels téléphoniques ; consultez Facturation.
Suivant
Pour la forme complète des requêtes et réponses sur chaque point d'accès, consultez Conversation (WebRTC) dans la référence API.
