De API werkt met dezelfde agents, gesprekken en credits als het webdashboard. Alles wat je in het dashboard opbouwt, kun je vanuit je eigen systemen aansturen, en andersom.
| Wat | Endpoint |
|---|---|
| Agents weergeven | GET /api/v1/voice/agents |
| Een agent maken | POST /api/v1/voice/agents |
| Een agent ophalen / bijwerken | GET · PATCH /api/v1/voice/agents/{id} |
| Een uitgaand gesprek plaatsen | POST /api/v1/voice/calls |
| Gesprekken weergeven | GET /api/v1/voice/calls |
| Een gesprek ophalen (transcript) | GET /api/v1/voice/calls/{id} |
| Een gesprek verwijderen | DELETE /api/v1/voice/calls/{id} |
| Gesprekken van een beller verwijderen | POST /api/v1/voice/calls/delete-by-caller |
| Campagnes | GET · POST /api/v1/voice/campaigns |
| Een campagne ophalen | GET /api/v1/voice/campaigns/{id} |
| Starten / pauzeren / annuleren | POST /api/v1/voice/campaigns/{id}/{action} |
| Gekloonde stemmen | GET · POST /api/v1/voice/voices |
| Een gekloonde stem verwijderen | DELETE /api/v1/voice/voices/{id} |
| Niet-bellen-lijst | GET · POST /api/v1/voice/do-not-call |
| Van de lijst verwijderen | DELETE /api/v1/voice/do-not-call/{phone} |
| Talen, stemmen, sjablonen | GET /api/v1/voice/catalog |
| Tests van een agent | GET · POST /api/v1/voice/agents/{id}/tests |
| Tests uitvoeren / run lezen | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| Nummer aan een agent koppelen | PATCH /api/v1/voice/phone-numbers/{id} |
| Woordenlijsten | GET · POST /api/v1/voice/vocabularies |
Maak een sleutel onder Integraties → API-sleutels in het dashboard (of met één klik op het overzicht) en stuur die mee als Bearer-token:
Authorization: Bearer sk-your-key
Een sleutel werkt in de werkruimte waarin de eigenaar werkt, met de rol van die persoon: de sleutel van een alleen-lezenlid kan lezen maar niets wijzigen.
Maak een agent van een sjabloon en stel desgewenst in hetzelfde verzoek zijn velden in. Zonder template begint hij leeg; GET /api/v1/voice/catalog toont de sjablonen en de talen en stemmen die een agent aanneemt. Sjablonen zijn in het Engels en Chinees geschreven, dus stuur voor een andere taal system_prompt en first_message in die taal mee. Het hele verzoek wordt opgeslagen of er wordt geen agent gemaakt, dus een geweigerd verzoek kan na correctie opnieuw worden verstuurd. De dashboard_url in het antwoord is de pagina van de agent in het dashboard, waar u in de browser met het concept praat. Elke agent draait op EvaSay ASR, EvaSay LLM en EvaSay TTS, door EvaSay per gesprek gekozen op basis van taal en belasting en inbegrepen in de gespreksprijs; er zijn geen accounts van andere aanbieders om in te stellen.
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..."
}'
Werk elk van deze velden bij met PATCH /api/v1/voice/agents/{id}:
| Veld | Opmerkingen |
|---|---|
name, first_message, system_prompt | Tekst |
status | published publiceert het concept als nieuwe versie; draft neemt geen gesprekken meer aan |
language | Een van 27 talen, bijv. en, zh, es, de, of multi om de beller te volgen |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | Gesprekslimieten |
interruptible, end_call_enabled | Booleans |
confirm_details | Boolean; noemt nummers en namen terug en laat ze bevestigen |
ai_disclosure | Boolean, standaard true; vóór het eerste bericht zegt de agent in de taal van het gesprek dat de beller met een AI spreekt (weggelaten als het eerste bericht dat al zegt) |
voicemail_detection | Boolean; uitgaande gesprekken luisteren eerst en eindigen bij een voicemail |
voicemail_message | Achtergelaten op een voicemail, mag {{variables}} gebruiken; geen bericht om op te hangen |
transfer_number | Nummer om naar door te verbinden, in internationaal formaat |
transfer_destinations | Tot 9 extra nummers, [{name, number, when}]; de agent kiest er een op basis van when |
transfer_briefing | Boolean; wie het eerst opneemt hoort de samenvatting van het gesprek door de agent |
pronunciations | Tot 100 [{word, say, case_sensitive}]; de stem zegt say voor word |
idle_messages | Tot 3 herinneringen die om beurten tegen een zwijgende beller worden gezegd voordat het gesprek eindigt |
noise_reduction | Boolean; achtergrondgeluid van de beller wordt gefilterd voordat het gehoord wordt |
recording_enabled | Boolean; gesprekken worden in twee kanalen opgenomen (beller links, agent rechts) |
recording_consent | announce, ask (alleen bewaard als de beller akkoord gaat) of none |
recording_notice | De mededeling of vraag die als eerste wordt gezegd; leeg voor de ingebouwde tekst in de taal van het gesprek |
handoffs | Tot 5 [{agent_id, when}]: andere agents aan wie deze een gesprek mag overdragen |
knowledge_base_id | Een van je kennisbanken |
vocabulary_id | Een van je woordenlijsten; termen daaruit worden betrouwbaarder herkend als de beller ze zegt |
voice_id | Een id van een ingebouwde EvaSay-stem, of een van je gekloonde stemmen (clone:…) |
Ingebouwde stemmen hangen van de taal af: Chinees heeft ming-designed-female-clear en ming-designed-female-warm (vrouwelijk), ming-designed-male-clear en ming-designed-male-friendly (mannelijk), de andere talen hebben F1–F5 (vrouwelijk) en M1–M5 (mannelijk). Een ingebouwde stem die de taal van de agent niet spreekt, wordt vervangen door de stem van hetzelfde geslacht die dat wel doet. De oudere veldnaam tts_voice_id wordt nog steeds geaccepteerd. Elke andere waarde van voice_id, of een gekloonde stem die nog niet klaar is, wordt geweigerd.
analysis_config bepaalt wat er na elk gesprek wordt uitgewerkt: summary (standaard true), summaryLanguage, successCriteria (wat een geslaagd gesprek bereikt), fields (tot 20 {name, type, description} om te extraheren, met type als string, number of boolean) en scorecard (tot 10 {name, description, weight}, gewicht 1–5). De resultaten staan in de analysis van het gesprek en in de webhook call.analyzed.
Wijzigingen in de velden van een agent wijzigen zijn concept. Gesprekken blijven de gepubliceerde versie gebruiken totdat je het concept publiceert met POST /api/v1/voice/agents/{id}/publish (eventueel met een note), of met status: "published" in dezelfde PATCH. Elke publicatie bewaart een genummerde versie; published_version in de agent zegt welke gesprekken gebruiken. Een gesprek blijft op de versie die live was toen het begon, dus publiceren tijdens een gesprek verandert er niets aan. Testgesprekken vanuit het dashboard gebruiken het concept.
GET /agents/{id}/versions toont de versies, en POST /agents/{id}/versions/{version}/restore zet terug: nieuwe gesprekken gebruiken die versie en het concept wordt die versie. De naam van de agent, de implementatie, het uiterlijk van de widget en de meldingen hebben geen versies en veranderen meteen, net als de documenten van zijn kennisbank.
Een test speelt één gesprek in tekst: een gesimuleerde beller speelt scenario, de agent antwoordt vanuit zijn concept (instructies, eerste bericht en kennis), en een beoordelaar toetst het gesprek aan success_criteria. Schrijf tests voor de gesprekken die ertoe doen, voer ze na elke wijziging uit en publiceer als ze slagen.
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 voert alle tests van de agent uit, of die in test_ids, en antwoordt met één run per test (status: "running"). Vraag GET /test-runs/{id} op tot status passed, failed of error is; de run bevat dan het transcript en de reason van de beoordelaar (in het Engels, of in het Chinees met reason_language: "zh"). Elke run kost Credits, getoond bij de tests in het dashboard; runs stoppen bij de eerste die het saldo niet kan betalen, en not_started toont de rest. Een run die niet kan worden uitgespeeld, wordt terugbetaald. Tests controleren wat de agent zegt, niet zijn stem of timing, en roepen zijn functies niet aan: test die vooraf met POST /tools/{id}/test. Een agent heeft maximaal 30 tests; PATCH en DELETE /agents/{id}/tests/{test_id} wijzigen of verwijderen er een.
Naast hele gesprekken (type: "simulation", standaard) kan een test één antwoord controleren. type: "next_reply" neemt chat_history, het gesprek ervoor dat eindigt met de beller ([{role, text}], met role als caller of agent), en success_criteria voor het volgende antwoord van de agent. type: "tool_call" neemt chat_history en expected_tool {name, arguments, match}: de agent moet name aanroepen (een van zijn functies, of end_call, transfer_call, handoff_to_agent) met arguments, exact vergeleken (tekst getrimd, hoofdletters genegeerd) of met match: "judge" op betekenis; andere argumenten die hij meegeeft worden genegeerd. Het transcript van een run toont in tool_calls de functies die de agent aanriep.
POST /agents/{id}/tests/generate stelt tests voor op basis van het concept van de agent: zijn instructies, kennis en functies. Het neemt count (1–10, standaard 5), types, focus (wat gedekt moet worden) en language (en of zh, voor namen en criteria), kost Credits zoals één run en antwoordt met concepten om na te kijken en naar POST /agents/{id}/tests te sturen; met save: true worden ze opgeslagen.
hooks doet iets op een moment in het gesprek, tot 10 regels: on: "silence" (de beller heeft seconds niets gezegd, 3–600, sinds de agent klaar was; eenmalig totdat ze weer praten), on: "time_elapsed" (seconds na het begin van het gesprek, 10–3600) of on: "call_ending". Elke regel voert (do) tot 3 van {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?} en {type: "tool", name, args?} uit; een regel voor het einde van het gesprek voert alleen functies uit. Een gesprek dat door een regel wordt beëindigd, heeft ended_reason rule_ended.
Bij telefoongesprekken bereiken de toetsen die een beller indrukt de agent als antwoord: een invoer eindigt met # of een pauze van twee seconden. Bij uitgaande gesprekken kan de agent ook zelf toetsen indrukken om door het keuzemenu van een bedrijf te komen.
transfer_number is het hoofdnummer van de agent om een beller aan een mens door te geven, en het nummer dat wordt gebeld als de werkruimte geen credits meer heeft. transfer_destinations voegt benoemde nummers toe, elk met een notitie over wanneer het te gebruiken („Facturatie: vragen over facturen en terugbetalingen”), en de agent kiest. Met transfer_briefing aan schrijft de agent bij het doorverbinden een korte samenvatting; de persoon die opneemt hoort die, met een EvaSay-stem en in de samenvattingstaal van de agent, voordat de beller wordt doorgezet. De ended_reason van het gesprek is dan transferred.
Met handoffs kan een agent een gesprek overdragen aan een andere gepubliceerde agent in je werkruimte, bijvoorbeeld een receptie die een boeking doorgeeft aan een boekingsagent. De agent kiest op basis van when. De andere agent neemt hetzelfde gesprek over met alles wat tot dan toe is gezegd, en spreekt met zijn eigen instructies, stem, kennis, tools en overdrachten. Het gesprek blijft vermeld, geanalyseerd en gefactureerd bij de eerste agent; het transcript noteert elke overdracht.
Nummers worden in het dashboard gekoppeld met uw eigen provideraccount. GET /api/v1/voice/phone-numbers toont ze, en PATCH /api/v1/voice/phone-numbers/{id} met { "agent_id": "AGENT_ID" } stuurt de gesprekken van het nummer naar een agent (de gepubliceerde versie), of met "agent_id": null naar geen enkele; label geeft het een andere naam.
Een woordenlijst bevat namen en termen die bellers zeggen en die spraakherkenning anders verkeerd zou verstaan, zoals product-, medicijn- of straatnamen. POST /api/v1/voice/vocabularies met { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } (tot 1.000 termen van 50 tekens, of "preset": "medical" of "legal" als vertrekpunt), en stel dan de vocabulary_id van de agent in. GET, PATCH (name, terms) en DELETE /vocabularies/{id}; een lijst die een agent gebruikt, kan niet worden verwijderd.
Elk nummer heeft gespreksregels op de pagina Telefoonnummers, toegepast voordat de agent opneemt: geblokkeerde nummers of nummerbegin (+1555) en bellers die hun nummer verbergen worden geweigerd zonder op te nemen en kosten niets; bellers op een lijst kunnen direct naar een ander nummer worden doorgezet; en als de agent niet kan opnemen (niet gepubliceerd, geen worker, geen credits zonder doorverbindnummer) kan het gesprek een nummer laten overgaan of je eigen bericht laten horen. Een startopzoeking (run_at_start) kan een beller ook weigeren door {"reject": true, "message": "..."} te antwoorden: de agent zegt het bericht, indien aanwezig, en hangt op, met ended_reason rejected.
Plaats één gesprek vanaf een van de telefoonnummers van je werkruimte. Elk verzoek heeft een unieke Idempotency-Key nodig; dezelfde sleutel opnieuw sturen geeft hetzelfde gesprek terug in plaats van twee keer te bellen.
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 is verplicht als de werkruimte meer dan één telefoonnummer heeft. variables vullen plaatshouders als {{customer_name}} in het eerste bericht en de prompt van de agent.
GET /api/v1/voice/calls/{id} geeft de status van het gesprek, de duur, de in rekening gebrachte credits, de samenvatting, de analyse en het transcript. Geef gesprekken weer met GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page= (user_id: je eigen id voor de persoon, uit een webgesprekstoken). De turns ervan zijn het foutopsporingslogboek van het gesprek, beurt voor beurt: waar de tijd heen ging vanaf het moment dat de beller klaar was met praten tot het horen van het antwoord, in milliseconden — waitMs (einde van de spraak), sttMs (herkenning), llmMs (eerste woord van het model, één extra per ronde functies), ttsMs (eerste geluid van de stem), firstAudioMs (antwoord gehoord) — met de aangeroepen functies, onderbrekingen en fouten. De gesprekspagina toont het ook.
Met recording_enabled wordt elk gesprek opgenomen als een WAV met twee kanalen: de beller links, de agent rechts, op de samplefrequentie van het gesprek. Met recording_consent: "announce" zegt de agent eerst dat het gesprek wordt opgenomen; met "ask" vraagt hij erom, en de opname wordt alleen bewaard als de beller akkoord gaat (geen antwoord telt als nee); met "none" wordt niets gezegd. Controleer wat de wet vereist op de plek waar jij en je bellers zich bevinden.
Opnames worden bewaard in de opslag van EvaSay, of in je eigen S3-compatibele bucket (AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO) die je instelt onder Integraties → Webhooks en meldingen → Opslag van opnames. De worker die het gesprek verwerkt, uploadt het bestand rechtstreeks naar de bucket. GET /api/v1/voice/calls/{id} geeft een recordingUrl die een uur geldig is zodra de opname is opgeslagen, en recording.status (recording, stored, declined of failed).
DELETE /api/v1/voice/calls/{id} verwijdert een beëindigd gesprek met transcript, opname (in de opslag van EvaSay of in je eigen bucket), analyse en webhook-leveringen. De gebruikte Credits blijven in je creditgeschiedenis. Een gesprek dat nog niet verwijderd kan worden, antwoordt 409 met code call_not_deletable_yet: het loopt nog of is net afgelopen, wordt nog afgerekend, de opname wordt mogelijk nog geüpload, of er staan Credits open die je saldo niet dekte (voeg eerst Credits toe).
Als iemand vraagt zijn gegevens te verwijderen, verwijdert POST /api/v1/voice/calls/delete-by-caller met { "phone_number": "+14155550123" } alle gesprekken met dat nummer (het nummer van de beller bij inkomende gesprekken, het gebelde nummer bij uitgaande), of met { "user_id": "..." } alle webgesprekken met die user_id in hun token. Het antwoord is { "deleted": 12, "kept_back": { "settling": 1 }, "more": false }: hoogstens 200 gesprekken per verzoek, dus vraag opnieuw zolang more true is, en later nog eens voor de gesprekken in kept_back. Staat het nummer op je niet-bellen-lijst, dan blijft het daarop. In het dashboard kan hetzelfde via Gesprekken → Gegevens van een beller verwijderen.
Stuur POST /api/v1/voice/calls/{id}/control terwijl een gesprek bezig is. De worker die het gesprek verwerkt, pikt het commando binnen ongeveer 2 seconden op; de API antwoordt 202. Is de worker die het gesprek verwerkt niet bereikbaar, dan antwoordt de API 503 (code call_unreachable) en wordt het commando niet uitgevoerd.
type | Wat er gebeurt |
|---|---|
say | De agent zegt text na zijn huidige zin, of meteen met "interrupt": true |
note | De agent krijgt text te horen (niet uitgesproken) en houdt er vanaf zijn volgende antwoord rekening mee; het transcript noteert het |
end | De agent zegt text indien opgegeven en hangt dan op; een telefoongesprek zonder text wordt direct opgehangen |
De ended_reason van het gesprek is dan api_ended. De gesprekspagina in het dashboard heeft dezelfde bediening.
POST /api/v1/voice/calls/{id}/listen (eventueel { "channels": 2 }) geeft een listen_url die je binnen twee minuten kunt openen. Het is een WebSocket: eerst een JSON-bericht {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}, daarna binaire frames met 16-bits little-endian PCM met wat de beller hoort (beide stemmen gemengd; met channels: 2 de beller links en de agent rechts). Hij sluit zodra het gesprek eindigt. De audio komt rechtstreeks van de worker die het gesprek verwerkt, niet via het besturingsvlak van EvaSay; de knop Luisteren op de gesprekspagina van het dashboard gebruikt hetzelfde adres.
Verstuur en ontvang de audio van een gesprek vanuit je eigen programma, bijvoorbeeld je eigen telefoonsysteem of app. Start het gesprek:
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 }'
De websocket_url in het antwoord bevat een eenmalig token en kan binnen 15 minuten worden geopend. encoding is pcm_s16le (16-bits little-endian PCM met een sample_rate van 8000, 16000 of 24000) of mulaw (8 kHz μ-law). Audio gaat in beide richtingen als binaire frames in dat formaat, bij voorkeur van 20 ms. Tekstframes zijn JSON:
| Richting | Berichten |
|---|---|
| Jij → EvaSay | {"type":"dtmf","digits":"12#"} toetsaanslagen; {"type":"end"} hangt op; {"type":"text","text":"…"} woorden die de beller typte ("input":"button" voor een aangetipt antwoord); {"type":"context","text":"…","key":"cart"} wat je app meldt (de agent antwoordt er niet op); {"type":"tool_result","id":"…","result":{…}} (of "error") het antwoord van een paginafunctie |
| EvaSay → jij | start (met callId); clear (de beller onderbrak: gooi nog niet afgespeelde audio van de agent weg); transcript (role, text, final, en input voor getypte woorden); user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking; cards (cards, quick_replies); tool (id, name, arguments: voer een van je paginafuncties uit); notice (code: too_fast of limit voor getypte woorden); ended (met reason, daarna sluit de socket) |
De agent begroet zodra je verbinding maakt. user_id en page_tools (de paginafuncties waarop je app antwoordt, zie hieronder) kunnen aan het verzoek worden toegevoegd. Gesprekken worden per minuut gefactureerd zoals elk ander, met dezelfde gespreksgegevens, analyse en webhooks.
Bij een webgesprek (de widget, de gehoste pagina, je app) ziet de beller een chat naast de stem, zodat de agent meer kan doen dan spreken. Bellers per telefoon hebben dit niet.
Zet Agent → Tools → Naar de beller sturen aan (send_to_caller in de agent-API) en de agent kan tot vijf kaarten tegelijk op het scherm van de beller zetten: een titel, een regel eronder, een beschrijving, een afbeelding, een link en elk tot twee antwoordknoppen; hij kan ook tot drie snelle antwoorden onder een vraag aanbieden. Links worden nooit voorgelezen.
De agent mag alleen links en afbeeldingen sturen die hij in dat gesprek heeft gekregen: zijn instructies en begroeting, de kennisbank (een document dat van een webpagina is gelezen draagt het adres daarvan), de antwoorden van je functies en de kant-en-klare kaarten. Een link moet bovendien op een van de allowed_hosts staan (de eigen site van een kant-en-klare kaart altijd). Links die de agent verzint, of die de beller voorleest, worden niet verstuurd. Links naar die websites in de woorden van de agent zijn in de chat aanklikbaar.
"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": "" }
]
}
Elke verzending krijg je als de call.message_sent-webhook (channel, items met title en url, quickReplies, blocked). Een gesprek verstuurt maximaal 10 keer.
Tijdens het gesprek kan de beller ook typen (een e-mailadres, een bestelnummer) of op een antwoordknop tikken; de agent antwoordt erop als op spraak en stopt eerst als hij aan het woord was. Tot 500 tekens per bericht, één per seconde, 100 per gesprek. In het gespreksverslag zijn deze beurten gemarkeerd als getypt of aangetikt.
De widget geeft je pagina een evasay-object. Alles is optioneel, en het gespreksframe luistert alleen naar je pagina als die een van de toegestane websites van de widget is (Agent → Implementeren):
<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 }); // for the next call
agent.update('Cart: 2 items, $120', { key: 'cart' }); // during it
agent.registerTool('add_to_cart', async function (args) {
// your code, in the visitor's browser
return { ok: true, checkout: 'https://shop.example.com/checkout' };
});
agent.on('cards', function (cards) {}); // also 'live', 'ended'
});
</script>
setVariables vullen {{name}} in de instructies en de begroeting. Ze komen uit de browser van de bezoeker, dus de agent krijgt te horen dat ze niet geverifieerd zijn, en ze bereiken nooit je functies of de startopzoeking.update vertelt de agent wat er op de pagina gebeurt zonder dat hij hoeft te antwoorden; een nieuwere update met dezelfde key vervangt de oudere."kind": "page", geen url) aan de agent toe en registreer op je pagina een functie met dezelfde naam. De agent mag haar alleen aanroepen bij gesprekken vanaf een pagina die haar heeft geregistreerd; hij wacht tot timeout_sec (1–10, standaard 5) op het antwoord, tenzij wait_for_result false is. Ze draait ingelogd als de bezoeker, als een klik van hen: vraag op je eigen pagina om bevestiging vóór alles wat geld kost of niet ongedaan te maken is. Links in het antwoord mogen als kaarten worden verstuurd.Zet Wie een gesprek kan starten op Alleen met een token en laat je server een eenmalig token ophalen voor de ingelogde gebruiker:
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 }'
Het antwoord bevat token (één gesprek, binnen expires_in seconden, 30–3600, standaard 600), agent (de slug) en web_call_url. Geef het token aan de pagina met data-token-url="/your/endpoint" op het script van de widget (hij doet daar een POST met de cookies van de bezoeker en leest { "token": "…" }) of met agent.setToken(async () => token). De variables van het token zijn geverifieerd: de instructies, de startopzoeking en de sjablonen van je functies mogen ze gebruiken, evenals user_id, dat het gesprek bewaart (userId in het gesprek, user_id om gesprekken te filteren). Houd je API-sleutel op je server.
Doe in je eigen app een POST met { "token": "…", "pageTools": ["add_to_cart"] } naar https://evasay.com + web_call_url. Het antwoord is de verbinding van het gesprek (een WebRTC-offeradres, of een WebSocket-adres, met zijn gesprekstoken), voor een RTVI-client zoals de client-SDK's van Pipecat voor web, iOS, Android en React Native. Kaarten, paginafuncties en meldingen komen binnen als RTVI-server-messages (evasay.cards, evasay.tool, evasay.notice); stuur getypte woorden met send-text en meldingen en antwoorden van de pagina als client-messages evasay.context en evasay.tool_result. Of vervoer de audio zelf met Audio via een WebSocket, met user_id en page_tools erbij.
Onder Telefoonnummers → SIP kunnen je eigen PBX, SIP-telefoons of SIP-trunk je agents rechtstreeks bellen; je SIP-aanbieder factureert de gesprekken:
sip:ev…@<EvaSay's SIP domain>, alleen inkomende gesprekken, voor PBX'en (3CX, FreePBX, Asterisk…) en SIP-telefoons.from); doorverbindingen naar een persoon worden per REFER aan de trunk overgedragen. Als de trunk om inloggegevens vraagt, antwoordt EvaSay met de gebruikersnaam en het wachtwoord van de trunk die je hebt ingevoerd.Inkomende gesprekken melden zich aan met digest en de gebruikersnaam en het wachtwoord die EvaSay je geeft (de realm is het SIP-domein); je kunt ook de bron-IP's beperken, of alleen op IP accepteren als je aanbieder zich niet kan aanmelden (de IP's zijn dan verplicht). Elk SIP-adres of elke trunk heeft een limiet voor gelijktijdige gesprekken; daarboven krijgen gesprekken 486. Wachtwoorden worden één keer getoond, bij aanmaken of resetten. Alleen UDP en G.711 (PCMU, PCMA); toetsaanslagen via RFC 4733 of SIP INFO; zet registratie (REGISTER) uit op de trunk van je PBX.
Een campagne belt een lijst contacten met één agent, binnen belttijden en een gelijktijdigheidslimiet (max_concurrency, tot 100 gesprekken tegelijk, en nooit meer dan de limiet voor gelijktijdige gesprekken van je werkruimte), en probeert het opnieuw bij mensen die niet bereikt zijn.
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" }
]
}'
Contacten kunnen ook CSV-tekst zijn met het telefoonnummer in de eerste kolom. consent_confirmed: true is verplicht: hiermee bevestig je dat iedereen op de lijst ermee akkoord is gegaan gebeld te worden, ook door een AI-stem, en EvaSay bewaart die bevestiging bij de campagne (consent_confirmed_at). De campagne wordt als concept gemaakt; start haar met POST /api/v1/voice/campaigns/{id}/start, en gebruik pause of cancel op dezelfde manier. De agent moet gepubliceerd zijn voordat een campagne start.
Met voicemail_detection aan (de standaard) wacht een uitgaand gesprek tot de andere kant praat voordat de agent begroet; blijven ze stil, dan begroet de agent na twee of drie seconden. Als de agent een voicemail of een opname van een telefoonmaatschappij hoort, wacht hij tot de begroeting en de piep voorbij zijn, laat voicemail_message achter, of hangt op als er geen is. Zulke gesprekken eindigen met ended_reason voicemail, worden gefactureerd voor de tijd die ze duurden en worden niet geanalyseerd of gemeld.
In een campagne wordt een nummer dat een voicemail bereikte opnieuw gebeld als een nummer dat niet opnam, en het bericht wordt alleen bij de laatste poging achtergelaten, zodat niemand hetzelfde bericht twee keer krijgt.
Elk uitgaand gesprek, uit een campagne of via POST /api/v1/voice/calls, controleert eerst de niet-bellen-lijst van de werkruimte. Een nummer op de lijst wordt nooit gebeld: de API antwoordt 409 met code do_not_call, en een campagne markeert het contact als canceled met outcome do_not_call en gaat door naar het volgende.
Als de persoon in een gesprek dat de agent plaatste vraagt niet meer gebeld te worden, zet de agent het nummer zelf op de lijst, zegt dat tegen hen, en wordt de call.do_not_call-webhook verstuurd. Nummers kunnen ook worden toegevoegd en verwijderd onder Campagnes → Niet-bellen-lijst of met de 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" }'
Nummers staan in internationaal formaat; tot 1.000 tegelijk. GET /api/v1/voice/do-not-call toont ze, nieuwste eerst (search, limit, page), elk met zijn source (call, dashboard of api). DELETE /api/v1/voice/do-not-call/%2B14155550100 haalt er weer een af; doe dat alleen als de persoon ermee akkoord is gegaan gebeld te worden.
POST /api/v1/voice/voices maakt een stem van een WAV-opname van één spreker (multipart audio, name, language, consent=true). Het antwoordt 202 met status running; vraag GET /api/v1/voice/voices op tot het ready is en zet dan de voice_id van een agent op de voice_id van de stem. language is de taal die in de opname wordt gesproken, een van de hierboven genoemde agenttalen; de stem spreekt ze allemaal behalve Chinees, waar een ingebouwde stem van hetzelfde geslacht antwoordt, en lijkt het meest op de spreker in de taal van de opname. Elke gekloonde stem kost credits, die het dashboard toont voordat je er een maakt.
Voeg een https-adres toe onder Integraties → Webhooks en meldingen om gespreksgebeurtenissen te ontvangen:
| Gebeurtenis | Wanneer |
|---|---|
call.started | De agent nam op |
call.ended | Het gesprek eindigde, met duur en credits |
call.analyzed | De samenvatting, uitkomst, geëxtraheerde velden en het transcript zijn klaar |
call.transcript | Tijdens het gesprek, om de paar seconden de nieuwe regels van het transcript |
call.tool_called | De agent riep een van je functies aan: name, arguments, status, durationMs |
call.handed_off | Het gesprek werd aan een andere agent overgedragen: fromAgentId, toAgentId |
call.transferred | Het gesprek werd doorverbonden naar een persoon: destination |
call.voicemail | Een uitgaand gesprek bereikte een voicemail |
call.do_not_call | De persoon in een uitgaand gesprek vroeg niet meer gebeld te worden: phone |
call.recording_ready | De opname met twee kanalen is opgeslagen: recordingUrl (24 uur geldig), durationMs |
call.message_sent | De agent toonde kaarten op het scherm van een web- of appbeller: items, quickReplies |
alert.triggered | Een van je alerts (Webhooks en meldingen → Alerts) overschreed zijn drempel |
alert.resolved | Die alert is weer normaal |
Gebeurtenissen die tijdens een gesprek worden verstuurd, bevatten data.call (id, agentId, channel, direction, nummers). Elk verzoek is ondertekend:
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Controleer de handtekening met het webhookgeheim dat werd getoond toen je de webhook maakte, en antwoord met een 2xx-status. Mislukte bezorgingen worden na 1 minuut, 5 minuten, 30 minuten, 2 uur en 12 uur opnieuw geprobeerd met dezelfde inhoud en hetzelfde EvaSay-Delivery-id, zodat je herhalingen kunt negeren. De laatste bezorgingen van een webhook, met de exact verstuurde inhoud en de status die je endpoint antwoordde, worden getoond door GET /webhooks/{id}/deliveries en in het dashboard; POST /webhooks/{id}/deliveries/{delivery_id} verstuurt er een opnieuw.
Naast e-mail en de webhooks alert.triggered / alert.resolved kunnen alerts naar een Slack-kanaal (incoming webhook), een WeCom-groepsrobot of Telegram gaan onder Webhooks en meldingen → Alerts → Ook versturen naar.
Fouten hebben één vorm:
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| Status | Betekenis |
|---|---|
| 400 | Ongeldige invoer (code en param zeggen welke) |
| 401 | Ontbrekende of ongeldige API-sleutel |
| 402 | Te weinig credits om het gesprek te starten |
| 403 | De sleutel is alleen-lezen, of de agent is niet beschikbaar |
| 404 | Geen agent, gesprek of campagne met die naam in deze werkruimte |
| 409 | Verkeerde status (bijv. een lopende campagne starten), een account bij een aanbieder is niet gekoppeld, of het nummer staat op de niet-bellen-lijst (do_not_call) |
| 429 | Te veel verzoeken; vertraag en probeer opnieuw |
De Voice API-verzoeken van de werkruimte van de afgelopen 30 dagen (tijd, endpoint, status, duur, sleutel en foutcode; geen inhoud) staan onder API-sleutels → API-verzoekenlogboek.
Leesverzoeken zijn beperkt tot 120 per minuut per sleutel, schrijfverzoeken en gesprekken tot 30.
Gesprekken worden per minuut afgeschreven van de credits van de werkruimte, hetzelfde saldo dat het dashboard gebruikt. De actuele tarieven staan op de prijspagina.
Gebruik het OpenAPI-contract om een integratieclient te genereren. De publieke API, het dashboard en het MCP-beheerendpoint werken op dezelfde resources en rechten van de werkruimte.
| Mogelijkheid | Endpoint |
|---|---|
| Herbruikbare tools | GET / POST /api/v1/voice/tools |
| Een tool lezen / bijwerken / verwijderen | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Een tool bekijken of testen | POST /api/v1/voice/tools/{id}/test |
| Tools aan een agent koppelen | PUT /api/v1/voice/agents/{id}/tools |
| Kennisbanken | GET / POST /api/v1/voice/knowledge-bases |
| Een kennisbank lezen / bijwerken / verwijderen | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Documenten toevoegen / weergeven | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Een document lezen / verwijderen | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Abonnementen op gespreksgebeurtenissen | GET / POST /api/v1/voice/webhooks |
| Een abonnement lezen / bijwerken / verwijderen | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Een ondertekende testgebeurtenis sturen | POST /api/v1/voice/webhooks/{id}/test |
| Externe MCP-tools ontdekken | POST /api/v1/voice/mcp/discover |
| MCP-beheerendpoint | /api/v1/voice/mcp |
Registreer een HTTP-tool die je CRM-adapter bevraagt:
{
"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 geeft { "data": { "id": "TOOL_ID", ... } } terug. Koppel de definitie aan een agent met PUT /agents/AGENT_ID/tools en { "tool_ids": ["TOOL_ID"] }. Dit vervangt de hele toollijst door momentopnamen; koppel opnieuw wanneer een tool uit de bibliotheek verandert. Een definitie uit de bibliotheek verwijderen laat bestaande momentopnamen intact. Je kunt ook inline-definities lezen en vervangen met het veld tools van de agent-API. Gebruik analysis_config en widget_config om de analyse en de widget via de agent-API te configureren.
Een tool kan GET, POST, PUT, PATCH of DELETE gebruiken. query en request_body accepteren geneste JSON-sjablonen zoals { "customer": { "id": "{{customer_id}}" } }. Een volledige plaatshouder behoudt het oorspronkelijke JSON-type. Plaatshouders in de URL worden gecodeerd en mogen alleen na een vaste, publieke HTTPS-host staan. Zonder request_body ontvangen tools die geen GET zijn { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path selecteert een veld met puntnotatie. Eenvoudige parameters worden nog steeds geaccepteerd; input_schema ondersteunt daarnaast geneste objecten, arrays, enums en numerieke grenzen. Externe schemaverwijzingen en reguliere expressies worden niet geaccepteerd.
static_args: waarden die met elke aanroep worden meegestuurd, wat het model ook schrijft, zoals een winkel-id; ze vullen ook {{name}} in het adres en de body.extract: [{name, path}] bewaart velden uit het antwoord van een functie (data.id) voor het {{name}} van latere functies, zodat het customer_id van een opzoeking in de volgende bestelling kan zonder dat het model het herhaalt. De velden van een startopzoeking worden op dezelfde manier bewaard.reject_when: tot 5 regels {on: caller|agent, pattern, negate}; zolang ze allemaal gelden voor de laatste woorden, wordt de functie niet uitgevoerd en krijgt de agent reject_message.model_says: bij elke aanroep schrijft het model één zin die wordt gezegd terwijl de functie draait, in plaats van de vaste speak_before.Koppel een app onder Integraties → Webhooks en meldingen → Gekoppelde apps en gebruik daarna „Een kant-en-klare functie toevoegen” op het tabblad Kennis en tools van de agent; je hebt geen eigen endpoint nodig:
check_availability (vrije tijden op een datum) en book_appointment (boekt de tijd die de beller koos) toe. Tijden in Google Calendar volgen de tijdzone, open dagen, uren en afspraakduur die je instelt; Cal.com gebruikt de eigen beschikbaarheid van het gebeurtenistype, en een reserve-e-mailadres dat je instelt voor als de beller er geen opgeeft.save_to_sheet toe, dat een rij toevoegt met de kolommen die je opgeeft, plus het id en de tijd van het gesprek.notify_team toe, dat een door de agent geschreven bericht in een Slack-kanaal (incoming webhook) plaatst.Het besturingsvlak van EvaSay voert deze functies uit met het account dat je hebt gekoppeld; noch de worker noch de agentdefinitie bewaart de inloggegevens van de app. Bij het lezen van een agent via de API verschijnen ze als kind: "app" (met app, action, connection_id, app_config); stuur ze ongewijzigd terug bij het vervangen van tools om ze te behouden. Apps worden alleen in het dashboard gekoppeld.
Zet run_at_start: true op een van de tools van een agent en die wordt aangeroepen zodra elk gesprek begint, voordat de agent begroet of antwoordt. Parameters met de namen caller_number, called_number, direction, channel of call_id worden voor hem ingevuld. De velden die hij teruggeeft vullen {{variables}} in first_message, system_prompt en idle_messages (velden een niveau dieper als {{customer.name}}), en het hele antwoord wordt als achtergrond aan de agent gegeven. Een begroeting met variabelen wacht tot 3 seconden op het antwoord; wat niet wordt gevonden blijft leeg. Per agent kan één tool bij de start draaien.
Inloggegevens worden in de serveromgeving ingesteld voor je werkruimte en bestemming; vraag support@evasay.com om een credential_ref in te richten. Zet geen API-sleutels in agentdefinities, headers, prompts of toolparameters. Je adapter kan CRM-inloggegevens in zijn eigen omgeving bewaren.
Bekijk een tool voordat je verzoeken naar het bedrijfssysteem doet:
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
Een voorbeeld valideert de parameters en toont het samengestelde verzoek; het voert geen gesprek en neemt geen contact op met het bedrijfssysteem. Zet dry_run=false voor een echte uitvoering. Een echte schrijftest vereist ook confirmed=true en idempotency_key (8–128 letters, cijfers, punten, underscores, dubbele punten of koppeltekens). Testantwoorden bevatten ok, status, duration_ms en een begrensd resultaat. Ze geven geen inloggegevens terug. Lees- en schrijftests vereisen schrijfrechten in de werkruimte.
Tijdens een gesprek dragen zakelijke verzoeken een stabiele Idempotency-Key. Je zakelijke endpoint moet die respecteren. Identieke schrijfacties binnen een gesprek worden gededupliceerd, ook bewerkingen met onbekende uitkomst na een time-out. Een andere bedoelde bewerking heeft een afzonderlijk zakelijk argument nodig, zoals operation_id. EvaSay herhaalt schrijfacties niet automatisch. Gebeurtenissen van toolgebruik bevatten status en duur; de gesprekstestfunctie blijft een tekstsimulatie, geen test van toolgebruik.
Configureer een Streamable HTTP-MCP-verbinding met:
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
In Claude Code bijvoorbeeld:
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
De koppeling vertelt de tool hoe hij een agent bouwt: de catalogus lezen, een concept maken, kennis, functies en woordenlijsten toevoegen, tests schrijven en uitvoeren, het concept verbeteren tot ze slagen, u de dashboard_url van de agent geven om hem uit te proberen, en pas na uw akkoord publiceren en een nummer koppelen. Tools die documentatie lezen kunnen beginnen bij https://evasay.com/llms.txt, dat naar deze pagina, het OpenAPI-contract en het MCP-adres linkt.
Gebruik de beveiligde token- of omgevingsvariabele-instelling van de client. Het endpoint gebruikt authenticatie met een API-sleutel, geen OAuth-aanmeldflow. Het biedt het maken en configureren van agents, bibliotheektools, kennis, webhooks en gespreksgegevens. Publiceren en echte gesprekken plaatsen zijn schrijfacties; uitgaande gesprekken verbruiken credits en providerkosten. Elke toolaanroep controleert de rechten van de werkruimte opnieuw. Schakel de bevestigingsopties van je client in voor schrijfacties. Het endpoint gebruikt stateless JSON-antwoorden; het biedt geen meldingenstroom op de achtergrond.
Een voorbeeldinstructie voor de codeertool is: „Maak een EvaSay-agent als concept, registreer mijn CRM-opzoekendpoint, bekijk het met testargumenten, koppel het aan de agent en configureer een call.analyzed-webhook.” Controleer en autoriseer publicatie en echte gesprekken uitdrukkelijk.
POST /mcp/discover accepteert { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } en geeft de tools en schema's van de server terug. Registreer alleen de geselecteerde tool:
{
"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"]
}
}
Alleen geregistreerde tools worden beschikbaar voor de agent. Externe verbindingen ondersteunen Streamable HTTP met JSON- of SSE-antwoorden op verzoeken, publieke HTTPS-bestemmingen en vooraf geconfigureerde inloggegevens. Oudere SSE-endpoints, OAuth-onderhandeling, door de server geïnitieerde sampling/elicitation en binaire bijlagen worden niet ondersteund. Ontdekking geeft niet automatisch toegang tot alle tools van de server. Het schema van de externe tool moet inline zijn en de ondersteunde subset van JSON Schema gebruiken.
Maak een webhook via POST /webhooks met { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }. Het antwoord bevat het abonnement en een gegenereerd ondertekeningsgeheim, dat alleen bij het aanmaken wordt teruggegeven. Controleer EvaSay-Signature, dedupliceer EvaSay-Delivery en werk je CRM bij. Gebeurtenis-webhooks zijn asynchrone meldingen; tools tijdens een gesprek geven resultaten rechtstreeks aan de agent terug.