La API trabaja con los mismos agentes, llamadas y créditos que el panel web. Todo lo que construyas en el panel puede controlarse desde tus propios sistemas, y al revés.
| Qué | Endpoint |
|---|---|
| Listar agentes | GET /api/v1/voice/agents |
| Crear un agente | POST /api/v1/voice/agents |
| Obtener / actualizar un agente | GET · PATCH /api/v1/voice/agents/{id} |
| Realizar una llamada saliente | POST /api/v1/voice/calls |
| Listar llamadas | GET /api/v1/voice/calls |
| Obtener una llamada (transcripción) | GET /api/v1/voice/calls/{id} |
| Eliminar una llamada | DELETE /api/v1/voice/calls/{id} |
| Eliminar las llamadas de un llamante | POST /api/v1/voice/calls/delete-by-caller |
| Campañas | GET · POST /api/v1/voice/campaigns |
| Obtener una campaña | GET /api/v1/voice/campaigns/{id} |
| Iniciar / pausar / cancelar | POST /api/v1/voice/campaigns/{id}/{action} |
| Voces clonadas | GET · POST /api/v1/voice/voices |
| Eliminar una voz clonada | DELETE /api/v1/voice/voices/{id} |
| Lista de no llamar | GET · POST /api/v1/voice/do-not-call |
| Quitar de la lista | DELETE /api/v1/voice/do-not-call/{phone} |
| Idiomas, voces, plantillas | GET /api/v1/voice/catalog |
| Pruebas de un agente | GET · POST /api/v1/voice/agents/{id}/tests |
| Ejecutar pruebas / leer una ejecución | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| Asignar un número a un agente | PATCH /api/v1/voice/phone-numbers/{id} |
| Listas de palabras | GET · POST /api/v1/voice/vocabularies |
Crea una clave en Integraciones → Claves de API del panel (o con un clic desde el resumen) y envíala como token Bearer:
Authorization: Bearer sk-your-key
Una clave actúa en el espacio de trabajo en el que trabaja su propietario, con el rol de esa persona: la clave de un miembro de solo lectura puede leer, pero no cambiar nada.
Crea un agente a partir de una plantilla y, si quieres, define sus campos en la misma solicitud. Sin template empieza en blanco; GET /api/v1/voice/catalog lista las plantillas junto con los idiomas y las voces que acepta un agente. Las plantillas están en inglés y chino, así que para otro idioma envíe system_prompt y first_message en ese idioma. O se guarda toda la solicitud o no se crea ningún agente, así que una solicitud rechazada puede reenviarse una vez corregida. dashboard_url en la respuesta es la página del agente en el panel, donde puede hablar con el borrador desde el navegador. Todos los agentes funcionan con EvaSay ASR, EvaSay LLM y EvaSay TTS, que EvaSay elige para cada llamada según su idioma y la carga, y que están incluidos en el precio de la llamada; no hay cuentas de otros proveedores que 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..."
}'
Actualiza cualquiera de estos campos con PATCH /api/v1/voice/agents/{id}:
| Campo | Notas |
|---|---|
name, first_message, system_prompt | Texto |
status | published publica el borrador como una versión nueva; draft deja de recibir llamadas |
language | Uno de 27 idiomas, p. ej. en, zh, es, de, o multi para seguir a quien llama |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | Límites de la llamada |
interruptible, end_call_enabled | Booleanos |
confirm_details | Booleano; repite números y nombres en voz alta y pide que se confirmen |
ai_disclosure | Booleano, por defecto true; antes de su primer mensaje, el agente dice en el idioma de la llamada que quien llama habla con una IA (se omite si el primer mensaje ya lo dice) |
voicemail_detection | Booleano; las llamadas salientes escuchan primero y terminan al llegar a un buzón |
voicemail_message | Se deja en un buzón, puede usar {{variables}}; ninguno para colgar |
transfer_number | Número al que transferir, en formato internacional |
transfer_destinations | Hasta 9 números más, [{name, number, when}]; el agente elige uno según when |
transfer_briefing | Booleano; la persona que contesta primero oye el resumen de la llamada que hace el agente |
pronunciations | Hasta 100 [{word, say, case_sensitive}]; la voz dice say en lugar de word |
idle_messages | Hasta 3 recordatorios que se dicen por turno a quien llama y guarda silencio antes de terminar la llamada |
noise_reduction | Booleano; el ruido de fondo de quien llama se filtra antes de que lo oigan |
recording_enabled | Booleano; las llamadas se graban en dos canales (quien llama a la izquierda, el agente a la derecha) |
recording_consent | announce, ask (se conserva solo si quien llama acepta) o none |
recording_notice | El aviso o la pregunta que se dice primero; vacío para usar el integrado en el idioma de la llamada |
handoffs | Hasta 5 [{agent_id, when}]: otros agentes a los que este puede pasar una llamada |
knowledge_base_id | Una de tus bases de conocimiento |
vocabulary_id | Uno de tus vocabularios; los términos que contiene se reconocen mejor cuando los dice quien llama |
voice_id | Un id de voz integrada de EvaSay, o una de tus voces clonadas (clone:…) |
Las voces integradas dependen del idioma: el chino tiene ming-designed-female-clear y ming-designed-female-warm (femeninas), ming-designed-male-clear y ming-designed-male-friendly (masculinas); los demás idiomas tienen F1–F5 (femeninas) y M1–M5 (masculinas). Una voz integrada que no habla el idioma del agente se sustituye por la del mismo género que sí lo habla. El nombre de campo anterior tts_voice_id se sigue aceptando. Cualquier otro valor de voice_id, o una voz clonada que aún no esté lista, se rechaza.
analysis_config define qué se analiza de cada llamada al terminar: summary (por defecto true), summaryLanguage, successCriteria (lo que logra una llamada exitosa), fields (hasta 20 {name, type, description} que extraer, con type igual a string, number o boolean) y scorecard (hasta 10 {name, description, weight}, peso de 1 a 5). Los resultados están en el analysis de la llamada y en el webhook call.analyzed.
Cambiar los campos de un agente cambia su borrador. Las llamadas siguen usando la versión publicada hasta que publiques el borrador con POST /api/v1/voice/agents/{id}/publish (opcionalmente con una note), o con status: "published" en el mismo PATCH. Cada publicación conserva una versión numerada; published_version en el agente indica cuál usan las llamadas. Una llamada se queda en la versión que estaba activa cuando empezó, así que publicar durante una llamada no la cambia. Las llamadas de prueba desde el panel usan el borrador.
GET /agents/{id}/versions lista las versiones, y POST /agents/{id}/versions/{version}/restore vuelve atrás: las llamadas nuevas usan esa versión y el borrador pasa a ser ella. El nombre del agente, el despliegue, el aspecto del widget y las notificaciones no tienen versiones y cambian de inmediato, igual que los documentos de su base de conocimiento.
Una prueba simula una llamada en texto: un llamante simulado representa scenario, el agente responde desde su borrador (instrucciones, primer mensaje y conocimiento) y un evaluador revisa la conversación según success_criteria. Escriba pruebas para las llamadas que importan, ejecútelas tras cada cambio y publique cuando pasen.
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 ejecuta todas las pruebas del agente, o las de test_ids, y responde con una ejecución por prueba (status: "running"). Consulte GET /test-runs/{id} hasta que status sea passed, failed o error; entonces la ejecución tiene el transcript y el reason del evaluador (en inglés, o en chino con reason_language: "zh"). Cada ejecución cuesta Credits, que se muestran junto a las pruebas en el panel; las ejecuciones se detienen en la primera que el saldo no puede pagar, y not_started lista el resto. Una ejecución que no llega a completarse se reembolsa. Las pruebas comprueban lo que dice el agente, no su voz ni sus tiempos, y no llaman a sus funciones: pruébelas antes con POST /tools/{id}/test. Un agente tiene hasta 30 pruebas; PATCH y DELETE /agents/{id}/tests/{test_id} cambian o eliminan una.
Además de llamadas completas (type: "simulation", por defecto), una prueba puede comprobar una sola respuesta. type: "next_reply" recibe chat_history, la conversación anterior que termina con el llamante ([{role, text}], con role igual a caller o agent), y success_criteria para la siguiente respuesta del agente. type: "tool_call" recibe chat_history y expected_tool {name, arguments, match}: el agente debe llamar a name (una de sus funciones, o end_call, transfer_call, handoff_to_agent) con arguments, comparados exactamente (texto recortado, sin distinguir mayúsculas) o, con match: "judge", por significado; los demás argumentos que pase se ignoran. El transcript de una ejecución muestra en tool_calls las funciones que llamó el agente.
POST /agents/{id}/tests/generate propone pruebas a partir del borrador del agente: sus instrucciones, conocimiento y funciones. Recibe count (1–10, por defecto 5), types, focus (qué cubrir) y language (en o zh, para nombres y criterios), cuesta Credits como una ejecución y responde con borradores para revisar y enviar a POST /agents/{id}/tests; con save: true los guarda.
hooks hace algo en un momento de la llamada, hasta 10 reglas: on: "silence" (quien llama no ha dicho nada durante seconds, 3–600, desde que el agente terminó; una vez hasta que vuelva a hablar), on: "time_elapsed" (seconds después del inicio de la llamada, 10–3600) o on: "call_ending". Cada regla hace (do) hasta 3 de {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?} y {type: "tool", name, args?}; una regla para el final de la llamada solo ejecuta funciones. Una llamada que termina una regla tiene ended_reason rule_ended.
En las llamadas telefónicas, las teclas que pulsa quien llama llegan al agente como su respuesta: una entrada termina con # o con una pausa de dos segundos. En las llamadas salientes, el agente también puede pulsar teclas para atravesar el menú telefónico de una empresa.
transfer_number es el número principal del agente para pasar a quien llama con una persona, y el número al que se llama cuando el espacio de trabajo se queda sin créditos. transfer_destinations añade números con nombre, cada uno con una nota sobre cuándo usarlo («Facturación: preguntas sobre facturas y reembolsos»), y el agente elige. Con transfer_briefing activado, el agente escribe un breve resumen al transferir; la persona que contesta lo oye, con una voz de EvaSay y en el idioma de resumen del agente, antes de que se ponga en comunicación a quien llama. El ended_reason de la llamada es entonces transferred.
Con handoffs, un agente puede pasar una llamada a otro agente publicado de tu espacio de trabajo, por ejemplo una recepción que pasa una reserva a un agente de reservas. El agente elige según when. El otro agente continúa la misma llamada con todo lo dicho hasta entonces, y habla con sus propias instrucciones, voz, conocimiento, herramientas y traspasos. La llamada sigue listada, analizada y facturada bajo el primer agente; su transcripción anota cada traspaso.
Los números se conectan en el panel con su propia cuenta del operador. GET /api/v1/voice/phone-numbers los lista, y PATCH /api/v1/voice/phone-numbers/{id} con { "agent_id": "AGENT_ID" } envía las llamadas del número a un agente (su versión publicada), o con "agent_id": null a ninguno; label le cambia el nombre.
Una lista de palabras guarda nombres y términos que dicen los llamantes y que un reconocedor entendería mal, como nombres de productos, medicamentos o calles. POST /api/v1/voice/vocabularies con { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } (hasta 1.000 términos de 50 caracteres, o "preset": "medical" o "legal" como punto de partida) y luego asigne el vocabulary_id del agente. GET, PATCH (name, terms) y DELETE /vocabularies/{id}; una lista que usa un agente no se puede eliminar.
Cada número tiene reglas de llamada en la página Números de teléfono, que se aplican antes de que el agente conteste: los números bloqueados o los prefijos de número (+1555), y quienes ocultan su número, se rechazan sin contestar y no cuestan nada; a quienes están en la lista se les puede pasar directamente a otro número; y cuando el agente no puede contestar (no publicado, sin worker, sin créditos y sin número de transferencia), la llamada puede sonar en un número o reproducir tu propio mensaje. Una consulta de inicio (run_at_start) también puede rechazar a quien llama respondiendo {"reject": true, "message": "..."}: el agente dice el mensaje, si lo hay, y cuelga, con ended_reason rejected.
Realiza una llamada desde uno de los números de teléfono de tu espacio de trabajo. Cada solicitud necesita un Idempotency-Key único; enviar de nuevo la misma clave devuelve la misma llamada en lugar de marcar dos veces.
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 es obligatorio cuando el espacio de trabajo tiene más de un número de teléfono. variables rellenan los marcadores del tipo {{customer_name}} en el primer mensaje y en el prompt del agente.
GET /api/v1/voice/calls/{id} devuelve el estado de la llamada, su duración, los créditos cobrados, el resumen, el análisis y la transcripción. Lista las llamadas con GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page= (user_id: tu propio id de la persona, tomado de un token de llamada web). Sus turns son el registro de depuración turno a turno de la llamada: en qué se fue el tiempo desde que quien llama termina de hablar hasta que oye la respuesta, en milisegundos — waitMs (fin del habla), sttMs (reconocimiento), llmMs (primera palabra del modelo, una más por cada ronda de funciones), ttsMs (primer audio de la voz), firstAudioMs (se oyó la respuesta) — con las funciones llamadas, las interrupciones y los errores. La página de la llamada también lo muestra.
Con recording_enabled, cada llamada se graba como un WAV de dos canales: quien llama a la izquierda, el agente a la derecha, con la frecuencia de muestreo de la llamada. Con recording_consent: "announce", el agente dice primero que la llamada se graba; con "ask" lo pregunta, y la grabación se conserva solo si quien llama acepta (no responder cuenta como un no); con "none" no se dice nada. Comprueba qué exige la ley donde estáis tú y quienes te llaman.
Las grabaciones se guardan en el almacenamiento de EvaSay o en tu propio bucket compatible con S3 (AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO) configurado en Integraciones → Webhooks y notificaciones → Almacenamiento de grabaciones. El worker que ejecuta la llamada sube el archivo directamente al bucket. GET /api/v1/voice/calls/{id} devuelve un recordingUrl válido durante una hora una vez almacenada la grabación, y recording.status (recording, stored, declined o failed).
DELETE /api/v1/voice/calls/{id} elimina una llamada terminada con su transcripción, grabación (en el almacenamiento de EvaSay o en tu propio bucket), análisis y registros de entrega de webhooks. Los Credits usados siguen en tu historial de créditos. Una llamada que aún no se puede eliminar responde 409 con code call_not_deletable_yet: sigue en curso o acaba de terminar, aún se está facturando, su grabación puede estar subiéndose o debe Credits que tu saldo no cubrió (añade Credits primero).
Cuando una persona pide que borres sus datos, POST /api/v1/voice/calls/delete-by-caller con { "phone_number": "+14155550123" } elimina todas las llamadas con ese número (el del llamante en las entrantes, el número llamado en las salientes), o con { "user_id": "..." } todas las llamadas web hechas con ese user_id en su token. Responde { "deleted": 12, "kept_back": { "settling": 1 }, "more": false }: como máximo 200 llamadas por solicitud, así que vuelve a pedirlo mientras more sea true, y más tarde para las llamadas de kept_back. Si el número está en tu lista de no llamar, sigue en ella. El panel hace lo mismo en Llamadas → Eliminar datos de un llamante.
Envía POST /api/v1/voice/calls/{id}/control mientras una llamada está en curso. El worker que ejecuta la llamada recoge el comando en unos 2 segundos; la API responde 202. Si no se puede contactar con el worker que ejecuta la llamada, la API responde 503 (code call_unreachable) y el comando no se ejecuta.
type | Qué ocurre |
|---|---|
say | El agente dice text al terminar su frase actual, o de inmediato con "interrupt": true |
note | Se le dice al agente text (sin pronunciarlo) y lo tiene en cuenta desde su siguiente respuesta; la transcripción lo anota |
end | El agente dice text si se da y luego cuelga; una llamada telefónica sin text se cuelga de inmediato |
El ended_reason de la llamada es entonces api_ended. La página de la llamada en el panel tiene los mismos controles.
POST /api/v1/voice/calls/{id}/listen (opcionalmente { "channels": 2 }) devuelve una listen_url que puedes abrir en los dos minutos siguientes. Es un WebSocket: primero un mensaje JSON {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}, y después tramas binarias de PCM de 16 bits little-endian con lo que oye quien llama (ambas voces mezcladas; con channels: 2, quien llama a la izquierda y el agente a la derecha). Se cierra cuando termina la llamada. El audio llega directamente del worker que ejecuta la llamada, sin pasar por el plano de control de EvaSay; el botón Escuchar de la página de la llamada del panel usa la misma dirección.
Envía y recibe el audio de una llamada desde tu propio programa, por ejemplo tu propia centralita o app. Inicia la llamada:
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 de la respuesta lleva un token de un solo uso y puede abrirse en los 15 minutos siguientes. encoding es pcm_s16le (PCM de 16 bits little-endian con un sample_rate de 8000, 16000 o 24000) o mulaw (μ-law a 8 kHz). El audio va en ambos sentidos como tramas binarias en ese formato, idealmente de 20 ms cada una. Las tramas de texto son JSON:
| Dirección | Mensajes |
|---|---|
| Tú → EvaSay | {"type":"dtmf","digits":"12#"} pulsaciones de teclas; {"type":"end"} cuelga; {"type":"text","text":"…"} palabras que quien llama ha escrito ("input":"button" para una respuesta tocada); {"type":"context","text":"…","key":"cart"} lo que informa tu app (el agente no responde a ello); {"type":"tool_result","id":"…","result":{…}} (o "error") la respuesta de una función de página |
| EvaSay → tú | start (con callId); clear (quien llama interrumpió: descarta el audio del agente aún no reproducido); transcript (role, text, final, e input para palabras escritas); user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking; cards (cards, quick_replies); tool (id, name, arguments: ejecuta una de tus funciones de página); notice (code: too_fast o limit para palabras escritas); ended (con reason, y después se cierra el socket) |
El agente saluda en cuanto te conectas. A la solicitud se pueden añadir user_id y page_tools (las funciones de página a las que responde tu app, véase más abajo). Las llamadas se facturan por minuto como cualquier otra, con los mismos registros de llamada, análisis y webhooks.
En una llamada web (el widget, la página alojada, tu app), quien llama ve un chat junto a la voz, de modo que el agente puede hacer más que hablar. Quienes llaman por teléfono no tienen nada de esto.
Activa Agente → Herramientas → Enviar a quien llama (send_to_caller en la API de agentes) y el agente podrá poner hasta cinco tarjetas a la vez en la pantalla de quien llama: un título, una línea debajo, una descripción, una imagen, un enlace y hasta dos botones de respuesta cada una; también puede ofrecer hasta tres respuestas rápidas bajo una pregunta. Los enlaces nunca se leen en voz alta.
El agente solo puede enviar enlaces e imágenes que se le hayan dado en esa llamada: sus instrucciones y saludo, la base de conocimiento (un documento leído de una página web lleva su dirección), las respuestas de tus funciones y las tarjetas predefinidas. Además, un enlace debe estar en uno de los allowed_hosts (el propio sitio de una tarjeta predefinida siempre lo está). Los enlaces que el agente se inventa, o que quien llama dicta en voz alta, no se envían. Los enlaces a esos sitios web en las palabras del agente se pueden pulsar en el 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 envío te llega como el webhook call.message_sent (channel, items con title y url, quickReplies, blocked). Una llamada envía como máximo 10 veces.
Durante la llamada, quien llama también puede escribir (una dirección de correo, un número de pedido) o tocar un botón de respuesta; el agente lo responde como si fuera voz, dejando de hablar primero si estaba hablando. Hasta 500 caracteres por mensaje, uno por segundo, 100 por llamada. En el registro de la llamada, estos turnos se marcan como escritos o tocados.
El widget da a tu página un objeto evasay. Todo es opcional, y el marco de la llamada solo escucha a tu página si es uno de los sitios web permitidos del widget (Agente → Desplegar):
<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 rellenan {{name}} en las instrucciones y el saludo. Vienen del navegador del visitante, así que al agente se le dice que no están verificados, y nunca llegan a tus funciones ni a la consulta de inicio.update cuenta al agente lo que ocurre en la página sin hacer que responda; una actualización más reciente con la misma key sustituye a la anterior."kind": "page", sin url) y registra en tu página una función con el mismo nombre. El agente solo puede llamarla en llamadas desde una página que la haya registrado; espera hasta timeout_sec (1–10, por defecto 5) la respuesta, salvo que wait_for_result sea false. Se ejecuta con la sesión del visitante, como un clic suyo: pide confirmación en tu propia página antes de cualquier cosa que cueste dinero o no se pueda deshacer. Los enlaces de su respuesta pueden enviarse como tarjetas.Configura Quién puede iniciar una llamada en Solo con un token, y haz que tu servidor obtenga un token de un solo uso para el usuario con sesión iniciada:
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 respuesta incluye token (una llamada, dentro de expires_in segundos, 30–3600, por defecto 600), agent (el slug) y web_call_url. Entrega el token a la página con data-token-url="/your/endpoint" en el script del widget (hace un POST allí con las cookies del visitante y lee { "token": "…" }) o con agent.setToken(async () => token). Las variables del token están verificadas: las instrucciones, la consulta de inicio y las plantillas de tus funciones pueden usarlas, igual que user_id, que la llamada conserva (userId en la llamada, user_id para filtrar llamadas). Mantén tu clave de API en tu servidor.
En tu propia app, haz POST de { "token": "…", "pageTools": ["add_to_cart"] } a https://evasay.com + web_call_url. La respuesta es la conexión de la llamada (una dirección de oferta WebRTC, o una dirección WebSocket, con su token de llamada), para un cliente RTVI como los SDK de cliente de Pipecat para web, iOS, Android y React Native. Las tarjetas, las funciones de página y los avisos llegan como server-message de RTVI (evasay.cards, evasay.tool, evasay.notice); envía las palabras escritas con send-text y los informes y respuestas de la página como client-message evasay.context y evasay.tool_result. O transporta tú mismo el audio con Audio por WebSocket, pasando user_id y page_tools.
En Números de teléfono → SIP, tu propia centralita, teléfonos SIP o troncal SIP pueden llamar directamente a tus agentes; tu proveedor SIP factura las llamadas:
sip:ev…@<EvaSay's SIP domain>, solo llamadas entrantes, para centralitas (3CX, FreePBX, Asterisk…) y teléfonos SIP.from); las transferencias a una persona se entregan al troncal por REFER. Cuando el troncal pide credenciales, EvaSay responde con el usuario y la contraseña del troncal que introdujiste.Las llamadas entrantes se autentican por digest con el usuario y la contraseña que te da EvaSay (el realm es el dominio SIP); también puedes limitar las IP de origen, o aceptar solo por IP cuando tu proveedor no puede autenticarse (las IP son entonces obligatorias). Cada dirección SIP o troncal tiene un límite de llamadas simultáneas; por encima de él, las llamadas reciben 486. Las contraseñas se muestran una sola vez, al crearlas o restablecerlas. Solo UDP y G.711 (PCMU, PCMA); pulsaciones de teclas por RFC 4733 o SIP INFO; desactiva el registro (REGISTER) en el troncal de tu centralita.
Una campaña llama a una lista de contactos con un agente, dentro de un horario de llamadas y un límite de concurrencia (max_concurrency, hasta 100 llamadas a la vez, y nunca más que el límite de llamadas simultáneas de tu espacio de trabajo), y vuelve a intentarlo con las personas a las que no consiguió localizar.
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" }
]
}'
Los contactos también pueden ser texto CSV con el número de teléfono en la primera columna. consent_confirmed: true es obligatorio: con él confirmas que todas las personas de la lista aceptaron ser llamadas, también por una voz de IA, y EvaSay guarda esa confirmación con la campaña (consent_confirmed_at). La campaña se crea como borrador; inícialo con POST /api/v1/voice/campaigns/{id}/start, y usa pause o cancel del mismo modo. El agente debe estar publicado antes de que empiece una campaña.
Con voicemail_detection activado (el valor predeterminado), una llamada saliente espera a que el otro lado hable antes de que el agente salude; si guardan silencio, el agente saluda después de dos o tres segundos. Cuando el agente oye un buzón o una grabación de una operadora, espera a que terminen el saludo y su pitido, deja voicemail_message, o cuelga si no hay ninguno. Estas llamadas terminan con ended_reason voicemail, se cobran por el tiempo que duraron y no se analizan ni se notifican.
En una campaña, un número que llegó a un buzón se vuelve a llamar como uno que no contestó, y el mensaje se deja solo en el último intento, para que nadie reciba el mismo mensaje dos veces.
Toda llamada saliente, de una campaña o de POST /api/v1/voice/calls, comprueba primero la lista de no llamar del espacio de trabajo. Un número de la lista nunca se marca: la API responde 409 con code do_not_call, y una campaña marca el contacto como canceled con outcome do_not_call y pasa al siguiente.
Cuando la persona de una llamada realizada por el agente pide que no se la vuelva a llamar, el agente pone su número en la lista por sí mismo, se lo dice, y se envía el webhook call.do_not_call. Los números también se pueden añadir y quitar en Campañas → Lista de no llamar o con la 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" }'
Los números van en formato internacional; hasta 1000 a la vez. GET /api/v1/voice/do-not-call los lista, los más recientes primero (search, limit, page), cada uno con su source (call, dashboard o api). DELETE /api/v1/voice/do-not-call/%2B14155550100 quita uno de nuevo; hazlo solo cuando la persona haya aceptado ser llamada.
POST /api/v1/voice/voices crea una voz a partir de una grabación WAV de una sola persona (multipart audio, name, language, consent=true). Responde 202 con el estado running; consulta GET /api/v1/voice/voices hasta que sea ready, y después asigna el voice_id de la voz al voice_id de un agente. language es el idioma hablado en la grabación, cualquiera de los idiomas de agente indicados arriba; la voz habla todos ellos salvo el chino, donde responde una voz integrada del mismo género, y se parece más a la persona de la grabación en el idioma de la grabación. Cada voz clonada cuesta créditos, que el panel muestra antes de crearla.
Añade una dirección https en Integraciones → Webhooks y notificaciones para recibir eventos de llamada:
| Evento | Cuándo |
|---|---|
call.started | El agente contestó |
call.ended | La llamada terminó, con su duración y créditos |
call.analyzed | El resumen, el resultado, los campos extraídos y la transcripción están listos |
call.transcript | Durante la llamada, las líneas nuevas de la transcripción cada pocos segundos |
call.tool_called | El agente llamó a una de tus funciones: name, arguments, status, durationMs |
call.handed_off | La llamada se pasó a otro agente: fromAgentId, toAgentId |
call.transferred | La llamada se transfirió a una persona: destination |
call.voicemail | Una llamada saliente llegó a un buzón |
call.do_not_call | La persona de una llamada saliente pidió que no se la volviera a llamar: phone |
call.recording_ready | La grabación de dos canales está almacenada: recordingUrl (válido 24 horas), durationMs |
call.message_sent | El agente mostró tarjetas en la pantalla de quien llama por web o app: items, quickReplies |
alert.triggered | Una de tus alertas (Webhooks y notificaciones → Alertas) superó su umbral |
alert.resolved | Esa alerta ha vuelto a la normalidad |
Los eventos enviados durante una llamada llevan data.call (id, agentId, channel, direction, números). Cada solicitud va firmada:
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Verifica la firma con el secreto del webhook que se mostró al crearlo, y responde con un estado 2xx. Las entregas fallidas se reintentan tras 1 minuto, 5 minutos, 30 minutos, 2 horas y 12 horas con el mismo cuerpo y el mismo id EvaSay-Delivery, así que puedes ignorar las repeticiones. Las últimas entregas de un webhook, con el cuerpo exacto enviado y el estado que respondió tu endpoint, las lista GET /webhooks/{id}/deliveries y el panel; POST /webhooks/{id}/deliveries/{delivery_id} reenvía una.
Además del correo electrónico y de los webhooks alert.triggered / alert.resolved, las alertas pueden ir a un canal de Slack (incoming webhook), a un robot de grupo de WeCom o a Telegram en Webhooks y notificaciones → Alertas → Enviar también a.
Los errores usan un único formato:
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| Estado | Significado |
|---|---|
| 400 | Entrada no válida (code y param indican cuál) |
| 401 | Clave de API ausente o no válida |
| 402 | No hay créditos suficientes para iniciar la llamada |
| 403 | La clave es de solo lectura, o el agente no está disponible |
| 404 | No existe ese agente, llamada o campaña en este espacio de trabajo |
| 409 | Estado incorrecto (p. ej. iniciar una campaña en curso), una cuenta de proveedor no está conectada, o el número está en la lista de no llamar (do_not_call) |
| 429 | Demasiadas solicitudes; reduce el ritmo y vuelve a intentarlo |
Las solicitudes a la API de voz del espacio de trabajo de los últimos 30 días (hora, endpoint, estado, duración, clave y código de error; sin cuerpos) se listan en Claves de API → Registro de solicitudes a la API.
Las lecturas están limitadas a 120 solicitudes por minuto y clave; las escrituras y las llamadas, a 30.
Las llamadas se facturan por minuto con los créditos del espacio de trabajo, el mismo saldo que usa el panel. Las tarifas actuales están en la página de precios.
Usa el contrato OpenAPI para generar un cliente de integración. La API pública, el panel y el endpoint de gestión MCP operan sobre los mismos recursos y permisos del espacio de trabajo.
| Capacidad | Endpoint |
|---|---|
| Herramientas reutilizables | GET / POST /api/v1/voice/tools |
| Leer / actualizar / eliminar una herramienta | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Previsualizar o probar una herramienta | POST /api/v1/voice/tools/{id}/test |
| Vincular herramientas a un agente | PUT /api/v1/voice/agents/{id}/tools |
| Bases de conocimiento | GET / POST /api/v1/voice/knowledge-bases |
| Leer / actualizar / eliminar una base de conocimiento | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Añadir / listar documentos | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Leer / eliminar un documento | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Suscripciones a eventos de llamada | GET / POST /api/v1/voice/webhooks |
| Leer / actualizar / eliminar una suscripción | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Enviar un evento de prueba firmado | POST /api/v1/voice/webhooks/{id}/test |
| Descubrir herramientas MCP remotas | POST /api/v1/voice/mcp/discover |
| Endpoint de gestión MCP | /api/v1/voice/mcp |
Registra una herramienta HTTP que consulta tu 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 devuelve { "data": { "id": "TOOL_ID", ... } }. Vincula su definición a un agente con PUT /agents/AGENT_ID/tools y { "tool_ids": ["TOOL_ID"] }. Esto sustituye toda la lista de herramientas por instantáneas; vuelve a vincular cuando cambie una herramienta de la biblioteca. Eliminar una definición de la biblioteca conserva las instantáneas existentes. También puedes leer y sustituir definiciones en línea con el campo tools de la API de agentes. Usa analysis_config y widget_config para configurar el análisis y el widget mediante la API de agentes.
Una herramienta puede usar GET, POST, PUT, PATCH o DELETE. query y request_body aceptan plantillas JSON anidadas como { "customer": { "id": "{{customer_id}}" } }. Un marcador completo conserva el tipo JSON original. Los marcadores de la URL se codifican y solo pueden aparecer después de un host HTTPS público fijo. Sin request_body, las herramientas que no son GET reciben { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path selecciona un campo con notación de puntos. Los parameters simples se siguen aceptando; input_schema admite además objetos anidados, arrays, enumeraciones y límites numéricos. No se aceptan referencias a esquemas externos ni expresiones regulares.
static_args: valores enviados en cada llamada escriba lo que escriba el modelo, como un id de tienda; también rellenan {{name}} en la dirección y el cuerpo.extract: [{name, path}] conserva campos de la respuesta de una función (data.id) para el {{name}} de funciones posteriores, de modo que el customer_id de una consulta pueda pasar al pedido siguiente sin que el modelo lo repita. Los campos de una consulta de inicio se conservan igual.reject_when: hasta 5 reglas {on: caller|agent, pattern, negate}; mientras todas se cumplan en las últimas palabras, la función no se ejecuta y el agente recibe reject_message.model_says: en cada llamada, el modelo escribe una frase que se dice mientras la función se ejecuta, en lugar del speak_before fijo.Conecta una app en Integraciones → Webhooks y notificaciones → Apps conectadas y después usa «Añadir una función predefinida» en la pestaña Conocimiento y herramientas del agente; no necesitas ningún endpoint propio:
check_availability (horas libres en una fecha) y book_appointment (reserva la hora que eligió quien llama). Las horas de Google Calendar siguen la zona horaria, los días de apertura, el horario y la duración de cita que definas; Cal.com usa la disponibilidad propia del tipo de evento, y un correo de reserva que definas cuando quien llama no da ninguno.save_to_sheet, que añade una fila con las columnas que indiques, más el ID y la hora de la llamada.notify_team, que publica en un canal de Slack (incoming webhook) un mensaje que escribe el agente.El plano de control de EvaSay ejecuta estas funciones con la cuenta que conectaste; ni el worker ni la definición del agente guardan las credenciales de la app. Al leer un agente con la API, aparecen como kind: "app" (con app, action, connection_id, app_config); devuélvelas sin cambios al sustituir tools para conservarlas. Las apps se conectan solo desde el panel.
Define run_at_start: true en una de las herramientas de un agente y se llamará al empezar cada llamada, antes de que el agente salude o responda. Los parámetros llamados caller_number, called_number, direction, channel o call_id se rellenan por ti. Los campos que devuelve rellenan {{variables}} en first_message, system_prompt e idle_messages (los campos un nivel más abajo, como {{customer.name}}), y la respuesta completa se entrega al agente como contexto. Un saludo con variables espera hasta 3 segundos la respuesta; lo que no se encuentre queda en blanco. Solo una herramienta por agente puede ejecutarse al inicio.
Las credenciales se configuran en el entorno del servidor para tu espacio de trabajo y destino; pide a support@evasay.com que aprovisione un credential_ref. No pongas claves de API en las definiciones de agente, cabeceras, prompts ni parámetros de herramientas. Tu adaptador puede guardar las credenciales del CRM en su propio entorno.
Previsualiza una herramienta antes de hacer solicitudes al sistema de negocio:
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
Una vista previa valida los parámetros y muestra la solicitud resultante; no ejecuta una conversación ni contacta con el sistema de negocio. Define dry_run=false para una ejecución real. Una prueba de escritura real también exige confirmed=true e idempotency_key (8–128 letras, números, puntos, guiones bajos, dos puntos o guiones). Las respuestas de prueba incluyen ok, status, duration_ms y un resultado acotado. No devuelven credenciales. Las pruebas de lectura y escritura requieren permiso de escritura en el espacio de trabajo.
Durante una llamada, las solicitudes de negocio llevan un Idempotency-Key estable. Tu endpoint de negocio debe respetarlo. Las escrituras idénticas dentro de una llamada se deduplican, incluidas las operaciones con resultado desconocido tras un tiempo de espera agotado. Una operación distinta e intencionada necesita un argumento de negocio distinto, como operation_id. EvaSay no reproduce escrituras automáticamente. Los eventos de ejecución de herramientas incluyen estado y duración; la función de prueba de conversación sigue siendo una simulación de texto, no una prueba de ejecución de herramientas.
Configura una conexión MCP Streamable HTTP con:
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
En Claude Code, por ejemplo:
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
La conexión le indica a la herramienta cómo crear un agente: leer el catálogo, crear un borrador, añadir conocimiento, funciones y listas de palabras, escribir pruebas y ejecutarlas, corregir el borrador hasta que pasen, darle el dashboard_url del agente para probarlo, y publicar y asignar un número solo cuando usted lo apruebe. Las herramientas que leen documentación pueden empezar por https://evasay.com/llms.txt, que enlaza esta página, el contrato OpenAPI y la dirección MCP.
Usa el ajuste seguro de token o de variable de entorno del cliente. El endpoint usa autenticación por clave de API, no un flujo de inicio de sesión OAuth. Expone la creación y configuración de agentes, las herramientas de la biblioteca, el conocimiento, los webhooks y los registros de llamadas. Publicar y realizar llamadas reales son acciones de escritura; las llamadas salientes consumen créditos y tarifas del operador. Cada llamada a una herramienta vuelve a comprobar los permisos del espacio de trabajo. Activa los controles de confirmación de tu cliente para las acciones de escritura. El endpoint usa respuestas JSON sin estado; no ofrece un flujo de notificaciones en segundo plano.
Un ejemplo de instrucción para la herramienta de programación es: «Crea un agente de EvaSay en borrador, registra mi endpoint de consulta de CRM, previsualízalo con argumentos de prueba, vincúlalo al agente y configura un webhook call.analyzed». Revisa y autoriza explícitamente la publicación y las llamadas reales.
POST /mcp/discover acepta { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } y devuelve las herramientas y los esquemas del servidor. Registra solo la herramienta seleccionada:
{
"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 las herramientas registradas quedan disponibles para el agente. Las conexiones remotas admiten Streamable HTTP con respuestas JSON o SSE a las solicitudes, destinos HTTPS públicos y credenciales preconfiguradas. No se admiten los endpoints SSE heredados, la negociación OAuth, el muestreo ni la elicitación iniciados por el servidor, ni los adjuntos binarios. El descubrimiento no concede automáticamente acceso a todas las herramientas del servidor. El esquema de la herramienta remota debe estar en línea y usar el subconjunto admitido de JSON Schema.
Crea un webhook mediante POST /webhooks con { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }. La respuesta contiene la suscripción y un secreto de firma generado, devuelto solo al crearla. Verifica EvaSay-Signature, deduplica EvaSay-Delivery y actualiza tu CRM. Los webhooks de eventos son notificaciones asíncronas; las herramientas durante la llamada devuelven los resultados directamente al agente.