L’API travaille sur les mêmes agents, appels et crédits que le tableau de bord web. Tout ce que vous créez dans le tableau de bord peut être piloté depuis vos propres systèmes, et inversement.
| Quoi | Point de terminaison |
|---|---|
| Lister les agents | GET /api/v1/voice/agents |
| Créer un agent | POST /api/v1/voice/agents |
| Lire / modifier un agent | GET · PATCH /api/v1/voice/agents/{id} |
| Passer un appel sortant | POST /api/v1/voice/calls |
| Lister les appels | GET /api/v1/voice/calls |
| Lire un appel (transcription) | GET /api/v1/voice/calls/{id} |
| Supprimer un appel | DELETE /api/v1/voice/calls/{id} |
| Supprimer les appels d’un appelant | POST /api/v1/voice/calls/delete-by-caller |
| Campagnes | GET · POST /api/v1/voice/campaigns |
| Lire une campagne | GET /api/v1/voice/campaigns/{id} |
| Lancer / mettre en pause / annuler | POST /api/v1/voice/campaigns/{id}/{action} |
| Voix clonées | GET · POST /api/v1/voice/voices |
| Supprimer une voix clonée | DELETE /api/v1/voice/voices/{id} |
| Liste d’opposition | GET · POST /api/v1/voice/do-not-call |
| Retirer de la liste | DELETE /api/v1/voice/do-not-call/{phone} |
| Langues, voix, modèles | GET /api/v1/voice/catalog |
| Tests d'un agent | GET · POST /api/v1/voice/agents/{id}/tests |
| Lancer les tests / lire une exécution | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| Attribuer un numéro à un agent | PATCH /api/v1/voice/phone-numbers/{id} |
| Listes de mots | GET · POST /api/v1/voice/vocabularies |
Créez une clé sous Intégrations → Clés API dans le tableau de bord (ou en un clic depuis la vue d’ensemble) et envoyez-la comme jeton Bearer :
Authorization: Bearer sk-your-key
Une clé agit dans l’espace de travail où travaille son propriétaire, avec le rôle de cette personne : la clé d’un membre en lecture seule peut lire mais ne rien modifier.
Créez un agent à partir d’un modèle, en définissant éventuellement ses champs dans la même requête. Sans template, il part de zéro ; GET /api/v1/voice/catalog liste les modèles ainsi que les langues et les voix qu'accepte un agent. Les modèles sont rédigés en anglais et en chinois : pour une autre langue, envoyez system_prompt et first_message dans cette langue. Soit toute la requête est enregistrée, soit aucun agent n'est créé, si bien qu'une requête refusée peut être renvoyée une fois corrigée. Le dashboard_url de la réponse est la page de l'agent dans le tableau de bord, où vous pouvez parler au brouillon dans votre navigateur. Chaque agent fonctionne avec EvaSay ASR, EvaSay LLM et EvaSay TTS, choisis par EvaSay selon la langue et la charge de chaque appel et inclus dans le prix de l’appel ; il n’y a aucun compte d’autre fournisseur à configurer.
curl https://evasay.com/api/v1/voice/agents \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Front desk",
"language": "en",
"first_message": "Hi, thanks for calling Sunrise Salon. How can I help?",
"system_prompt": "You are the front desk of Sunrise Salon..."
}'
Modifiez n’importe lequel de ces champs avec PATCH /api/v1/voice/agents/{id} :
| Champ | Remarques |
|---|---|
name, first_message, system_prompt | Texte |
status | published publie le brouillon comme nouvelle version ; draft arrête de prendre des appels |
language | L’une des 27 langues, par ex. en, zh, es, de, ou multi pour suivre l’appelant |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | Limites d’appel |
interruptible, end_call_enabled | Booléens |
confirm_details | Booléen ; relire les numéros et les noms et les faire confirmer |
ai_disclosure | Booléen, true par défaut ; avant son premier message, l’agent dit dans la langue de l’appel que l’appelant parle à une IA (omis quand le premier message le dit déjà) |
voicemail_detection | Booléen ; les appels sortants écoutent d’abord et s’arrêtent sur une messagerie |
voicemail_message | Laissé sur une messagerie, peut utiliser {{variables}} ; vide pour raccrocher |
transfer_number | Numéro de transfert, format international |
transfer_destinations | Jusqu’à 9 numéros de plus, [{name, number, when}] ; l’agent en choisit un selon when |
transfer_briefing | Booléen ; la personne qui décroche entend d’abord le résumé de l’appel par l’agent |
pronunciations | Jusqu’à 100 [{word, say, case_sensitive}] ; la voix dit say pour word |
idle_messages | Jusqu’à 3 rappels dits tour à tour à un appelant silencieux avant la fin de l’appel |
noise_reduction | Booléen ; le bruit de fond de l’appelant est filtré avant qu’il soit entendu |
recording_enabled | Booléen ; les appels sont enregistrés sur deux canaux (appelant à gauche, agent à droite) |
recording_consent | announce, ask (conservé seulement si l’appelant accepte) ou none |
recording_notice | L’annonce ou la question dite en premier ; vide pour le texte intégré dans la langue de l’appel |
handoffs | Jusqu’à 5 [{agent_id, when}] : d’autres agents à qui celui-ci peut confier un appel |
knowledge_base_id | L’une de vos bases de connaissances |
vocabulary_id | L’un de vos vocabulaires ; les appelants qui en prononcent les termes sont mieux compris |
voice_id | Un identifiant de voix intégrée EvaSay, ou l’une de vos voix clonées (clone:…) |
Les voix intégrées dépendent de la langue : le chinois a ming-designed-female-clear, ming-designed-female-warm (féminines), ming-designed-male-clear et ming-designed-male-friendly (masculines), les autres langues F1–F5 (féminines) et M1–M5 (masculines). Une voix intégrée qui ne parle pas la langue de l’agent devient la voix du même genre qui la parle. L’ancien nom de champ tts_voice_id est toujours accepté. Toute autre valeur de voice_id, ou une voix clonée pas encore prête, est refusée.
analysis_config définit ce qui est tiré de chaque appel une fois terminé : summary (par défaut true), summaryLanguage, successCriteria (ce qu'un appel réussi accomplit), fields (jusqu'à 20 {name, type, description} à extraire, type valant string, number ou boolean) et scorecard (jusqu'à 10 {name, description, weight}, poids de 1 à 5). Les résultats figurent dans l'analysis de l'appel et dans le webhook call.analyzed.
Modifier les champs d’un agent modifie son brouillon. Les appels continuent d’utiliser la version publiée jusqu’à ce que vous publiiez le brouillon avec POST /api/v1/voice/agents/{id}/publish (avec éventuellement une note), ou avec status: "published" dans le même PATCH. Chaque publication conserve une version numérotée ; published_version dans l’agent indique celle qu’utilisent les appels. Un appel reste sur la version en ligne à son début, donc publier pendant un appel ne le modifie pas. Les appels de test depuis le tableau de bord utilisent le brouillon.
GET /agents/{id}/versions liste les versions, et POST /agents/{id}/versions/{version}/restore revient en arrière : les nouveaux appels utilisent cette version et le brouillon devient celle-ci. Le nom de l’agent, son déploiement, l’apparence du widget et les notifications ne sont pas versionnés et changent aussitôt, tout comme les documents de sa base de connaissances.
Un test joue un appel en texte : un appelant simulé joue scenario, l'agent répond à partir de son brouillon (instructions, premier message et connaissances), et un juge évalue la conversation selon success_criteria. Écrivez des tests pour les appels qui comptent, lancez-les après chaque modification et publiez quand ils passent.
curl https://evasay.com/api/v1/voice/agents/AGENT_ID/tests \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Move a booking",
"scenario": "A regular customer wants to move Friday 3 pm to Saturday morning.",
"success_criteria": "Offers Saturday times and confirms the new time and the customer'"'"'s name.",
"max_turns": 8
}'
POST /agents/{id}/test-runs lance tous les tests de l'agent, ou ceux de test_ids, et renvoie une exécution par test (status: "running"). Interrogez GET /test-runs/{id} jusqu'à ce que status soit passed, failed ou error ; l'exécution contient alors le transcript et la reason du juge (en anglais, ou en chinois avec reason_language: "zh"). Chaque exécution coûte des Credits, affichés avec les tests dans le tableau de bord ; les exécutions s'arrêtent à la première que le solde ne peut pas payer, et not_started liste les autres. Une exécution qui échoue à se dérouler est remboursée. Les tests vérifient ce que dit l'agent, pas sa voix ni son rythme, et n'appellent pas ses fonctions : prévisualisez-les avec POST /tools/{id}/test. Un agent a jusqu'à 30 tests ; PATCH et DELETE /agents/{id}/tests/{test_id} en modifient ou en suppriment un.
Outre les appels complets (type: "simulation", par défaut), un test peut vérifier une seule réponse. type: "next_reply" prend chat_history, la conversation qui précède et se termine par l'appelant ([{role, text}], role valant caller ou agent), et success_criteria pour la réponse suivante de l'agent. type: "tool_call" prend chat_history et expected_tool {name, arguments, match} : l'agent doit appeler name (une de ses fonctions, ou end_call, transfer_call, handoff_to_agent) avec arguments, comparés exactement (texte rogné, sans tenir compte de la casse) ou, avec match: "judge", selon le sens ; les autres arguments qu'il passe sont ignorés. Le transcript d'une exécution indique dans tool_calls les fonctions appelées par l'agent.
POST /agents/{id}/tests/generate propose des tests à partir du brouillon de l'agent : ses instructions, ses connaissances et ses fonctions. Il prend count (1 à 10, 5 par défaut), types, focus (ce qu'il faut couvrir) et language (en ou zh, pour les noms et critères), coûte des Credits comme une exécution et renvoie des brouillons à relire puis à envoyer à POST /agents/{id}/tests ; avec save: true, il les enregistre.
hooks fait quelque chose à un moment de l’appel, jusqu’à 10 règles : on: "silence" (l’appelant n’a rien dit depuis seconds, 3–600, depuis que l’agent a fini ; une fois jusqu’à ce qu’il reparle), on: "time_elapsed" (seconds après le début de l’appel, 10–3600) ou on: "call_ending". Chacune fait (do) jusqu’à 3 actions parmi {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?} et {type: "tool", name, args?} ; une règle de fin d’appel n’exécute que des fonctions. Un appel terminé par une règle a pour ended_reason rule_ended.
Au téléphone, les touches pressées par l’appelant arrivent à l’agent comme réponse : une saisie se termine par # ou par une pause de deux secondes. En appel sortant, l’agent peut aussi presser des touches pour franchir le menu téléphonique d’une entreprise.
transfer_number est le numéro principal de l’agent pour passer un appelant à une personne, et le numéro appelé quand l’espace de travail n’a plus de crédits. transfer_destinations ajoute des numéros nommés, chacun avec une note sur quand l’utiliser (« Facturation : questions sur les factures et les remboursements »), et l’agent choisit. Avec transfer_briefing, l’agent rédige un court résumé en transférant ; la personne qui décroche l’entend, avec une voix EvaSay et dans la langue de résumé de l’agent, avant que l’appelant soit mis en relation. Le ended_reason de l’appel est alors transferred.
Avec handoffs, un agent peut passer un appel à un autre agent publié de votre espace de travail, comme un accueil qui passe une réservation à un agent de réservation. L’agent choisit selon when. L’autre agent reprend le même appel avec tout ce qui a été dit, et parle avec ses propres instructions, sa voix, ses connaissances, ses outils et ses passations. L’appel reste listé, analysé et facturé sous le premier agent ; sa transcription note chaque passation.
Les numéros se connectent dans le tableau de bord avec votre propre compte opérateur. GET /api/v1/voice/phone-numbers les liste, et PATCH /api/v1/voice/phone-numbers/{id} avec { "agent_id": "AGENT_ID" } envoie les appels du numéro à un agent (sa version publiée), ou avec "agent_id": null à aucun ; label le renomme.
Une liste de mots contient les noms et termes que disent les appelants et qu'une reconnaissance vocale comprendrait mal, comme des noms de produits, de médicaments ou de rues. POST /api/v1/voice/vocabularies avec { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } (jusqu'à 1 000 termes de 50 caractères, ou "preset": "medical" ou "legal" comme point de départ), puis renseignez le vocabulary_id de l'agent. GET, PATCH (name, terms) et DELETE /vocabularies/{id} ; une liste utilisée par un agent ne peut pas être supprimée.
Chaque numéro a des règles d’appel sur la page Numéros de téléphone, appliquées avant que l’agent réponde : les numéros ou débuts de numéros bloqués (+1555), et les appelants qui masquent leur numéro, sont refusés sans réponse et ne coûtent rien ; certains appelants peuvent être mis en relation directement avec un autre numéro ; et quand l’agent ne peut pas répondre (non publié, pas de worker, plus de crédits sans numéro de transfert), l’appel peut faire sonner un numéro ou entendre votre propre message. Une consultation au démarrage (run_at_start) peut aussi refuser un appelant en répondant {"reject": true, "message": "..."} : l’agent dit le message s’il y en a un et raccroche, avec ended_reason rejected.
Passez un appel depuis l’un des numéros de votre espace de travail. Chaque requête doit avoir un Idempotency-Key unique ; renvoyer la même clé renvoie le même appel au lieu de composer deux fois.
curl https://evasay.com/api/v1/voice/calls \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "AGENT_ID",
"to": "+14155550100",
"variables": { "customer_name": "Alex", "order_id": "1234" }
}'
from est obligatoire quand l’espace de travail a plus d’un numéro de téléphone. variables remplit les variables du type {{customer_name}} dans le premier message et le prompt de l’agent.
GET /api/v1/voice/calls/{id} renvoie le statut, la durée, les crédits débités, le résumé, l’analyse et la transcription de l’appel. Listez les appels avec GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page= (user_id : votre propre identifiant de la personne, issu d’un jeton d’appel web). Ses turns sont le journal de débogage tour par tour de l’appel : où est passé le temps entre la fin de la phrase de l’appelant et le moment où il entend la réponse, en millisecondes — waitMs (fin de parole), sttMs (reconnaissance), llmMs (premier mot du modèle, un de plus par tour de fonctions), ttsMs (premier son de la voix), firstAudioMs (réponse entendue) — avec les fonctions appelées, les interruptions et les erreurs. La page de l’appel l’affiche aussi.
Avec recording_enabled, chaque appel est enregistré en WAV sur deux canaux : l’appelant à gauche, l’agent à droite, à la fréquence d’échantillonnage de l’appel. Avec recording_consent: "announce", l’agent annonce d’abord que l’appel est enregistré ; avec "ask", il demande, et l’enregistrement n’est conservé que si l’appelant accepte (l’absence de réponse vaut refus) ; avec "none", rien n’est dit. Vérifiez ce qu’exige la loi là où vous et vos appelants vous trouvez.
Les enregistrements sont conservés dans le stockage d’EvaSay, ou dans votre propre bucket compatible S3 (AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO) défini sous Intégrations → Webhooks et notifications → Stockage des enregistrements. Le worker qui exécute l’appel envoie le fichier directement dans le bucket. GET /api/v1/voice/calls/{id} renvoie une recordingUrl valable une heure une fois l’enregistrement stocké, et recording.status (recording, stored, declined ou failed).
DELETE /api/v1/voice/calls/{id} supprime un appel terminé avec sa transcription, son enregistrement (dans le stockage d’EvaSay ou votre propre bucket), son analyse et ses envois webhook. Les Credits utilisés restent dans l’historique des crédits. Un appel qui ne peut pas encore être supprimé répond 409 avec le code call_not_deletable_yet : il est en cours ou vient de se terminer, il est en cours de facturation, son enregistrement est peut-être encore en cours d’envoi, ou il doit des Credits que votre solde n’a pas couverts (ajoutez d’abord des Credits).
Quand une personne demande la suppression de ses données, POST /api/v1/voice/calls/delete-by-caller avec { "phone_number": "+14155550123" } supprime tous les appels avec ce numéro (celui de l’appelant pour les appels entrants, le numéro appelé pour les appels sortants), ou avec { "user_id": "..." } tous les appels web passés avec ce user_id dans leur jeton. La réponse est { "deleted": 12, "kept_back": { "settling": 1 }, "more": false } : 200 appels au plus par requête, donc relancez tant que more vaut true, et plus tard pour les appels de kept_back. Si le numéro figure sur votre liste d’opposition, il y reste. Le tableau de bord fait la même chose dans Appels → Supprimer les données d’un appelant.
Envoyez POST /api/v1/voice/calls/{id}/control pendant un appel. Le worker qui exécute l’appel prend la commande en environ 2 secondes ; l’API répond 202. Si le worker qui exécute l’appel est injoignable, l’API répond 503 (code call_unreachable) et la commande n’est pas exécutée.
type | Ce qui se passe |
|---|---|
say | L’agent dit text après sa phrase en cours, ou tout de suite avec "interrupt": true |
note | L’agent reçoit text (sans le dire) et en tient compte dès sa réponse suivante ; la transcription le note |
end | L’agent dit text s’il est fourni, puis raccroche ; un appel téléphonique sans text est raccroché aussitôt |
Le ended_reason de l’appel est alors api_ended. La page de l’appel dans le tableau de bord offre les mêmes commandes.
POST /api/v1/voice/calls/{id}/listen (avec éventuellement { "channels": 2 }) renvoie une listen_url à ouvrir dans les deux minutes. C’est un WebSocket : d’abord un message JSON {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}, puis des trames binaires de PCM 16 bits little-endian avec ce qu’entend l’appelant (les deux voix mélangées ; avec channels: 2, l’appelant à gauche et l’agent à droite). Il se ferme à la fin de l’appel. L’audio vient directement du worker qui exécute l’appel, pas du plan de contrôle d’EvaSay ; le bouton Écouter de la page de l’appel utilise la même adresse.
Envoyez et recevez l’audio d’un appel depuis votre propre programme, par exemple votre propre système téléphonique ou votre app. Lancez l’appel :
curl https://evasay.com/api/v1/voice/calls/websocket \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{ "agent_id": "AGENT_ID", "encoding": "pcm_s16le", "sample_rate": 16000 }'
Le websocket_url de la réponse porte un jeton à usage unique et peut être ouvert dans les 15 minutes. encoding vaut pcm_s16le (PCM 16 bits little-endian à une sample_rate de 8000, 16000 ou 24000) ou mulaw (μ-law 8 kHz). L’audio circule dans les deux sens en trames binaires dans ce format, idéalement de 20 ms chacune. Les trames texte sont en JSON :
| Sens | Messages |
|---|---|
| Vous → EvaSay | {"type":"dtmf","digits":"12#"} touches pressées ; {"type":"end"} raccroche ; {"type":"text","text":"…"} mots tapés par l’appelant ("input":"button" pour une réponse touchée) ; {"type":"context","text":"…","key":"cart"} ce que signale votre app (l’agent n’y répond pas) ; {"type":"tool_result","id":"…","result":{…}} (ou "error") la réponse d’une fonction de page |
| EvaSay → vous | start (avec callId) ; clear (l’appelant a interrompu : abandonner l’audio de l’agent pas encore joué) ; transcript (role, text, final, et input pour les mots tapés) ; user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking ; cards (cards, quick_replies) ; tool (id, name, arguments : exécuter l’une de vos fonctions de page) ; notice (code : too_fast ou limit pour les mots tapés) ; ended (avec reason, puis le socket se ferme) |
L’agent salue dès que vous êtes connecté. user_id et page_tools (les fonctions de page auxquelles répond votre app, voir plus bas) peuvent être ajoutés à la requête. Les appels sont facturés à la minute comme les autres, avec les mêmes historiques, analyses et webhooks.
Lors d’un appel web (widget, page hébergée, votre app), l’appelant voit un chat à côté de la voix, donc l’agent peut faire plus que parler. Les appelants au téléphone n’ont rien de tout cela.
Activez Agent → Connaissances et outils → Envoyer à l’appelant (send_to_caller dans l’API des agents) et l’agent peut afficher jusqu’à cinq cartes à la fois sur l’écran de l’appelant : un titre, une ligne dessous, une description, une image, un lien et jusqu’à deux boutons de réponse chacune ; il peut aussi proposer jusqu’à trois réponses rapides sous une question. Les liens ne sont jamais lus à voix haute.
L’agent ne peut envoyer que des liens et des images qu’on lui a donnés dans cet appel : ses instructions et son accueil, la base de connaissances (un document lu depuis une page web porte son adresse), les réponses de vos fonctions et les cartes prêtes à l’emploi. Un lien doit aussi figurer sur l’un des allowed_hosts (le site d’une carte prête à l’emploi l’est toujours). Les liens que l’agent invente, ou que l’appelant lit à voix haute, ne sont pas envoyés. Les liens vers ces sites dans les paroles de l’agent sont cliquables dans le chat.
"send_to_caller": {
"enabled": true,
"allowed_hosts": ["shop.example.com", "*.cdn.example.com"],
"presets": [
{ "name": "hours", "when": "asked when we open", "title": "Opening hours",
"description": "Mon–Sat 9:00–18:00", "url": "https://shop.example.com/stores", "image_url": "" }
]
}
Chaque envoi vous parvient sous forme de webhook call.message_sent (channel, items avec title et url, quickReplies, blocked). Un appel envoie au plus 10 fois.
Pendant l’appel, l’appelant peut aussi taper (une adresse e-mail, un numéro de commande) ou toucher un bouton de réponse ; l’agent y répond comme à la parole, en s’arrêtant d’abord s’il parlait. Jusqu’à 500 caractères par message, un par seconde, 100 par appel. Dans l’historique de l’appel, ces tours sont marqués comme tapés ou touchés.
Le widget fournit à votre page un objet evasay. Tout est facultatif, et le cadre d’appel n’écoute votre page que si elle fait partie des sites autorisés du widget (Agent → Déployer) :
<script src="https://evasay.com/voice-widget.js" data-agent="AGENT_SLUG" async></script>
<script>
window.evasay = window.evasay || [];
evasay.push(function (agent) {
agent.setVariables({ plan: 'pro', page: location.pathname }); // pour le prochain appel
agent.update('Cart: 2 items, $120', { key: 'cart' }); // pendant l’appel
agent.registerTool('add_to_cart', async function (args) {
// votre code, dans le navigateur du visiteur
return { ok: true, checkout: 'https://shop.example.com/checkout' };
});
agent.on('cards', function (cards) {}); // aussi 'live', 'ended'
});
</script>
setVariables remplissent {{name}} dans les instructions et l’accueil. Elles viennent du navigateur du visiteur, donc l’agent est prévenu qu’elles ne sont pas vérifiées, et elles n’atteignent jamais vos fonctions ni la consultation au démarrage.update indique à l’agent ce qui se passe sur la page sans le faire répondre ; une mise à jour plus récente avec la même key remplace l’ancienne."kind": "page", sans url) et enregistrez une fonction du même nom sur votre page. L’agent ne peut l’appeler que lors d’appels depuis une page qui l’a enregistrée ; il attend la réponse jusqu’à timeout_sec (1–10, 5 par défaut) sauf si wait_for_result vaut false. Elle s’exécute avec la session du visiteur, comme un clic de sa part : demandez une confirmation sur votre propre page avant toute action payante ou irréversible. Les liens de sa réponse peuvent être envoyés sous forme de cartes.Réglez Qui peut lancer un appel sur Uniquement avec un jeton, et faites obtenir par votre serveur un jeton à usage unique pour l’utilisateur connecté :
curl https://evasay.com/api/v1/voice/web-calls \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{ "agent_id": "AGENT_ID", "user_id": "u_42", "variables": { "name": "Ann" }, "expires_in": 600 }'
La réponse contient token (un appel, dans les expires_in secondes, 30–3600, 600 par défaut), agent (le slug) et web_call_url. Donnez le jeton à la page avec data-token-url="/your/endpoint" sur le script du widget (il y fait un POST avec les cookies du visiteur et lit { "token": "…" }) ou avec agent.setToken(async () => token). Les variables du jeton sont vérifiées : les instructions, la consultation au démarrage et les modèles de vos fonctions peuvent les utiliser, de même que user_id, que l’appel conserve (userId dans l’appel, user_id pour filtrer les appels). Gardez votre clé API sur votre serveur.
Dans votre propre app, envoyez en POST { "token": "…", "pageTools": ["add_to_cart"] } à https://evasay.com + web_call_url. La réponse est la connexion de l’appel (une adresse d’offre WebRTC ou une adresse WebSocket, avec son jeton d’appel), pour un client RTVI comme les SDK clients de Pipecat pour le web, iOS, Android et React Native. Les cartes, fonctions de page et avis arrivent comme server-messages RTVI (evasay.cards, evasay.tool, evasay.notice) ; envoyez les mots tapés avec send-text et les signalements et réponses de la page comme client-messages evasay.context et evasay.tool_result. Ou transportez l’audio vous-même avec Audio via un WebSocket, en passant user_id et page_tools.
Sous Numéros de téléphone → SIP, votre propre PBX, vos téléphones SIP ou votre trunk SIP peuvent appeler directement vos agents ; votre fournisseur SIP facture les appels :
sip:ev…@<domaine SIP d’EvaSay>, appels entrants uniquement, pour les PBX (3CX, FreePBX, Asterisk…) et les téléphones SIP.from) ; les transferts vers une personne sont confiés au trunk par REFER. Quand le trunk demande des identifiants, EvaSay répond avec le nom d’utilisateur et le mot de passe du trunk que vous avez saisis.Les appels entrants s’authentifient en digest avec le nom d’utilisateur et le mot de passe fournis par EvaSay (le realm est le domaine SIP) ; vous pouvez aussi limiter les IP sources, ou accepter par IP seule quand votre fournisseur ne peut pas s’authentifier (les IP sont alors obligatoires). Chaque adresse SIP ou trunk a une limite d’appels simultanés ; au-delà, les appels reçoivent 486. Les mots de passe s’affichent une fois, à la création ou à la réinitialisation. UDP et G.711 (PCMU, PCMA) uniquement ; touches par RFC 4733 ou SIP INFO ; désactivez l’enregistrement (REGISTER) sur le trunk de votre PBX.
Une campagne appelle une liste de contacts avec un agent, dans des horaires d’appel et une limite de simultanéité (max_concurrency, jusqu’à 100 appels à la fois, et jamais plus que la limite d’appels simultanés de votre espace de travail), et relance les personnes qu’elle n’a pas pu joindre.
curl https://evasay.com/api/v1/voice/campaigns \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{
"name": "Appointment reminders",
"agent_id": "AGENT_ID",
"phone_number_id": "PHONE_NUMBER_ID",
"timezone": "America/New_York",
"window_start": "09:00",
"window_end": "18:00",
"max_concurrency": 2,
"max_attempts": 2,
"retry_delay_min": 60,
"consent_confirmed": true,
"contacts": [
{ "phone": "+14155550100", "name": "Alex", "appointment": "Tuesday 3 pm" }
]
}'
Les contacts peuvent aussi être du texte CSV avec le numéro de téléphone dans la première colonne. consent_confirmed: true est obligatoire : vous confirmez ainsi que toutes les personnes de la liste ont accepté d’être appelées, y compris par une voix d’IA, et EvaSay conserve cette confirmation avec la campagne (consent_confirmed_at). La campagne est créée en brouillon ; lancez-la avec POST /api/v1/voice/campaigns/{id}/start, et utilisez pause ou cancel de la même façon. L’agent doit être publié avant le lancement d’une campagne.
Avec voicemail_detection activé (par défaut), un appel sortant attend que l’autre partie parle avant que l’agent la salue ; si elle reste silencieuse, l’agent salue après deux ou trois secondes. Quand l’agent entend une messagerie ou un message d’opérateur, il attend la fin de l’annonce et du bip, laisse voicemail_message, ou raccroche s’il n’y en a pas. Ces appels se terminent avec ended_reason voicemail, sont facturés pour leur durée et ne sont ni analysés ni notifiés.
Dans une campagne, un numéro tombé sur une messagerie est rappelé comme un numéro qui n’a pas répondu, et le message n’est laissé qu’à sa dernière tentative, pour que personne ne reçoive deux fois le même message.
Chaque appel sortant, d’une campagne ou de POST /api/v1/voice/calls, vérifie d’abord la liste d’opposition de l’espace de travail. Un numéro listé n’est jamais composé : l’API répond 409 avec code do_not_call, et une campagne marque le contact canceled avec outcome do_not_call et passe au suivant.
Quand la personne d’un appel passé par l’agent demande à ne plus être appelée, l’agent ajoute lui-même son numéro à la liste, le lui dit, et le webhook call.do_not_call est envoyé. Les numéros peuvent aussi être ajoutés et retirés sous Campagnes → Liste d’opposition ou avec l’API :
curl https://evasay.com/api/v1/voice/do-not-call \
-X POST \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-d '{ "phones": ["+14155550100"], "note": "Asked by email" }'
Les numéros sont au format international ; jusqu’à 1 000 à la fois. GET /api/v1/voice/do-not-call les liste, du plus récent au plus ancien (search, limit, page), chacun avec sa source (call, dashboard ou api). DELETE /api/v1/voice/do-not-call/%2B14155550100 en retire un ; ne le faites que si la personne a accepté d’être appelée.
POST /api/v1/voice/voices crée une voix à partir d’un enregistrement WAV d’une seule personne (multipart audio, name, language, consent=true). Il répond 202 avec le statut running ; interrogez GET /api/v1/voice/voices jusqu’à ce qu’elle soit ready, puis réglez le voice_id d’un agent sur son voice_id. language est la langue parlée dans l’enregistrement, l’une des langues d’agent ci-dessus ; la voix les parle toutes sauf le chinois, où une voix intégrée du même genre répond, et ressemble le plus à la personne dans la langue de l’enregistrement. Chaque voix clonée coûte des crédits, affichés dans le tableau de bord avant sa création.
Ajoutez une adresse https sous Intégrations → Webhooks et notifications pour recevoir les événements d’appel :
| Événement | Quand |
|---|---|
call.started | L’agent a décroché |
call.ended | L’appel est terminé, avec sa durée et ses crédits |
call.analyzed | Le résumé, le résultat, les champs extraits et la transcription sont prêts |
call.transcript | Pendant l’appel, les nouvelles lignes de la transcription toutes les quelques secondes |
call.tool_called | L’agent a appelé l’une de vos fonctions : name, arguments, status, durationMs |
call.handed_off | L’appel a été confié à un autre agent : fromAgentId, toAgentId |
call.transferred | L’appel a été transféré à une personne : destination |
call.voicemail | Un appel sortant est tombé sur une messagerie |
call.do_not_call | La personne d’un appel sortant a demandé à ne plus être appelée : phone |
call.recording_ready | L’enregistrement sur deux canaux est stocké : recordingUrl (valable 24 heures), durationMs |
call.message_sent | L’agent a affiché des cartes sur l’écran d’un appelant web ou app : items, quickReplies |
alert.triggered | L’une de vos alertes (Webhooks et notifications → Alertes) a franchi son seuil |
alert.resolved | Cette alerte est revenue à la normale |
Les événements envoyés pendant un appel portent data.call (id, agentId, channel, direction, numéros). Chaque requête est signée :
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Vérifiez la signature avec le secret du webhook affiché à sa création, et répondez avec un statut 2xx. Les livraisons échouées sont réessayées après 1 minute, 5 minutes, 30 minutes, 2 heures et 12 heures avec le même contenu et le même identifiant EvaSay-Delivery, pour que vous puissiez ignorer les doublons. Les dernières livraisons d’un webhook, avec le contenu exact envoyé et le statut répondu par votre point de terminaison, sont listées par GET /webhooks/{id}/deliveries et dans le tableau de bord ; POST /webhooks/{id}/deliveries/{delivery_id} en renvoie une.
En plus de l’e-mail et des webhooks alert.triggered / alert.resolved, les alertes peuvent aller vers un canal Slack (incoming webhook), un robot de groupe WeCom ou Telegram sous Webhooks et notifications → Alertes → Envoyer aussi à.
Les erreurs ont une forme unique :
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| Statut | Signification |
|---|---|
| 400 | Entrée invalide (code et param indiquent laquelle) |
| 401 | Clé API absente ou invalide |
| 402 | Pas assez de crédits pour lancer l’appel |
| 403 | La clé est en lecture seule, ou l’agent est indisponible |
| 404 | Aucun agent, appel ou campagne de ce type dans cet espace de travail |
| 409 | Mauvais état (par ex. lancer une campagne en cours), un compte de fournisseur n’est pas connecté, ou le numéro est sur la liste d’opposition (do_not_call) |
| 429 | Trop de requêtes ; ralentissez et réessayez |
Les requêtes à l’API Voice de l’espace de travail des 30 derniers jours (heure, point de terminaison, statut, durée, clé et code d’erreur ; sans contenu) sont listées sous Clés API → Journal des requêtes API.
Les lectures sont limitées à 120 requêtes par minute et par clé, les écritures et les appels à 30.
Les appels sont facturés à la minute sur les crédits de l’espace de travail, le même solde que celui du tableau de bord. Les tarifs actuels sont sur la page des tarifs.
Utilisez le contrat OpenAPI pour générer un client d’intégration. L’API publique, le tableau de bord et le point de terminaison de gestion MCP agissent sur les mêmes ressources et permissions de l’espace de travail.
| Capacité | Point de terminaison |
|---|---|
| Outils réutilisables | GET / POST /api/v1/voice/tools |
| Lire / modifier / supprimer un outil | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Prévisualiser ou tester un outil | POST /api/v1/voice/tools/{id}/test |
| Lier des outils à un agent | PUT /api/v1/voice/agents/{id}/tools |
| Bases de connaissances | GET / POST /api/v1/voice/knowledge-bases |
| Lire / modifier / supprimer une base de connaissances | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Ajouter / lister des documents | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Lire / supprimer un document | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Abonnements aux événements d’appel | GET / POST /api/v1/voice/webhooks |
| Lire / modifier / supprimer un abonnement | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Envoyer un événement de test signé | POST /api/v1/voice/webhooks/{id}/test |
| Découvrir des outils MCP distants | POST /api/v1/voice/mcp/discover |
| Point de terminaison de gestion MCP | /api/v1/voice/mcp |
Enregistrez un outil HTTP qui interroge votre adaptateur CRM :
{
"name": "lookup_customer",
"description": "Find a customer after confirming their customer ID.",
"kind": "http",
"url": "https://crm.example.com/customers/{{customer_id}}",
"method": "GET",
"read_only": true,
"credential_ref": "crm",
"input_schema": {
"type": "object",
"properties": { "customer_id": { "type": "string" } },
"required": ["customer_id"],
"additionalProperties": false
},
"response_path": "data"
}
POST /tools renvoie { "data": { "id": "TOOL_ID", ... } }. Liez sa définition à un agent avec PUT /agents/AGENT_ID/tools et { "tool_ids": ["TOOL_ID"] }. Cela remplace toute la liste d’outils par des instantanés ; reliez à nouveau quand un outil de la bibliothèque change. Supprimer une définition de la bibliothèque conserve les instantanés existants. Vous pouvez aussi lire et remplacer les définitions en ligne avec le champ tools de l’API des agents. Utilisez analysis_config et widget_config pour configurer l’analyse et le widget via l’API des agents.
Un outil peut utiliser GET, POST, PUT, PATCH ou DELETE. query et request_body acceptent des modèles JSON imbriqués comme { "customer": { "id": "{{customer_id}}" } }. Une variable qui occupe toute la valeur conserve le type JSON d’origine. Les variables d’URL sont encodées et ne peuvent apparaître qu’après un hôte HTTPS public fixe. Sans request_body, les outils non GET reçoivent { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path sélectionne un champ en notation pointée. Les parameters simples sont toujours acceptés ; input_schema prend en charge en plus les objets imbriqués, tableaux, énumérations et bornes numériques. Les références de schéma externes et les expressions régulières ne sont pas acceptées.
static_args : valeurs envoyées à chaque appel quoi que le modèle écrive, comme un identifiant de magasin ; elles remplissent aussi {{name}} dans l’adresse et le corps.extract : [{name, path}] conserve des champs de la réponse d’une fonction (data.id) pour le {{name}} des fonctions suivantes, afin que le customer_id d’une recherche passe dans la commande qui suit sans que le modèle le répète. Les champs d’une consultation au démarrage sont conservés de la même façon.reject_when : jusqu’à 5 règles {on: caller|agent, pattern, negate} ; tant qu’elles sont toutes vraies pour les derniers mots, la fonction n’est pas exécutée et l’agent reçoit reject_message.model_says : à chaque appel, le modèle écrit une phrase dite pendant l’exécution de la fonction, au lieu du speak_before fixe.Connectez une app sous Intégrations → Webhooks et notifications → Applications connectées, puis utilisez « Ajouter une fonction prête à l’emploi » dans l’onglet Connaissances et outils de l’agent ; aucun point de terminaison à vous n’est nécessaire :
check_availability (créneaux libres à une date) et book_appointment (réserve le créneau choisi par l’appelant). Les créneaux Google Agenda suivent le fuseau horaire, les jours d’ouverture, les horaires et la durée de rendez-vous que vous définissez ; Cal.com utilise les disponibilités propres au type d’événement, et un e-mail de secours que vous définissez quand l’appelant n’en donne pas.save_to_sheet, qui ajoute une ligne avec les colonnes que vous listez, plus l’identifiant et l’heure de l’appel.notify_team, qui publie un message rédigé par l’agent dans un canal Slack (incoming webhook).Le plan de contrôle d’EvaSay exécute ces fonctions avec le compte que vous avez connecté ; ni le worker ni la définition de l’agent ne détiennent les identifiants de l’app. Lire un agent via l’API les montre comme kind: "app" (avec app, action, connection_id, app_config) ; renvoyez-les inchangées en remplaçant tools pour les garder. Les apps ne se connectent que dans le tableau de bord.
Réglez run_at_start: true sur l’un des outils d’un agent et il est appelé au début de chaque appel, avant que l’agent salue ou réponde. Les paramètres nommés caller_number, called_number, direction, channel ou call_id sont remplis pour lui. Les champs qu’il renvoie remplissent les {{variables}} dans first_message, system_prompt et idle_messages (les champs d’un niveau plus bas en {{customer.name}}), et toute la réponse est donnée à l’agent comme contexte. Un accueil avec des variables attend la réponse jusqu’à 3 secondes ; ce qui n’est pas trouvé reste vide. Un seul outil par agent peut s’exécuter au début.
Les identifiants sont configurés dans l’environnement serveur pour votre espace de travail et votre destination ; demandez à support@evasay.com de créer une credential_ref. Ne mettez pas de clés API dans les définitions d’agent, les en-têtes, les prompts ou les paramètres d’outil. Votre adaptateur peut garder les identifiants du CRM dans son propre environnement.
Prévisualisez un outil avant d’envoyer des requêtes au système métier :
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
Une prévisualisation valide les paramètres et montre la requête produite ; elle n’exécute pas de conversation et ne contacte pas le système métier. Réglez dry_run=false pour une exécution réelle. Un test d’écriture réel exige aussi confirmed=true et idempotency_key (8–128 lettres, chiffres, points, tirets bas, deux-points ou tirets). Les réponses de test contiennent ok, status, duration_ms et un résultat borné. Elles ne renvoient pas d’identifiants. Les tests de lecture et d’écriture exigent le droit d’écriture sur l’espace de travail.
Pendant un appel, les requêtes métier portent un Idempotency-Key stable. Votre point de terminaison métier doit le respecter. Les écritures identiques dans un appel sont dédupliquées, y compris les opérations au résultat inconnu après un délai dépassé. Une autre opération voulue nécessite un argument métier distinct, comme operation_id. EvaSay ne rejoue pas automatiquement les écritures. Les événements d’exécution d’outil incluent le statut et la durée ; la fonction de tests de conversation reste une simulation textuelle, pas un test d’exécution d’outil.
Configurez une connexion MCP Streamable HTTP avec :
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
Dans Claude Code, par exemple :
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
La connexion indique à l'outil comment construire un agent : lire le catalogue, créer un brouillon, ajouter des connaissances, des fonctions et des listes de mots, écrire des tests et les lancer, corriger le brouillon jusqu'à ce qu'ils passent, vous donner le dashboard_url de l'agent pour l'essayer, puis publier et attribuer un numéro seulement après votre accord. Les outils qui lisent la documentation peuvent partir de https://evasay.com/llms.txt, qui renvoie vers cette page, le contrat OpenAPI et l'adresse MCP.
Utilisez le réglage sécurisé de jeton ou de variable d’environnement du client. Le point de terminaison utilise une authentification par clé API, pas un flux de connexion OAuth. Il expose la création et la configuration d’agents, les outils de la bibliothèque, les connaissances, les webhooks et l’historique des appels. Publier et passer de vrais appels sont des actions d’écriture ; les appels sortants consomment des crédits et des frais d’opérateur. Chaque appel d’outil revérifie les permissions de l’espace de travail. Activez les confirmations de votre client pour les actions d’écriture. Le point de terminaison utilise des réponses JSON sans état ; il ne propose pas de flux de notifications en arrière-plan.
Exemple d’instruction pour l’outil de codage : « Crée un agent EvaSay en brouillon, enregistre mon point de terminaison de recherche CRM, prévisualise-le avec des arguments de test, lie-le à l’agent et configure un webhook call.analyzed. » Vérifiez et autorisez explicitement la publication et les vrais appels.
POST /mcp/discover accepte { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } et renvoie les outils et schémas du serveur. N’enregistrez que l’outil choisi :
{
"name": "lookup_customer",
"description": "Find a CRM customer by confirmed ID.",
"kind": "mcp",
"url": "https://crm.example.com/mcp",
"mcp_tool": "get_customer",
"credential_ref": "crm",
"read_only": true,
"input_schema": {
"type": "object",
"properties": { "customer_id": { "type": "string" } },
"required": ["customer_id"]
}
}
Seuls les outils enregistrés sont disponibles pour l’agent. Les connexions distantes prennent en charge le Streamable HTTP avec réponses JSON ou SSE aux requêtes, des destinations HTTPS publiques et des identifiants préconfigurés. Les anciens points de terminaison SSE, la négociation OAuth, le sampling/elicitation initié par le serveur et les pièces jointes binaires ne sont pas pris en charge. La découverte n’accorde pas automatiquement l’accès à tous les outils du serveur. Le schéma de l’outil distant doit être en ligne et utiliser le sous-ensemble de JSON Schema pris en charge.
Créez un webhook via POST /webhooks avec { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }. La réponse contient l’abonnement et un secret de signature généré, renvoyé uniquement à la création. Vérifiez EvaSay-Signature, dédupliquez EvaSay-Delivery et mettez à jour votre CRM. Les webhooks d’événements sont des notifications asynchrones ; les outils en cours d’appel renvoient leurs résultats directement à l’agent.