L'API lavora sugli stessi agenti, chiamate e crediti della dashboard web. Tutto ciò che crei nella dashboard può essere gestito dai tuoi sistemi, e viceversa.
| Cosa | Endpoint |
|---|---|
| Elencare gli agenti | GET /api/v1/voice/agents |
| Creare un agente | POST /api/v1/voice/agents |
| Leggere / aggiornare un agente | GET · PATCH /api/v1/voice/agents/{id} |
| Effettuare una chiamata in uscita | POST /api/v1/voice/calls |
| Elencare le chiamate | GET /api/v1/voice/calls |
| Leggere una chiamata (trascrizione) | GET /api/v1/voice/calls/{id} |
| Eliminare una chiamata | DELETE /api/v1/voice/calls/{id} |
| Eliminare le chiamate di un chiamante | POST /api/v1/voice/calls/delete-by-caller |
| Campagne | GET · POST /api/v1/voice/campaigns |
| Leggere una campagna | GET /api/v1/voice/campaigns/{id} |
| Avviare / sospendere / annullare | POST /api/v1/voice/campaigns/{id}/{action} |
| Voci clonate | GET · POST /api/v1/voice/voices |
| Eliminare una voce clonata | DELETE /api/v1/voice/voices/{id} |
| Elenco da non chiamare | GET · POST /api/v1/voice/do-not-call |
| Rimuovere dall'elenco | DELETE /api/v1/voice/do-not-call/{phone} |
| Lingue, voci, modelli | GET /api/v1/voice/catalog |
| Test di un agente | GET · POST /api/v1/voice/agents/{id}/tests |
| Eseguire i test / leggere un'esecuzione | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| Assegnare un numero a un agente | PATCH /api/v1/voice/phone-numbers/{id} |
| Elenchi di parole | GET · POST /api/v1/voice/vocabularies |
Crea una chiave in Integrazioni → Chiavi API nella dashboard (o con un clic dalla panoramica) e inviala come token Bearer:
Authorization: Bearer sk-your-key
Una chiave agisce nello spazio di lavoro in cui lavora il suo proprietario, con il ruolo di quella persona: la chiave di un membro in sola lettura può leggere ma non modificare nulla.
Crea un agente da un modello, impostando se vuoi i suoi campi nella stessa richiesta. Senza template parte vuoto; GET /api/v1/voice/catalog elenca i modelli insieme alle lingue e alle voci che un agente accetta. I modelli sono scritti in inglese e in cinese, quindi per un'altra lingua invii system_prompt e first_message in quella lingua. O viene salvata l'intera richiesta o non viene creato alcun agente, quindi una richiesta rifiutata può essere reinviata dopo la correzione. Il dashboard_url della risposta è la pagina dell'agente nella dashboard, dove può parlare con la bozza dal browser. Ogni agente funziona con EvaSay ASR, EvaSay LLM ed EvaSay TTS, scelti da EvaSay per ogni chiamata in base alla lingua e al carico e inclusi nel prezzo della chiamata; non ci sono account di altri fornitori da configurare.
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..."
}'
Aggiorna uno qualsiasi di questi campi con PATCH /api/v1/voice/agents/{id}:
| Campo | Note |
|---|---|
name, first_message, system_prompt | Testo |
status | published pubblica la bozza come nuova versione; draft smette di ricevere chiamate |
language | Una delle 27 lingue, ad es. en, zh, es, de, oppure multi per seguire chi chiama |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | Limiti della chiamata |
interruptible, end_call_enabled | Booleani |
confirm_details | Booleano; ripete numeri e nomi a voce e ne chiede conferma |
ai_disclosure | Booleano, predefinito true; prima del primo messaggio l'agente dice nella lingua della chiamata che chi chiama sta parlando con un'IA (omesso se il primo messaggio lo dice già) |
voicemail_detection | Booleano; le chiamate in uscita ascoltano prima e terminano davanti a una segreteria |
voicemail_message | Lasciato in una segreteria, può usare {{variables}}; nessuno per riagganciare |
transfer_number | Numero a cui trasferire, in formato internazionale |
transfer_destinations | Fino a 9 numeri in più, [{name, number, when}]; l'agente ne sceglie uno in base a when |
transfer_briefing | Booleano; chi risponde per primo sente il riepilogo della chiamata fatto dall'agente |
pronunciations | Fino a 100 [{word, say, case_sensitive}]; la voce dice say al posto di word |
idle_messages | Fino a 3 promemoria detti in sequenza a chi chiama e tace, prima che la chiamata termini |
noise_reduction | Booleano; il rumore di fondo di chi chiama viene filtrato prima di essere ascoltato |
recording_enabled | Booleano; le chiamate vengono registrate su due canali (chi chiama a sinistra, l'agente a destra) |
recording_consent | announce, ask (conservata solo se chi chiama accetta) oppure none |
recording_notice | L'avviso o la domanda detti per primi; vuoto per usare quello integrato nella lingua della chiamata |
handoffs | Fino a 5 [{agent_id, when}]: altri agenti a cui questo può passare una chiamata |
knowledge_base_id | Una delle tue basi di conoscenza |
vocabulary_id | Uno dei tuoi vocabolari; i suoi termini vengono riconosciuti con più affidabilità quando chi chiama li dice |
voice_id | Un id di voce integrata di EvaSay, oppure una delle tue voci clonate (clone:…) |
Le voci integrate dipendono dalla lingua: il cinese ha ming-designed-female-clear e ming-designed-female-warm (femminili), ming-designed-male-clear e ming-designed-male-friendly (maschili); le altre lingue hanno F1–F5 (femminili) e M1–M5 (maschili). Una voce integrata che non parla la lingua dell'agente viene sostituita da quella dello stesso genere che la parla. Il vecchio nome di campo tts_voice_id è ancora accettato. Qualsiasi altro valore di voice_id, o una voce clonata non ancora pronta, viene rifiutato.
analysis_config stabilisce cosa ricavare da ogni chiamata quando termina: summary (predefinito true), summaryLanguage, successCriteria (cosa ottiene una chiamata riuscita), fields (fino a 20 {name, type, description} da estrarre, con type pari a string, number o boolean) e scorecard (fino a 10 {name, description, weight}, peso da 1 a 5). I risultati sono nell'analysis della chiamata e nel webhook call.analyzed.
Modificare i campi di un agente modifica la sua bozza. Le chiamate continuano a usare la versione pubblicata finché non pubblichi la bozza con POST /api/v1/voice/agents/{id}/publish (facoltativamente con una note), oppure con status: "published" nello stesso PATCH. Ogni pubblicazione conserva una versione numerata; published_version nell'agente indica quale usano le chiamate. Una chiamata resta sulla versione che era attiva quando è iniziata, quindi pubblicare durante una chiamata non la cambia. Le chiamate di prova dalla dashboard usano la bozza.
GET /agents/{id}/versions elenca le versioni, e POST /agents/{id}/versions/{version}/restore torna indietro: le nuove chiamate usano quella versione e la bozza diventa quella. Il nome dell'agente, la distribuzione, l'aspetto del widget e le notifiche non hanno versioni e cambiano subito, come i documenti della sua base di conoscenza.
Un test svolge una chiamata in forma di testo: un chiamante simulato recita scenario, l'agente risponde dalla sua bozza (istruzioni, primo messaggio e conoscenza) e un giudice valuta la conversazione secondo success_criteria. Scriva test per le chiamate importanti, li esegua dopo ogni modifica e pubblichi quando passano.
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 esegue tutti i test dell'agente, o quelli in test_ids, e risponde con un'esecuzione per test (status: "running"). Interroghi GET /test-runs/{id} finché status non è passed, failed o error; a quel punto l'esecuzione contiene il transcript e la reason del giudice (in inglese, o in cinese con reason_language: "zh"). Ogni esecuzione costa Credits, mostrati accanto ai test nella dashboard; le esecuzioni si fermano alla prima che il saldo non può pagare e not_started elenca le restanti. Un'esecuzione che non riesce a svolgersi viene rimborsata. I test verificano ciò che l'agente dice, non la voce né i tempi, e non chiamano le sue funzioni: le provi prima con POST /tools/{id}/test. Un agente ha fino a 30 test; PATCH e DELETE /agents/{id}/tests/{test_id} ne modificano o ne eliminano uno.
Oltre alle chiamate complete (type: "simulation", predefinito), un test può verificare una sola risposta. type: "next_reply" riceve chat_history, la conversazione precedente che termina con il chiamante ([{role, text}], con role pari a caller o agent), e success_criteria per la risposta successiva dell'agente. type: "tool_call" riceve chat_history ed expected_tool {name, arguments, match}: l'agente deve chiamare name (una sua funzione, oppure end_call, transfer_call, handoff_to_agent) con arguments, confrontati esattamente (testo ripulito, senza distinguere maiuscole) o, con match: "judge", per significato; gli altri argomenti passati vengono ignorati. Il transcript di un'esecuzione mostra in tool_calls le funzioni chiamate dall'agente.
POST /agents/{id}/tests/generate propone test a partire dalla bozza dell'agente: istruzioni, conoscenza e funzioni. Riceve count (1–10, predefinito 5), types, focus (cosa coprire) e language (en o zh, per nomi e criteri), costa Credits come un'esecuzione e risponde con bozze da rivedere e inviare a POST /agents/{id}/tests; con save: true le salva.
hooks fa qualcosa in un momento della chiamata, fino a 10 regole: on: "silence" (chi chiama non ha detto nulla per seconds, 3–600, da quando l'agente ha finito; una volta finché non riparla), on: "time_elapsed" (seconds dopo l'inizio della chiamata, 10–3600) oppure on: "call_ending". Ogni regola esegue (do) fino a 3 tra {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?} e {type: "tool", name, args?}; una regola per la fine della chiamata esegue solo funzioni. Una chiamata terminata da una regola ha ended_reason rule_ended.
Nelle chiamate telefoniche i tasti premuti da chi chiama arrivano all'agente come risposta: un inserimento termina con # o con una pausa di due secondi. Nelle chiamate in uscita l'agente può anche premere tasti per attraversare il menu telefonico di un'azienda.
transfer_number è il numero principale dell'agente per passare chi chiama a una persona, e il numero chiamato quando lo spazio di lavoro ha esaurito i crediti. transfer_destinations aggiunge numeri con nome, ciascuno con una nota su quando usarlo («Fatturazione: domande su fatture e rimborsi»), e l'agente sceglie. Con transfer_briefing attivo, l'agente scrive un breve riepilogo mentre trasferisce; la persona che risponde lo sente, con una voce EvaSay e nella lingua di riepilogo dell'agente, prima che chi chiama venga messo in comunicazione. L'ended_reason della chiamata è allora transferred.
Con handoffs, un agente può passare una chiamata a un altro agente pubblicato del tuo spazio di lavoro, ad esempio un'accoglienza che passa una prenotazione a un agente per le prenotazioni. L'agente sceglie in base a when. L'altro agente prende in carico la stessa chiamata con tutto ciò che è stato detto finora, e parla con le proprie istruzioni, voce, conoscenza, strumenti e passaggi. La chiamata resta elencata, analizzata e fatturata sotto il primo agente; la sua trascrizione annota ogni passaggio.
I numeri si collegano nella dashboard con il proprio account dell'operatore. GET /api/v1/voice/phone-numbers li elenca e PATCH /api/v1/voice/phone-numbers/{id} con { "agent_id": "AGENT_ID" } invia le chiamate del numero a un agente (la sua versione pubblicata), oppure con "agent_id": null a nessuno; label lo rinomina.
Un elenco di parole contiene nomi e termini che i chiamanti pronunciano e che un riconoscitore capirebbe male, come nomi di prodotti, farmaci o vie. POST /api/v1/voice/vocabularies con { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } (fino a 1.000 termini di 50 caratteri, oppure "preset": "medical" o "legal" come punto di partenza), poi imposti il vocabulary_id dell'agente. GET, PATCH (name, terms) e DELETE /vocabularies/{id}; un elenco usato da un agente non può essere eliminato.
Ogni numero ha regole di chiamata nella pagina Numeri di telefono, applicate prima che l'agente risponda: numeri bloccati o inizi di numero (+1555), e chi nasconde il proprio numero, vengono rifiutati senza risposta e non costano nulla; chi è in elenco può essere passato direttamente a un altro numero; e quando l'agente non può rispondere (non pubblicato, nessun worker, crediti esauriti senza numero di trasferimento) la chiamata può far squillare un numero o far sentire un tuo messaggio. Una consultazione di avvio (run_at_start) può anche respingere chi chiama rispondendo {"reject": true, "message": "..."}: l'agente dice il messaggio, se c'è, e riaggancia, con ended_reason rejected.
Effettua una chiamata da uno dei numeri di telefono del tuo spazio di lavoro. Ogni richiesta richiede un Idempotency-Key univoco; inviare di nuovo la stessa chiave restituisce la stessa chiamata invece di comporre due volte.
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 è obbligatorio quando lo spazio di lavoro ha più di un numero di telefono. variables riempiono segnaposto del tipo {{customer_name}} nel primo messaggio e nel prompt dell'agente.
GET /api/v1/voice/calls/{id} restituisce lo stato della chiamata, la durata, i crediti addebitati, il riepilogo, l'analisi e la trascrizione. Elenca le chiamate con GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page= (user_id: il tuo id della persona, preso da un token di chiamata web). I suoi turns sono il registro di debug della chiamata, turno per turno: dove è andato il tempo da quando chi chiama ha finito di parlare a quando ha sentito la risposta, in millisecondi — waitMs (fine del parlato), sttMs (riconoscimento), llmMs (prima parola del modello, una in più per ogni giro di funzioni), ttsMs (primo audio della voce), firstAudioMs (risposta sentita) — con le funzioni chiamate, le interruzioni e gli errori. Lo mostra anche la pagina della chiamata.
Con recording_enabled, ogni chiamata viene registrata come WAV a due canali: chi chiama a sinistra, l'agente a destra, alla frequenza di campionamento della chiamata. Con recording_consent: "announce" l'agente dice prima che la chiamata è registrata; con "ask" lo chiede, e la registrazione viene conservata solo se chi chiama accetta (nessuna risposta vale come no); con "none" non viene detto nulla. Verifica che cosa richiede la legge dove ti trovi tu e dove si trovano le persone che ti chiamano.
Le registrazioni sono conservate nell'archiviazione di EvaSay, oppure nel tuo bucket compatibile con S3 (AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO) impostato in Integrazioni → Webhook e notifiche → Archiviazione delle registrazioni. Il worker che esegue la chiamata carica il file direttamente nel bucket. GET /api/v1/voice/calls/{id} restituisce un recordingUrl valido per un'ora una volta archiviata la registrazione, e recording.status (recording, stored, declined o failed).
DELETE /api/v1/voice/calls/{id} elimina una chiamata terminata con trascrizione, registrazione (nello storage di EvaSay o nel tuo bucket), analisi e invii webhook. I Credits usati restano nella cronologia dei crediti. Una chiamata che non si può ancora eliminare risponde 409 con code call_not_deletable_yet: è ancora in corso o appena terminata, è ancora in fatturazione, la registrazione potrebbe essere ancora in caricamento, oppure deve Credits che il saldo non ha coperto (aggiungi prima dei Credits).
Quando una persona chiede di cancellare i propri dati, POST /api/v1/voice/calls/delete-by-caller con { "phone_number": "+14155550123" } elimina tutte le chiamate con quel numero (quello del chiamante nelle chiamate in entrata, quello chiamato nelle chiamate in uscita), oppure con { "user_id": "..." } tutte le chiamate web fatte con quel user_id nel token. Risponde { "deleted": 12, "kept_back": { "settling": 1 }, "more": false }: al massimo 200 chiamate per richiesta, quindi ripeti finché more è true, e più tardi per le chiamate in kept_back. Se il numero è nell’elenco da non chiamare, ci resta. La dashboard fa lo stesso in Chiamate → Elimina i dati di un chiamante.
Invia POST /api/v1/voice/calls/{id}/control mentre una chiamata è in corso. Il worker che esegue la chiamata riceve il comando entro circa 2 secondi; l'API risponde 202. Se il worker che esegue la chiamata non è raggiungibile, l'API risponde 503 (code call_unreachable) e il comando non viene eseguito.
type | Che cosa succede |
|---|---|
say | L'agente dice text dopo la frase in corso, oppure subito con "interrupt": true |
note | All'agente viene detto text (senza pronunciarlo) e ne tiene conto dalla risposta successiva; la trascrizione lo annota |
end | L'agente dice text se indicato, poi riaggancia; una chiamata telefonica senza text viene riagganciata subito |
L'ended_reason della chiamata è allora api_ended. La pagina della chiamata nella dashboard ha gli stessi controlli.
POST /api/v1/voice/calls/{id}/listen (facoltativamente { "channels": 2 }) restituisce una listen_url che puoi aprire entro due minuti. È un WebSocket: prima un messaggio JSON {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}, poi frame binari di PCM a 16 bit little-endian con ciò che sente chi chiama (le due voci mescolate; con channels: 2, chi chiama a sinistra e l'agente a destra). Si chiude quando la chiamata termina. L'audio arriva direttamente dal worker che esegue la chiamata, senza passare dal piano di controllo di EvaSay; il pulsante Ascolta della pagina della chiamata nella dashboard usa lo stesso indirizzo.
Invia e ricevi l'audio di una chiamata dal tuo programma, ad esempio il tuo sistema telefonico o la tua app. Avvia la chiamata:
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 }'
La websocket_url nella risposta contiene un token monouso e può essere aperta entro 15 minuti. encoding è pcm_s16le (PCM a 16 bit little-endian con sample_rate di 8000, 16000 o 24000) oppure mulaw (μ-law a 8 kHz). L'audio viaggia nei due sensi come frame binari in quel formato, idealmente da 20 ms ciascuno. I frame di testo sono JSON:
| Direzione | Messaggi |
|---|---|
| Tu → EvaSay | {"type":"dtmf","digits":"12#"} tasti premuti; {"type":"end"} riaggancia; {"type":"text","text":"…"} parole digitate da chi chiama ("input":"button" per una risposta toccata); {"type":"context","text":"…","key":"cart"} ciò che segnala la tua app (l'agente non risponde); {"type":"tool_result","id":"…","result":{…}} (o "error") la risposta di una funzione di pagina |
| EvaSay → tu | start (con callId); clear (chi chiama ha interrotto: scarta l'audio dell'agente non ancora riprodotto); transcript (role, text, final e input per le parole digitate); user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking; cards (cards, quick_replies); tool (id, name, arguments: esegui una delle tue funzioni di pagina); notice (code: too_fast o limit per le parole digitate); ended (con reason, poi il socket si chiude) |
L'agente saluta non appena ti connetti. Alla richiesta si possono aggiungere user_id e page_tools (le funzioni di pagina a cui risponde la tua app, vedi sotto). Le chiamate sono fatturate al minuto come tutte le altre, con gli stessi registri di chiamata, analisi e webhook.
In una chiamata web (il widget, la pagina ospitata, la tua app) chi chiama vede una chat accanto alla voce, quindi l'agente può fare più che parlare. Chi chiama per telefono non ha nulla di tutto questo.
Attiva Agente → Strumenti → Invia a chi chiama (send_to_caller nell'API degli agenti) e l'agente potrà mostrare fino a cinque schede alla volta sullo schermo di chi chiama: un titolo, una riga sotto, una descrizione, un'immagine, un link e fino a due pulsanti di risposta ciascuna; può anche proporre fino a tre risposte rapide sotto una domanda. I link non vengono mai letti ad alta voce.
L'agente può inviare solo link e immagini che gli sono stati dati in quella chiamata: le sue istruzioni e il saluto, la base di conoscenza (un documento letto da una pagina web porta il suo indirizzo), le risposte delle tue funzioni e le schede predefinite. Inoltre un link deve essere in uno degli allowed_hosts (il sito di una scheda predefinita lo è sempre). I link che l'agente inventa, o che chi chiama detta a voce, non vengono inviati. I link a quei siti nelle parole dell'agente sono cliccabili nella 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": "" }
]
}
Ogni invio ti arriva come webhook call.message_sent (channel, items con title e url, quickReplies, blocked). Una chiamata invia al massimo 10 volte.
Durante la chiamata chi chiama può anche scrivere (un indirizzo e-mail, un numero d'ordine) o toccare un pulsante di risposta; l'agente risponde come al parlato, fermandosi prima se stava parlando. Fino a 500 caratteri per messaggio, uno al secondo, 100 per chiamata. Nel registro della chiamata questi turni sono contrassegnati come digitati o toccati.
Il widget dà alla tua pagina un oggetto evasay. Tutto è facoltativo, e il frame della chiamata ascolta la tua pagina solo se è uno dei siti consentiti del widget (Agente → Distribuisci):
<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 riempiono {{name}} nelle istruzioni e nel saluto. Provengono dal browser del visitatore, quindi all'agente viene detto che non sono verificati, e non arrivano mai alle tue funzioni né alla consultazione di avvio.update dice all'agente che cosa succede nella pagina senza farlo rispondere; un aggiornamento più recente con la stessa key sostituisce il precedente."kind": "page", senza url) e registra nella tua pagina una funzione con lo stesso nome. L'agente può chiamarla solo nelle chiamate da una pagina che l'ha registrata; aspetta la risposta fino a timeout_sec (1–10, predefinito 5) a meno che wait_for_result sia false. Viene eseguita con l'accesso del visitatore, come un suo clic: chiedi conferma nella tua pagina prima di qualsiasi cosa che costi denaro o non si possa annullare. I link nella sua risposta possono essere inviati come schede.Imposta Chi può avviare una chiamata su Solo con un token, e fai ottenere al tuo server un token monouso per l'utente connesso:
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 risposta contiene token (una chiamata, entro expires_in secondi, 30–3600, predefinito 600), agent (lo slug) e web_call_url. Passa il token alla pagina con data-token-url="/your/endpoint" sullo script del widget (fa un POST lì con i cookie del visitatore e legge { "token": "…" }) oppure con agent.setToken(async () => token). Le variables del token sono verificate: le istruzioni, la consultazione di avvio e i modelli delle tue funzioni possono usarle, come user_id, che la chiamata conserva (userId nella chiamata, user_id per filtrare le chiamate). Tieni la tua chiave API sul tuo server.
Nella tua app, fai POST di { "token": "…", "pageTools": ["add_to_cart"] } a https://evasay.com + web_call_url. La risposta è la connessione della chiamata (un indirizzo di offerta WebRTC, o un indirizzo WebSocket, con il suo token di chiamata), per un client RTVI come gli SDK client di Pipecat per web, iOS, Android e React Native. Schede, funzioni di pagina e avvisi arrivano come server-message RTVI (evasay.cards, evasay.tool, evasay.notice); invia le parole digitate con send-text e le segnalazioni e risposte della pagina come client-message evasay.context e evasay.tool_result. Oppure trasporta tu l'audio con Audio tramite WebSocket, passando user_id e page_tools.
In Numeri di telefono → SIP il tuo PBX, i telefoni SIP o il trunk SIP possono chiamare direttamente i tuoi agenti; le chiamate sono fatturate dal tuo fornitore SIP:
sip:ev…@<EvaSay's SIP domain>, solo chiamate in entrata, per PBX (3CX, FreePBX, Asterisk…) e telefoni SIP.from); i trasferimenti a una persona vengono consegnati al trunk con REFER. Quando il trunk chiede le credenziali, EvaSay risponde con il nome utente e la password del trunk che hai inserito.Le chiamate in entrata si autenticano con digest usando il nome utente e la password forniti da EvaSay (il realm è il dominio SIP); puoi anche limitare gli IP di origine, o accettare solo per IP quando il tuo fornitore non può autenticarsi (gli IP sono allora obbligatori). Ogni indirizzo SIP o trunk ha un limite di chiamate contemporanee; oltre quel limite le chiamate ricevono 486. Le password vengono mostrate una sola volta, alla creazione o al ripristino. Solo UDP e G.711 (PCMU, PCMA); tasti con RFC 4733 o SIP INFO; disattiva la registrazione (REGISTER) sul trunk del tuo PBX.
Una campagna chiama un elenco di contatti con un agente, entro un orario di chiamata e un limite di contemporaneità (max_concurrency, fino a 100 chiamate alla volta, e mai più del limite di chiamate contemporanee del tuo spazio di lavoro), e riprova con chi non è riuscita a raggiungere.
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" }
]
}'
I contatti possono essere anche testo CSV con il numero di telefono nella prima colonna. consent_confirmed: true è obbligatorio: con esso confermi che tutte le persone dell'elenco hanno accettato di essere chiamate, anche da una voce IA, ed EvaSay conserva questa conferma insieme alla campagna (consent_confirmed_at). La campagna viene creata come bozza; avviala con POST /api/v1/voice/campaigns/{id}/start, e usa pause o cancel allo stesso modo. L'agente deve essere pubblicato prima dell'avvio di una campagna.
Con voicemail_detection attivo (il predefinito), una chiamata in uscita aspetta che l'altra parte parli prima che l'agente saluti; se restano in silenzio, l'agente saluta dopo due o tre secondi. Quando l'agente sente una segreteria o una registrazione dell'operatore telefonico, aspetta che il saluto e il segnale acustico finiscano, lascia voicemail_message, oppure riaggancia se non ce n'è uno. Queste chiamate terminano con ended_reason voicemail, sono addebitate per il tempo che sono durate e non vengono analizzate né notificate.
In una campagna, un numero che ha raggiunto una segreteria viene richiamato come uno che non ha risposto, e il messaggio viene lasciato solo all'ultimo tentativo, così nessuno riceve due volte lo stesso messaggio.
Ogni chiamata in uscita, da una campagna o da POST /api/v1/voice/calls, controlla prima l'elenco da non chiamare dello spazio di lavoro. Un numero in elenco non viene mai composto: l'API risponde 409 con code do_not_call, e una campagna segna il contatto come canceled con outcome do_not_call e passa al successivo.
Quando la persona di una chiamata fatta dall'agente chiede di non essere richiamata, l'agente inserisce da solo il suo numero nell'elenco, glielo dice, e viene inviato il webhook call.do_not_call. I numeri si possono anche aggiungere e rimuovere in Campagne → Elenco da non chiamare o con 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" }'
I numeri sono in formato internazionale; fino a 1.000 alla volta. GET /api/v1/voice/do-not-call li elenca, i più recenti per primi (search, limit, page), ciascuno con la sua source (call, dashboard o api). DELETE /api/v1/voice/do-not-call/%2B14155550100 ne toglie uno; fallo solo quando la persona ha accettato di essere chiamata.
POST /api/v1/voice/voices crea una voce da una registrazione WAV di una sola persona (multipart audio, name, language, consent=true). Risponde 202 con stato running; interroga GET /api/v1/voice/voices finché non è ready, poi imposta il voice_id di un agente sul voice_id della voce. language è la lingua parlata nella registrazione, una qualsiasi delle lingue degli agenti indicate sopra; la voce le parla tutte tranne il cinese, dove risponde una voce integrata dello stesso genere, e somiglia di più alla persona della registrazione nella lingua della registrazione. Ogni voce clonata costa crediti, mostrati nella dashboard prima di crearla.
Aggiungi un indirizzo https in Integrazioni → Webhook e notifiche per ricevere gli eventi di chiamata:
| Evento | Quando |
|---|---|
call.started | L'agente ha risposto |
call.ended | La chiamata è terminata, con durata e crediti |
call.analyzed | Il riepilogo, l'esito, i campi estratti e la trascrizione sono pronti |
call.transcript | Durante la chiamata, le nuove righe della trascrizione ogni pochi secondi |
call.tool_called | L'agente ha chiamato una delle tue funzioni: name, arguments, status, durationMs |
call.handed_off | La chiamata è stata passata a un altro agente: fromAgentId, toAgentId |
call.transferred | La chiamata è stata trasferita a una persona: destination |
call.voicemail | Una chiamata in uscita ha raggiunto una segreteria |
call.do_not_call | La persona di una chiamata in uscita ha chiesto di non essere richiamata: phone |
call.recording_ready | La registrazione a due canali è archiviata: recordingUrl (valido 24 ore), durationMs |
call.message_sent | L'agente ha mostrato schede sullo schermo di chi chiama da web o app: items, quickReplies |
alert.triggered | Uno dei tuoi avvisi (Webhook e notifiche → Avvisi) ha superato la soglia |
alert.resolved | Quell'avviso è tornato alla normalità |
Gli eventi inviati durante una chiamata portano data.call (id, agentId, channel, direction, numeri). Ogni richiesta è firmata:
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Verifica la firma con il segreto del webhook mostrato quando lo hai creato, e rispondi con uno stato 2xx. Le consegne non riuscite vengono ritentate dopo 1 minuto, 5 minuti, 30 minuti, 2 ore e 12 ore con lo stesso corpo e lo stesso id EvaSay-Delivery, quindi puoi ignorare le ripetizioni. Le ultime consegne di un webhook, con il corpo esatto inviato e lo stato risposto dal tuo endpoint, sono elencate da GET /webhooks/{id}/deliveries e nella dashboard; POST /webhooks/{id}/deliveries/{delivery_id} ne reinvia una.
Oltre all'e-mail e ai webhook alert.triggered / alert.resolved, gli avvisi possono andare a un canale Slack (incoming webhook), a un robot di gruppo WeCom o a Telegram in Webhook e notifiche → Avvisi → Invia anche a.
Gli errori usano un unico formato:
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| Stato | Significato |
|---|---|
| 400 | Input non valido (code e param indicano quale) |
| 401 | Chiave API mancante o non valida |
| 402 | Crediti insufficienti per avviare la chiamata |
| 403 | La chiave è di sola lettura, oppure l'agente non è disponibile |
| 404 | Nessun agente, chiamata o campagna di questo tipo in questo spazio di lavoro |
| 409 | Stato errato (ad es. avviare una campagna già in corso), un account del fornitore non è collegato, oppure il numero è nell'elenco da non chiamare (do_not_call) |
| 429 | Troppe richieste; rallenta e riprova |
Le richieste all'API vocale dello spazio di lavoro negli ultimi 30 giorni (ora, endpoint, stato, durata, chiave e codice di errore; senza corpi) sono elencate in Chiavi API → Registro delle richieste API.
Le letture sono limitate a 120 richieste al minuto per chiave, le scritture e le chiamate a 30.
Le chiamate sono fatturate al minuto dai crediti dello spazio di lavoro, lo stesso saldo usato dalla dashboard. Le tariffe attuali sono nella pagina dei prezzi.
Usa il contratto OpenAPI per generare un client di integrazione. L'API pubblica, la dashboard e l'endpoint di gestione MCP operano sulle stesse risorse e sugli stessi permessi dello spazio di lavoro.
| Funzionalità | Endpoint |
|---|---|
| Strumenti riutilizzabili | GET / POST /api/v1/voice/tools |
| Leggere / aggiornare / eliminare uno strumento | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Anteprima o test di uno strumento | POST /api/v1/voice/tools/{id}/test |
| Associare strumenti a un agente | PUT /api/v1/voice/agents/{id}/tools |
| Basi di conoscenza | GET / POST /api/v1/voice/knowledge-bases |
| Leggere / aggiornare / eliminare una base di conoscenza | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Aggiungere / elencare documenti | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Leggere / eliminare un documento | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Sottoscrizioni agli eventi di chiamata | GET / POST /api/v1/voice/webhooks |
| Leggere / aggiornare / eliminare una sottoscrizione | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Inviare un evento di prova firmato | POST /api/v1/voice/webhooks/{id}/test |
| Scoprire gli strumenti MCP remoti | POST /api/v1/voice/mcp/discover |
| Endpoint di gestione MCP | /api/v1/voice/mcp |
Registra uno strumento HTTP che interroga il tuo adattatore 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 restituisce { "data": { "id": "TOOL_ID", ... } }. Associa la sua definizione a un agente con PUT /agents/AGENT_ID/tools e { "tool_ids": ["TOOL_ID"] }. Questo sostituisce l'intero elenco di strumenti con degli snapshot; riassocia quando uno strumento della libreria cambia. Eliminare una definizione della libreria mantiene gli snapshot esistenti. Puoi anche leggere e sostituire le definizioni incorporate con il campo tools dell'API degli agenti. Usa analysis_config e widget_config per configurare l'analisi e il widget tramite l'API degli agenti.
Uno strumento può usare GET, POST, PUT, PATCH o DELETE. query e request_body accettano modelli JSON annidati come { "customer": { "id": "{{customer_id}}" } }. Un segnaposto intero conserva il tipo JSON originale. I segnaposto dell'URL vengono codificati e possono comparire solo dopo un host HTTPS pubblico fisso. Senza request_body, gli strumenti diversi da GET ricevono { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path seleziona un campo con la notazione a punti. I semplici parameters sono ancora accettati; input_schema supporta inoltre oggetti annidati, array, enumerazioni e limiti numerici. Non sono accettati riferimenti a schemi esterni ed espressioni regolari.
static_args: valori inviati a ogni chiamata qualunque cosa scriva il modello, come l'id di un negozio; riempiono anche {{name}} nell'indirizzo e nel corpo.extract: [{name, path}] conserva campi della risposta di una funzione (data.id) per il {{name}} delle funzioni successive, così il customer_id di una consultazione può passare all'ordine seguente senza che il modello lo ripeta. I campi di una consultazione di avvio vengono conservati allo stesso modo.reject_when: fino a 5 regole {on: caller|agent, pattern, negate}; finché valgono tutte per le ultime parole, la funzione non viene eseguita e l'agente riceve reject_message.model_says: a ogni chiamata il modello scrive una frase che viene detta mentre la funzione è in esecuzione, al posto del speak_before fisso.Collega un'app in Integrazioni → Webhook e notifiche → App collegate, poi usa «Aggiungi una funzione predefinita» nella scheda Conoscenza e strumenti dell'agente; non serve alcun endpoint tuo:
check_availability (orari liberi in una data) e book_appointment (prenota l'orario scelto da chi chiama). Gli orari di Google Calendar seguono il fuso orario, i giorni di apertura, gli orari e la durata dell'appuntamento che imposti; Cal.com usa la disponibilità del tipo di evento stesso, e un'e-mail di riserva che imposti quando chi chiama non ne fornisce una.save_to_sheet, che accoda una riga con le colonne che elenchi, più l'ID e l'ora della chiamata.notify_team, che pubblica in un canale Slack (incoming webhook) un messaggio scritto dall'agente.Il piano di controllo di EvaSay esegue queste funzioni con l'account che hai collegato; né il worker né la definizione dell'agente conservano le credenziali dell'app. Leggendo un agente tramite l'API compaiono come kind: "app" (con app, action, connection_id, app_config); rimandale invariate quando sostituisci tools per mantenerle. Le app si collegano solo dalla dashboard.
Imposta run_at_start: true su uno strumento di un agente e verrà chiamato all'inizio di ogni chiamata, prima che l'agente saluti o risponda. I parametri chiamati caller_number, called_number, direction, channel o call_id vengono compilati per lui. I campi che restituisce riempiono {{variables}} in first_message, system_prompt e idle_messages (i campi un livello più giù come {{customer.name}}), e l'intera risposta viene data all'agente come contesto. Un saluto con variabili aspetta la risposta fino a 3 secondi; ciò che non viene trovato resta vuoto. Può essere eseguito all'avvio uno strumento per agente.
Le credenziali si configurano nell'ambiente del server per il tuo spazio di lavoro e la destinazione; chiedi a support@evasay.com di predisporre un credential_ref. Non inserire chiavi API nelle definizioni degli agenti, negli header, nei prompt o nei parametri degli strumenti. Il tuo adattatore può tenere le credenziali del CRM nel proprio ambiente.
Fai un'anteprima di uno strumento prima di inviare richieste al sistema aziendale:
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
Un'anteprima convalida i parametri e mostra la richiesta risultante; non esegue una conversazione né contatta il sistema aziendale. Imposta dry_run=false per un'esecuzione reale. Un test di scrittura reale richiede anche confirmed=true e idempotency_key (8–128 lettere, numeri, punti, trattini bassi, due punti o trattini). Le risposte dei test includono ok, status, duration_ms e un risultato limitato. Non restituiscono credenziali. I test di lettura e di scrittura richiedono il permesso di scrittura nello spazio di lavoro.
Durante una chiamata, le richieste aziendali portano un Idempotency-Key stabile. Il tuo endpoint aziendale deve rispettarlo. Le scritture identiche all'interno di una chiamata vengono deduplicate, comprese le operazioni con esito sconosciuto dopo un timeout. Un'altra operazione intenzionale richiede un argomento aziendale distinto, come operation_id. EvaSay non ripete automaticamente le scritture. Gli eventi di esecuzione degli strumenti includono stato e durata; la funzione di test della conversazione resta una simulazione di testo, non un test di esecuzione degli strumenti.
Configura una connessione MCP Streamable HTTP con:
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
In Claude Code, per esempio:
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
La connessione spiega allo strumento come costruire un agente: leggere il catalogo, creare una bozza, aggiungere conoscenza, funzioni ed elenchi di parole, scrivere test ed eseguirli, correggere la bozza finché non passano, darle il dashboard_url dell'agente per provarlo, e pubblicare e assegnare un numero solo dopo la sua approvazione. Gli strumenti che leggono la documentazione possono partire da https://evasay.com/llms.txt, che rimanda a questa pagina, al contratto OpenAPI e all'indirizzo MCP.
Usa l'impostazione sicura del client per token o variabili d'ambiente. L'endpoint usa l'autenticazione con chiave API, non un flusso di accesso OAuth. Espone la creazione e la configurazione degli agenti, gli strumenti della libreria, la conoscenza, i webhook e i registri delle chiamate. Pubblicare ed effettuare chiamate reali sono azioni di scrittura; le chiamate in uscita consumano crediti e costi dell'operatore. Ogni chiamata a uno strumento ricontrolla i permessi dello spazio di lavoro. Attiva i controlli di conferma del tuo client per le azioni di scrittura. L'endpoint usa risposte JSON senza stato; non offre un flusso di notifiche in background.
Un esempio di istruzione per lo strumento di programmazione è: «Crea una bozza di agente EvaSay, registra il mio endpoint di consultazione del CRM, fanne un'anteprima con argomenti di prova, associalo all'agente e configura un webhook call.analyzed». Controlla e autorizza esplicitamente la pubblicazione e le chiamate reali.
POST /mcp/discover accetta { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } e restituisce gli strumenti e gli schemi del server. Registra solo lo strumento selezionato:
{
"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"]
}
}
Solo gli strumenti registrati diventano disponibili per l'agente. Le connessioni remote supportano Streamable HTTP con risposte JSON o SSE alle richieste, destinazioni HTTPS pubbliche e credenziali preconfigurate. Non sono supportati endpoint SSE legacy, negoziazione OAuth, sampling o elicitation avviati dal server e allegati binari. La scoperta non concede automaticamente l'accesso a tutti gli strumenti del server. Lo schema dello strumento remoto deve essere incorporato e usare il sottoinsieme supportato di JSON Schema.
Crea un webhook con POST /webhooks e { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }. La risposta contiene la sottoscrizione e un segreto di firma generato, restituito solo alla creazione. Verifica EvaSay-Signature, deduplica EvaSay-Delivery e aggiorna il tuo CRM. I webhook degli eventi sono notifiche asincrone; gli strumenti durante la chiamata restituiscono i risultati direttamente all'agente.