A API trabalha com os mesmos agentes, chamadas e créditos do painel web. Tudo o que você monta no painel pode ser controlado a partir dos seus próprios sistemas, e vice-versa.
| O quê | Endpoint |
|---|---|
| Listar agentes | GET /api/v1/voice/agents |
| Criar um agente | POST /api/v1/voice/agents |
| Obter / atualizar um agente | GET · PATCH /api/v1/voice/agents/{id} |
| Fazer uma chamada de saída | POST /api/v1/voice/calls |
| Listar chamadas | GET /api/v1/voice/calls |
| Obter uma chamada (transcrição) | GET /api/v1/voice/calls/{id} |
| Excluir uma chamada | DELETE /api/v1/voice/calls/{id} |
| Excluir as chamadas de uma pessoa | POST /api/v1/voice/calls/delete-by-caller |
| Campanhas | GET · POST /api/v1/voice/campaigns |
| Obter uma campanha | GET /api/v1/voice/campaigns/{id} |
| Iniciar / pausar / cancelar | POST /api/v1/voice/campaigns/{id}/{action} |
| Vozes clonadas | GET · POST /api/v1/voice/voices |
| Excluir uma voz clonada | DELETE /api/v1/voice/voices/{id} |
| Lista de não ligar | GET · POST /api/v1/voice/do-not-call |
| Remover da lista | DELETE /api/v1/voice/do-not-call/{phone} |
| Idiomas, vozes, modelos | GET /api/v1/voice/catalog |
| Testes de um agente | GET · POST /api/v1/voice/agents/{id}/tests |
| Executar testes / ler uma execução | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| Atribuir um número a um agente | PATCH /api/v1/voice/phone-numbers/{id} |
| Listas de palavras | GET · POST /api/v1/voice/vocabularies |
Crie uma chave em Integrações → Chaves de API no painel (ou com um clique na visão geral) e envie-a como token Bearer:
Authorization: Bearer sk-your-key
Uma chave atua no workspace em que seu proprietário trabalha, com o papel dessa pessoa: a chave de um membro somente leitura pode ler, mas não alterar nada.
Crie um agente a partir de um modelo, definindo opcionalmente seus campos na mesma requisição. Sem template, ele começa em branco; GET /api/v1/voice/catalog lista os modelos, junto com os idiomas e as vozes que um agente aceita. Os modelos estão em inglês e chinês, então para outro idioma envie system_prompt e first_message nesse idioma. Ou a solicitação inteira é salva ou nenhum agente é criado, então uma solicitação recusada pode ser reenviada depois de corrigida. O dashboard_url da resposta é a página do agente no painel, onde você conversa com o rascunho no navegador. Todos os agentes rodam em EvaSay ASR, EvaSay LLM e EvaSay TTS, escolhidos pela EvaSay para cada chamada de acordo com o idioma e a carga, e incluídos no preço da chamada; não há contas de outros provedores para configurar.
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..."
}'
Atualize qualquer um destes campos com PATCH /api/v1/voice/agents/{id}:
| Campo | Observações |
|---|---|
name, first_message, system_prompt | Texto |
status | published publica o rascunho como uma nova versão; draft deixa de receber chamadas |
language | Um dos 27 idiomas, p. ex. en, zh, es, de, ou multi para acompanhar quem liga |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | Limites da chamada |
interruptible, end_call_enabled | Booleanos |
confirm_details | Booleano; repete números e nomes em voz alta e pede confirmação |
ai_disclosure | Booleano, padrão true; antes da primeira mensagem, o agente diz no idioma da chamada que quem liga está falando com uma IA (omitido quando a primeira mensagem já diz isso) |
voicemail_detection | Booleano; as chamadas de saída escutam primeiro e terminam ao chegar a uma caixa postal |
voicemail_message | Deixada em uma caixa postal, pode usar {{variables}}; nenhuma para desligar |
transfer_number | Número para transferir, em formato internacional |
transfer_destinations | Até 9 números a mais, [{name, number, when}]; o agente escolhe um conforme when |
transfer_briefing | Booleano; quem atende primeiro ouve o resumo da chamada feito pelo agente |
pronunciations | Até 100 [{word, say, case_sensitive}]; a voz diz say no lugar de word |
idle_messages | Até 3 lembretes ditos em sequência a quem liga e fica em silêncio, antes de a chamada terminar |
noise_reduction | Booleano; o ruído de fundo de quem liga é filtrado antes de ser ouvido |
recording_enabled | Booleano; as chamadas são gravadas em dois canais (quem liga à esquerda, o agente à direita) |
recording_consent | announce, ask (mantida só se quem liga concordar) ou none |
recording_notice | O aviso ou a pergunta dita primeiro; vazio para usar o integrado no idioma da chamada |
handoffs | Até 5 [{agent_id, when}]: outros agentes para os quais este pode transferir uma chamada |
knowledge_base_id | Uma das suas bases de conhecimento |
vocabulary_id | Um dos seus vocabulários; os termos dele são reconhecidos com mais segurança quando quem liga os diz |
voice_id | Um id de voz integrada da EvaSay, ou uma das suas vozes clonadas (clone:…) |
As vozes integradas dependem do idioma: o chinês tem ming-designed-female-clear e ming-designed-female-warm (femininas), ming-designed-male-clear e ming-designed-male-friendly (masculinas); os demais idiomas têm F1–F5 (femininas) e M1–M5 (masculinas). Uma voz integrada que não fala o idioma do agente é substituída pela do mesmo gênero que fala. O nome de campo antigo tts_voice_id continua aceito. Qualquer outro valor de voice_id, ou uma voz clonada que ainda não esteja pronta, é recusado.
analysis_config define o que é extraído de cada chamada quando ela termina: summary (padrão true), summaryLanguage, successCriteria (o que uma chamada bem-sucedida alcança), fields (até 20 {name, type, description} a extrair, com type sendo string, number ou boolean) e scorecard (até 10 {name, description, weight}, peso de 1 a 5). Os resultados ficam na analysis da chamada e no webhook call.analyzed.
Alterar os campos de um agente altera o rascunho dele. As chamadas continuam usando a versão publicada até você publicar o rascunho com POST /api/v1/voice/agents/{id}/publish (opcionalmente com uma note), ou com status: "published" no mesmo PATCH. Cada publicação guarda uma versão numerada; published_version no agente informa qual delas as chamadas usam. Uma chamada permanece na versão que estava ativa quando começou, então publicar durante uma chamada não a altera. As chamadas de teste feitas no painel usam o rascunho.
GET /agents/{id}/versions lista as versões, e POST /agents/{id}/versions/{version}/restore volta atrás: as novas chamadas usam essa versão e o rascunho passa a ser ela. O nome do agente, a implantação, a aparência do widget e as notificações não têm versões e mudam na hora, assim como os documentos da base de conhecimento.
Um teste encena uma chamada em texto: um chamador simulado representa scenario, o agente responde a partir do rascunho (instruções, primeira mensagem e conhecimento) e um avaliador verifica a conversa segundo success_criteria. Escreva testes para as chamadas que importam, execute-os após cada mudança e publique quando passarem.
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 executa todos os testes do agente, ou os de test_ids, e responde com uma execução por teste (status: "running"). Consulte GET /test-runs/{id} até que status seja passed, failed ou error; então a execução traz o transcript e o reason do avaliador (em inglês, ou em chinês com reason_language: "zh"). Cada execução custa Credits, mostrados junto aos testes no painel; as execuções param na primeira que o saldo não consegue pagar, e not_started lista as demais. Uma execução que não consegue rodar é reembolsada. Os testes verificam o que o agente diz, não a voz nem o tempo, e não chamam suas funções: visualize-as antes com POST /tools/{id}/test. Um agente tem até 30 testes; PATCH e DELETE /agents/{id}/tests/{test_id} alteram ou removem um.
Além de chamadas completas (type: "simulation", o padrão), um teste pode verificar uma única resposta. type: "next_reply" recebe chat_history, a conversa anterior terminando com o chamador ([{role, text}], com role sendo caller ou agent), e success_criteria para a próxima resposta do agente. type: "tool_call" recebe chat_history e expected_tool {name, arguments, match}: o agente deve chamar name (uma de suas funções, ou end_call, transfer_call, handoff_to_agent) com arguments, comparados exatamente (texto aparado, sem diferenciar maiúsculas) ou, com match: "judge", pelo sentido; outros argumentos passados são ignorados. O transcript de uma execução mostra em tool_calls as funções que o agente chamou.
POST /agents/{id}/tests/generate sugere testes a partir do rascunho do agente: instruções, conhecimento e funções. Recebe count (1–10, padrão 5), types, focus (o que cobrir) e language (en ou zh, para nomes e critérios), custa Credits como uma execução e responde com rascunhos para revisar e enviar a POST /agents/{id}/tests; com save: true ele os salva.
hooks faz algo em um momento da chamada, até 10 regras: on: "silence" (quem liga não disse nada por seconds, 3–600, desde que o agente terminou; uma vez até voltar a falar), on: "time_elapsed" (seconds depois do início da chamada, 10–3600) ou on: "call_ending". Cada regra faz (do) até 3 entre {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?} e {type: "tool", name, args?}; uma regra para o fim da chamada só executa funções. Uma chamada encerrada por uma regra tem ended_reason rule_ended.
Nas chamadas telefônicas, as teclas que quem liga pressiona chegam ao agente como resposta: uma entrada termina com # ou com uma pausa de dois segundos. Nas chamadas de saída, o agente também pode pressionar teclas para atravessar o menu telefônico de uma empresa.
transfer_number é o número principal do agente para passar quem liga a uma pessoa, e o número chamado quando o workspace fica sem créditos. transfer_destinations acrescenta números com nome, cada um com uma observação sobre quando usá-lo («Financeiro: dúvidas sobre faturas e reembolsos»), e o agente escolhe. Com transfer_briefing ativado, o agente escreve um breve resumo ao transferir; quem atende o ouve, com uma voz da EvaSay e no idioma de resumo do agente, antes de quem liga ser completado. O ended_reason da chamada passa então a ser transferred.
Com handoffs, um agente pode passar uma chamada a outro agente publicado do seu workspace, por exemplo uma recepção que passa um agendamento a um agente de agendamentos. O agente escolhe conforme when. O outro agente assume a mesma chamada com tudo o que foi dito até então, e fala com suas próprias instruções, voz, conhecimento, ferramentas e transferências. A chamada continua listada, analisada e cobrada no primeiro agente; a transcrição registra cada passagem.
Os números são conectados no painel com sua própria conta da operadora. GET /api/v1/voice/phone-numbers os lista, e PATCH /api/v1/voice/phone-numbers/{id} com { "agent_id": "AGENT_ID" } envia as chamadas do número para um agente (a versão publicada), ou com "agent_id": null para nenhum; label o renomeia.
Uma lista de palavras guarda nomes e termos que os chamadores dizem e que um reconhecedor entenderia mal, como nomes de produtos, remédios ou ruas. POST /api/v1/voice/vocabularies com { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } (até 1.000 termos de 50 caracteres, ou "preset": "medical" ou "legal" como ponto de partida), depois defina o vocabulary_id do agente. GET, PATCH (name, terms) e DELETE /vocabularies/{id}; uma lista usada por um agente não pode ser excluída.
Cada número tem regras de chamada na página Números de telefone, aplicadas antes de o agente atender: números bloqueados ou prefixos de número (+1555), e quem oculta o número, são recusados sem atender e não custam nada; quem está na lista pode ser passado direto para outro número; e quando o agente não pode atender (não publicado, sem worker, sem créditos e sem número de transferência), a chamada pode tocar em um número ou reproduzir uma mensagem sua. Uma consulta de início (run_at_start) também pode recusar quem liga respondendo {"reject": true, "message": "..."}: o agente diz a mensagem, se houver, e desliga, com ended_reason rejected.
Faça uma chamada a partir de um dos números de telefone do seu workspace. Cada requisição precisa de um Idempotency-Key único; enviar a mesma chave de novo devolve a mesma chamada em vez de discar duas vezes.
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 é obrigatório quando o workspace tem mais de um número de telefone. variables preenchem marcadores do tipo {{customer_name}} na primeira mensagem e no prompt do agente.
GET /api/v1/voice/calls/{id} retorna o status da chamada, a duração, os créditos cobrados, o resumo, a análise e a transcrição. Liste as chamadas com GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page= (user_id: o seu próprio id da pessoa, vindo de um token de chamada web). Seus turns são o registro de depuração da chamada, turno a turno: para onde o tempo foi desde que quem liga terminou de falar até ouvir a resposta, em milissegundos — waitMs (fim da fala), sttMs (reconhecimento), llmMs (primeira palavra do modelo, mais uma por rodada de funções), ttsMs (primeiro áudio da voz), firstAudioMs (resposta ouvida) — com as funções chamadas, as interrupções e os erros. A página da chamada também o mostra.
Com recording_enabled, cada chamada é gravada como um WAV de dois canais: quem liga à esquerda, o agente à direita, na taxa de amostragem da chamada. Com recording_consent: "announce", o agente diz primeiro que a chamada é gravada; com "ask", ele pergunta, e a gravação só é mantida se quem liga concordar (sem resposta conta como não); com "none", nada é dito. Verifique o que a lei exige onde você e quem liga estão.
As gravações ficam no armazenamento da EvaSay, ou no seu próprio bucket compatível com S3 (AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO) configurado em Integrações → Webhooks e notificações → Armazenamento de gravações. O worker que executa a chamada envia o arquivo direto para o bucket. GET /api/v1/voice/calls/{id} retorna um recordingUrl válido por uma hora depois que a gravação é armazenada, e recording.status (recording, stored, declined ou failed).
DELETE /api/v1/voice/calls/{id} exclui uma chamada encerrada com transcrição, gravação (no armazenamento da EvaSay ou no seu próprio bucket), análise e registros de entrega de webhooks. Os Credits usados continuam no histórico de créditos. Uma chamada que ainda não pode ser excluída responde 409 com code call_not_deletable_yet: ainda está em andamento ou acabou de terminar, ainda está sendo cobrada, a gravação pode estar sendo enviada, ou ela deve Credits que o saldo não cobriu (adicione Credits antes).
Quando alguém pede a exclusão dos próprios dados, POST /api/v1/voice/calls/delete-by-caller com { "phone_number": "+14155550123" } exclui todas as chamadas com esse número (o de quem ligou nas recebidas, o número chamado nas feitas), ou com { "user_id": "..." } todas as chamadas web feitas com esse user_id no token. A resposta é { "deleted": 12, "kept_back": { "settling": 1 }, "more": false }: no máximo 200 chamadas por solicitação, então repita enquanto more for true, e mais tarde para as chamadas em kept_back. Se o número estiver na lista de não ligar, ele continua nela. O painel faz o mesmo em Chamadas → Excluir dados de quem ligou.
Envie POST /api/v1/voice/calls/{id}/control enquanto uma chamada está em andamento. O worker que executa a chamada recebe o comando em cerca de 2 segundos; a API responde 202. Se o worker que executa a chamada não puder ser alcançado, a API responde 503 (code call_unreachable) e o comando não é executado.
type | O que acontece |
|---|---|
say | O agente diz text ao terminar a frase atual, ou imediatamente com "interrupt": true |
note | O agente recebe text (sem falar) e o leva em conta a partir da próxima resposta; a transcrição o registra |
end | O agente diz text, se informado, e desliga; uma chamada telefônica sem text é desligada imediatamente |
O ended_reason da chamada passa então a ser api_ended. A página da chamada no painel tem os mesmos controles.
POST /api/v1/voice/calls/{id}/listen (opcionalmente { "channels": 2 }) retorna uma listen_url que você pode abrir em até dois minutos. É um WebSocket: primeiro uma mensagem JSON {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}, depois quadros binários de PCM de 16 bits little-endian com o que quem liga ouve (as duas vozes misturadas; com channels: 2, quem liga à esquerda e o agente à direita). Ele se fecha quando a chamada termina. O áudio vem direto do worker que executa a chamada, sem passar pelo plano de controle da EvaSay; o botão Escutar da página da chamada no painel usa o mesmo endereço.
Envie e receba o áudio de uma chamada a partir do seu próprio programa, por exemplo seu próprio sistema telefônico ou app. Inicie a chamada:
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 }'
A websocket_url da resposta traz um token de uso único e pode ser aberta em até 15 minutos. encoding é pcm_s16le (PCM de 16 bits little-endian com sample_rate de 8000, 16000 ou 24000) ou mulaw (μ-law a 8 kHz). O áudio vai nos dois sentidos como quadros binários nesse formato, idealmente de 20 ms cada. Os quadros de texto são JSON:
| Direção | Mensagens |
|---|---|
| Você → EvaSay | {"type":"dtmf","digits":"12#"} teclas pressionadas; {"type":"end"} desliga; {"type":"text","text":"…"} palavras digitadas por quem liga ("input":"button" para uma resposta tocada); {"type":"context","text":"…","key":"cart"} o que seu app informa (o agente não responde a isso); {"type":"tool_result","id":"…","result":{…}} (ou "error") a resposta de uma função de página |
| EvaSay → você | start (com callId); clear (quem liga interrompeu: descarte o áudio do agente ainda não reproduzido); transcript (role, text, final e input para palavras digitadas); user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking; cards (cards, quick_replies); tool (id, name, arguments: execute uma das suas funções de página); notice (code: too_fast ou limit para palavras digitadas); ended (com reason, e então o socket se fecha) |
O agente cumprimenta assim que você se conecta. user_id e page_tools (as funções de página às quais seu app responde, veja abaixo) podem ser adicionados à requisição. As chamadas são cobradas por minuto como qualquer outra, com os mesmos registros de chamada, análise e webhooks.
Em uma chamada web (o widget, a página hospedada, seu app), quem liga vê um chat ao lado da voz, de modo que o agente pode fazer mais do que falar. Quem liga por telefone não tem nada disso.
Ative Agente → Ferramentas → Enviar para quem liga (send_to_caller na API de agentes) e o agente poderá colocar até cinco cartões por vez na tela de quem liga: um título, uma linha abaixo, uma descrição, uma imagem, um link e até dois botões de resposta em cada um; ele também pode oferecer até três respostas rápidas sob uma pergunta. Os links nunca são lidos em voz alta.
O agente só pode enviar links e imagens que recebeu naquela chamada: suas instruções e saudação, a base de conhecimento (um documento lido de uma página web carrega o endereço dela), as respostas das suas funções e os cartões prontos. Além disso, um link precisa estar em um dos allowed_hosts (o próprio site de um cartão pronto sempre está). Links que o agente inventa, ou que quem liga dita em voz alta, não são enviados. Links para esses sites nas palavras do agente podem ser clicados no 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": "" }
]
}
Cada envio chega a você como o webhook call.message_sent (channel, items com title e url, quickReplies, blocked). Uma chamada envia no máximo 10 vezes.
Durante a chamada, quem liga também pode digitar (um endereço de e-mail, um número de pedido) ou tocar em um botão de resposta; o agente responde como se fosse fala, parando antes se estiver falando. Até 500 caracteres por mensagem, uma por segundo, 100 por chamada. No registro da chamada, esses turnos são marcados como digitados ou tocados.
O widget dá à sua página um objeto evasay. Tudo é opcional, e o quadro da chamada só escuta a sua página se ela for um dos sites permitidos do widget (Agente → Implantar):
<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 preenchem {{name}} nas instruções e na saudação. Vêm do navegador do visitante, então o agente é avisado de que não são verificados, e nunca chegam às suas funções nem à consulta de início.update informa ao agente o que acontece na página sem fazê-lo responder; uma atualização mais nova com a mesma key substitui a anterior."kind": "page", sem url) e registre na sua página uma função com o mesmo nome. O agente só pode chamá-la em chamadas de uma página que a registrou; ele espera até timeout_sec (1–10, padrão 5) pela resposta, a menos que wait_for_result seja false. Ela roda com a sessão do visitante, como um clique dele: peça confirmação na sua própria página antes de qualquer coisa que custe dinheiro ou não possa ser desfeita. Links na resposta podem ser enviados como cartões.Defina Quem pode iniciar uma chamada como Somente com token, e faça seu servidor obter um token de uso único para o usuário conectado:
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 }'
A resposta traz token (uma chamada, dentro de expires_in segundos, 30–3600, padrão 600), agent (o slug) e web_call_url. Entregue o token à página com data-token-url="/your/endpoint" no script do widget (ele faz um POST ali com os cookies do visitante e lê { "token": "…" }) ou com agent.setToken(async () => token). As variables do token são verificadas: as instruções, a consulta de início e os modelos das suas funções podem usá-las, assim como user_id, que a chamada guarda (userId na chamada, user_id para filtrar chamadas). Mantenha sua chave de API no seu servidor.
No seu próprio app, faça POST de { "token": "…", "pageTools": ["add_to_cart"] } para https://evasay.com + web_call_url. A resposta é a conexão da chamada (um endereço de oferta WebRTC, ou um endereço WebSocket, com seu token de chamada), para um cliente RTVI como os SDKs de cliente do Pipecat para web, iOS, Android e React Native. Cartões, funções de página e avisos chegam como server-message do RTVI (evasay.cards, evasay.tool, evasay.notice); envie as palavras digitadas com send-text e os relatos e respostas da página como client-message evasay.context e evasay.tool_result. Ou transporte você mesmo o áudio com Áudio por WebSocket, passando user_id e page_tools.
Em Números de telefone → SIP, seu próprio PBX, telefones SIP ou tronco SIP podem chamar seus agentes diretamente; seu provedor SIP cobra as chamadas:
sip:ev…@<EvaSay's SIP domain>, somente chamadas recebidas, para PBXs (3CX, FreePBX, Asterisk…) e telefones SIP.from); transferências para uma pessoa são entregues ao tronco por REFER. Quando o tronco pede credenciais, a EvaSay responde com o usuário e a senha do tronco que você informou.As chamadas recebidas se autenticam por digest com o usuário e a senha fornecidos pela EvaSay (o realm é o domínio SIP); você também pode limitar os IPs de origem, ou aceitar somente por IP quando seu provedor não consegue se autenticar (os IPs passam então a ser obrigatórios). Cada endereço SIP ou tronco tem um limite de chamadas simultâneas; acima dele, as chamadas recebem 486. As senhas são exibidas uma única vez, ao criar ou redefinir. Somente UDP e G.711 (PCMU, PCMA); teclas por RFC 4733 ou SIP INFO; desative o registro (REGISTER) no tronco do seu PBX.
Uma campanha liga para uma lista de contatos com um agente, dentro de um horário de chamadas e de um limite de simultaneidade (max_concurrency, até 100 chamadas ao mesmo tempo, e nunca mais do que o limite de chamadas simultâneas do seu workspace), e tenta de novo com quem não conseguiu alcançar.
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" }
]
}'
Os contatos também podem ser texto CSV com o número de telefone na primeira coluna. consent_confirmed: true é obrigatório: com ele você confirma que todas as pessoas da lista concordaram em ser chamadas, inclusive por uma voz de IA, e a EvaSay guarda essa confirmação junto com a campanha (consent_confirmed_at). A campanha é criada como rascunho; inicie-a com POST /api/v1/voice/campaigns/{id}/start, e use pause ou cancel da mesma forma. O agente precisa estar publicado antes de a campanha começar.
Com voicemail_detection ativado (o padrão), uma chamada de saída espera o outro lado falar antes de o agente cumprimentar; se ficarem em silêncio, o agente cumprimenta depois de dois ou três segundos. Quando o agente ouve uma caixa postal ou uma gravação da operadora, espera a saudação e o bipe terminarem, deixa voicemail_message, ou desliga quando não há nenhuma. Essas chamadas terminam com ended_reason voicemail, são cobradas pelo tempo que duraram e não são analisadas nem notificadas.
Em uma campanha, um número que caiu em uma caixa postal é chamado de novo como um que não atendeu, e a mensagem só é deixada na última tentativa, para que ninguém receba a mesma mensagem duas vezes.
Toda chamada de saída, de uma campanha ou de POST /api/v1/voice/calls, verifica primeiro a lista de não ligar do workspace. Um número da lista nunca é discado: a API responde 409 com code do_not_call, e uma campanha marca o contato como canceled com outcome do_not_call e segue para o próximo.
Quando a pessoa de uma chamada feita pelo agente pede para não ser chamada de novo, o agente coloca o número dela na lista por conta própria, avisa a ela, e o webhook call.do_not_call é enviado. Os números também podem ser adicionados e removidos em Campanhas → Lista de não ligar ou pela 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" }'
Os números ficam em formato internacional; até 1.000 por vez. GET /api/v1/voice/do-not-call os lista, os mais recentes primeiro (search, limit, page), cada um com sua source (call, dashboard ou api). DELETE /api/v1/voice/do-not-call/%2B14155550100 tira um da lista novamente; faça isso somente quando a pessoa tiver concordado em ser chamada.
POST /api/v1/voice/voices cria uma voz a partir de uma gravação WAV de uma única pessoa (multipart audio, name, language, consent=true). Ele responde 202 com o status running; consulte GET /api/v1/voice/voices até ficar ready e então defina o voice_id de um agente com o voice_id da voz. language é o idioma falado na gravação, qualquer um dos idiomas de agente acima; a voz fala todos eles, exceto o chinês, em que responde uma voz integrada do mesmo gênero, e se parece mais com a pessoa da gravação no idioma da gravação. Cada voz clonada custa créditos, mostrados no painel antes de você criá-la.
Adicione um endereço https em Integrações → Webhooks e notificações para receber eventos de chamada:
| Evento | Quando |
|---|---|
call.started | O agente atendeu |
call.ended | A chamada terminou, com duração e créditos |
call.analyzed | O resumo, o resultado, os campos extraídos e a transcrição estão prontos |
call.transcript | Durante a chamada, as novas linhas da transcrição a cada poucos segundos |
call.tool_called | O agente chamou uma das suas funções: name, arguments, status, durationMs |
call.handed_off | A chamada foi passada a outro agente: fromAgentId, toAgentId |
call.transferred | A chamada foi transferida para uma pessoa: destination |
call.voicemail | Uma chamada de saída chegou a uma caixa postal |
call.do_not_call | A pessoa de uma chamada de saída pediu para não ser chamada de novo: phone |
call.recording_ready | A gravação de dois canais está armazenada: recordingUrl (válido por 24 horas), durationMs |
call.message_sent | O agente mostrou cartões na tela de quem liga pela web ou pelo app: items, quickReplies |
alert.triggered | Um dos seus alertas (Webhooks e notificações → Alertas) ultrapassou o limite |
alert.resolved | Esse alerta voltou ao normal |
Os eventos enviados durante uma chamada trazem data.call (id, agentId, channel, direction, números). Cada requisição é assinada:
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Verifique a assinatura com o segredo do webhook exibido quando você o criou, e responda com um status 2xx. As entregas com falha são repetidas após 1 minuto, 5 minutos, 30 minutos, 2 horas e 12 horas com o mesmo corpo e o mesmo id EvaSay-Delivery, então você pode ignorar as repetições. As últimas entregas de um webhook, com o corpo exato enviado e o status que seu endpoint respondeu, são listadas por GET /webhooks/{id}/deliveries e no painel; POST /webhooks/{id}/deliveries/{delivery_id} reenvia uma.
Além do e-mail e dos webhooks alert.triggered / alert.resolved, os alertas podem ir para um canal do Slack (incoming webhook), um robô de grupo do WeCom ou o Telegram em Webhooks e notificações → Alertas → Enviar também para.
Os erros usam um único formato:
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| Status | Significado |
|---|---|
| 400 | Entrada inválida (code e param dizem qual) |
| 401 | Chave de API ausente ou inválida |
| 402 | Créditos insuficientes para iniciar a chamada |
| 403 | A chave é somente leitura, ou o agente está indisponível |
| 404 | Não existe esse agente, chamada ou campanha neste workspace |
| 409 | Estado incorreto (p. ex. iniciar uma campanha em andamento), uma conta de provedor não está conectada, ou o número está na lista de não ligar (do_not_call) |
| 429 | Requisições demais; reduza o ritmo e tente de novo |
As requisições à API de voz do workspace nos últimos 30 dias (hora, endpoint, status, duração, chave e código de erro; sem corpos) são listadas em Chaves de API → Registro de requisições da API.
As leituras são limitadas a 120 requisições por minuto por chave; as escritas e as chamadas, a 30.
As chamadas são cobradas por minuto dos créditos do workspace, o mesmo saldo que o painel usa. As tarifas atuais estão na página de preços.
Use o contrato OpenAPI para gerar um cliente de integração. A API pública, o painel e o endpoint de gerenciamento MCP operam sobre os mesmos recursos e permissões do workspace.
| Capacidade | Endpoint |
|---|---|
| Ferramentas reutilizáveis | GET / POST /api/v1/voice/tools |
| Ler / atualizar / excluir uma ferramenta | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Visualizar ou testar uma ferramenta | POST /api/v1/voice/tools/{id}/test |
| Vincular ferramentas a um agente | PUT /api/v1/voice/agents/{id}/tools |
| Bases de conhecimento | GET / POST /api/v1/voice/knowledge-bases |
| Ler / atualizar / excluir uma base de conhecimento | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Adicionar / listar documentos | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Ler / excluir um documento | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Assinaturas de eventos de chamada | GET / POST /api/v1/voice/webhooks |
| Ler / atualizar / excluir uma assinatura | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Enviar um evento de teste assinado | POST /api/v1/voice/webhooks/{id}/test |
| Descobrir ferramentas MCP remotas | POST /api/v1/voice/mcp/discover |
| Endpoint de gerenciamento MCP | /api/v1/voice/mcp |
Registre uma ferramenta HTTP que consulta o seu adaptador de 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 retorna { "data": { "id": "TOOL_ID", ... } }. Vincule a definição a um agente com PUT /agents/AGENT_ID/tools e { "tool_ids": ["TOOL_ID"] }. Isso substitui toda a lista de ferramentas por instantâneos; vincule de novo quando uma ferramenta da biblioteca mudar. Excluir uma definição da biblioteca mantém os instantâneos existentes. Você também pode ler e substituir definições embutidas com o campo tools da API de agentes. Use analysis_config e widget_config para configurar a análise e o widget pela API de agentes.
Uma ferramenta pode usar GET, POST, PUT, PATCH ou DELETE. query e request_body aceitam modelos JSON aninhados como { "customer": { "id": "{{customer_id}}" } }. Um marcador inteiro preserva o tipo JSON original. Os marcadores da URL são codificados e só podem aparecer depois de um host HTTPS público fixo. Sem request_body, as ferramentas que não são GET recebem { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path seleciona um campo com notação de pontos. Os parameters simples continuam aceitos; input_schema também aceita objetos aninhados, arrays, enumerações e limites numéricos. Referências a esquemas externos e expressões regulares não são aceitas.
static_args: valores enviados em toda chamada, não importa o que o modelo escreva, como um id de loja; também preenchem {{name}} no endereço e no corpo.extract: [{name, path}] guarda campos da resposta de uma função (data.id) para o {{name}} de funções posteriores, de modo que o customer_id de uma consulta possa ir para o pedido seguinte sem o modelo repeti-lo. Os campos de uma consulta de início são guardados do mesmo jeito.reject_when: até 5 regras {on: caller|agent, pattern, negate}; enquanto todas valerem para as últimas palavras, a função não é executada e o agente recebe reject_message.model_says: a cada chamada, o modelo escreve uma frase que é dita enquanto a função roda, no lugar do speak_before fixo.Conecte um app em Integrações → Webhooks e notificações → Apps conectados e depois use «Adicionar uma função pronta» na aba Conhecimento e ferramentas do agente; você não precisa de nenhum endpoint próprio:
check_availability (horários livres em uma data) e book_appointment (agenda o horário que quem liga escolheu). Os horários do Google Calendar seguem o fuso horário, os dias de funcionamento, o horário e a duração do atendimento que você definir; o Cal.com usa a disponibilidade do próprio tipo de evento, e um e-mail reserva que você definir quando quem liga não informa nenhum.save_to_sheet, que acrescenta uma linha com as colunas que você listar, além do ID e da hora da chamada.notify_team, que publica em um canal do Slack (incoming webhook) uma mensagem escrita pelo agente.O plano de controle da EvaSay executa essas funções com a conta que você conectou; nem o worker nem a definição do agente guardam as credenciais do app. Ao ler um agente pela API, elas aparecem como kind: "app" (com app, action, connection_id, app_config); devolva-as sem alterações ao substituir tools para mantê-las. Os apps são conectados somente no painel.
Defina run_at_start: true em uma das ferramentas de um agente e ela será chamada no início de cada chamada, antes de o agente cumprimentar ou responder. Os parâmetros chamados caller_number, called_number, direction, channel ou call_id são preenchidos para ela. Os campos que ela retorna preenchem {{variables}} em first_message, system_prompt e idle_messages (campos um nível abaixo, como {{customer.name}}), e a resposta inteira é dada ao agente como contexto. Uma saudação com variáveis espera até 3 segundos pela resposta; o que não for encontrado fica em branco. Apenas uma ferramenta por agente pode rodar no início.
As credenciais são configuradas no ambiente do servidor para o seu workspace e destino; peça a support@evasay.com para provisionar um credential_ref. Não coloque chaves de API em definições de agente, cabeçalhos, prompts nem parâmetros de ferramentas. Seu adaptador pode guardar as credenciais do CRM no próprio ambiente.
Visualize uma ferramenta antes de fazer requisições ao sistema de negócio:
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
Uma visualização valida os parâmetros e mostra a requisição resultante; ela não executa uma conversa nem contata o sistema de negócio. Defina dry_run=false para uma execução real. Um teste de escrita real também exige confirmed=true e idempotency_key (8–128 letras, números, pontos, sublinhados, dois-pontos ou hifens). As respostas de teste incluem ok, status, duration_ms e um resultado limitado. Elas não retornam credenciais. Os testes de leitura e de escrita exigem permissão de escrita no workspace.
Durante uma chamada, as requisições de negócio levam um Idempotency-Key estável. Seu endpoint de negócio deve respeitá-lo. Escritas idênticas dentro de uma chamada são deduplicadas, inclusive operações de resultado desconhecido após um tempo esgotado. Uma operação diferente e intencional precisa de um argumento de negócio distinto, como operation_id. A EvaSay não reenvia escritas automaticamente. Os eventos de execução de ferramentas incluem status e duração; o recurso de teste de conversa continua sendo uma simulação de texto, não um teste de execução de ferramentas.
Configure uma conexão MCP Streamable HTTP com:
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
No Claude Code, por exemplo:
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
A conexão diz à ferramenta como montar um agente: ler o catálogo, criar um rascunho, adicionar conhecimento, funções e listas de palavras, escrever testes e executá-los, corrigir o rascunho até passarem, entregar a você o dashboard_url do agente para experimentar e só publicar e atribuir um número depois da sua aprovação. Ferramentas que leem documentação podem começar por https://evasay.com/llms.txt, que aponta para esta página, o contrato OpenAPI e o endereço MCP.
Use a configuração segura de token ou de variável de ambiente do cliente. O endpoint usa autenticação por chave de API, não um fluxo de login OAuth. Ele expõe a criação e a configuração de agentes, as ferramentas da biblioteca, o conhecimento, os webhooks e os registros de chamadas. Publicar e fazer chamadas reais são ações de escrita; as chamadas de saída consomem créditos e tarifas da operadora. Cada chamada de ferramenta verifica de novo as permissões do workspace. Ative os controles de confirmação do seu cliente para ações de escrita. O endpoint usa respostas JSON sem estado; não oferece um fluxo de notificações em segundo plano.
Um exemplo de instrução para a ferramenta de programação é: «Crie um agente EvaSay em rascunho, registre o endpoint de consulta do meu CRM, visualize-o com argumentos de teste, vincule-o ao agente e configure um webhook call.analyzed». Revise e autorize explicitamente a publicação e as chamadas reais.
POST /mcp/discover aceita { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } e retorna as ferramentas e os esquemas do servidor. Registre somente a ferramenta selecionada:
{
"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"]
}
}
Somente as ferramentas registradas ficam disponíveis para o agente. As conexões remotas aceitam Streamable HTTP com respostas JSON ou SSE às requisições, destinos HTTPS públicos e credenciais pré-configuradas. Não são aceitos endpoints SSE legados, negociação OAuth, amostragem e elicitação iniciadas pelo servidor, nem anexos binários. A descoberta não concede automaticamente acesso a todas as ferramentas do servidor. O esquema da ferramenta remota deve ser embutido e usar o subconjunto compatível de JSON Schema.
Crie um webhook com POST /webhooks e { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }. A resposta contém a assinatura e um segredo de assinatura gerado, retornado somente na criação. Verifique EvaSay-Signature, desduplique EvaSay-Delivery e atualize o seu CRM. Os webhooks de eventos são notificações assíncronas; as ferramentas durante a chamada retornam os resultados diretamente ao agente.