API работает с теми же агентами, звонками и кредитами, что и веб-панель. Всё, что вы собираете в панели, можно вести из собственных систем, и наоборот.
| Что | Endpoint |
|---|---|
| Список агентов | GET /api/v1/voice/agents |
| Создать агента | POST /api/v1/voice/agents |
| Получить / обновить агента | GET · PATCH /api/v1/voice/agents/{id} |
| Сделать исходящий звонок | POST /api/v1/voice/calls |
| Список звонков | GET /api/v1/voice/calls |
| Получить звонок (расшифровку) | GET /api/v1/voice/calls/{id} |
| Удалить звонок | DELETE /api/v1/voice/calls/{id} |
| Удалить звонки одного человека | POST /api/v1/voice/calls/delete-by-caller |
| Кампании | GET · POST /api/v1/voice/campaigns |
| Получить кампанию | GET /api/v1/voice/campaigns/{id} |
| Запустить / приостановить / отменить | POST /api/v1/voice/campaigns/{id}/{action} |
| Клонированные голоса | GET · POST /api/v1/voice/voices |
| Удалить клонированный голос | DELETE /api/v1/voice/voices/{id} |
| Список «Не звонить» | GET · POST /api/v1/voice/do-not-call |
| Убрать из списка | DELETE /api/v1/voice/do-not-call/{phone} |
| Языки, голоса, шаблоны | GET /api/v1/voice/catalog |
| Тесты агента | GET · POST /api/v1/voice/agents/{id}/tests |
| Запуск тестов / чтение прогона | POST /api/v1/voice/agents/{id}/test-runs · GET /api/v1/voice/test-runs/{id} |
| Назначить номер агенту | PATCH /api/v1/voice/phone-numbers/{id} |
| Словари | GET · POST /api/v1/voice/vocabularies |
Создайте ключ в разделе Интеграции → API-ключи панели (или одним нажатием на обзорной странице) и передавайте его как токен Bearer:
Authorization: Bearer sk-your-key
Ключ действует в рабочем пространстве, в котором работает его владелец, и с ролью этого человека: ключ участника с доступом только на чтение может читать, но ничего не менять.
Создайте агента из шаблона, при желании задав его поля в том же запросе. Без template агент создаётся пустым; GET /api/v1/voice/catalog перечисляет шаблоны, а также языки и голоса, которые можно задать агенту. Шаблоны написаны на английском и китайском, поэтому для другого языка передайте system_prompt и first_message на нём. Либо сохраняется весь запрос, либо агент не создаётся, так что отклонённый запрос можно отправить снова после исправления. dashboard_url в ответе — страница агента в панели, где можно поговорить с черновиком в браузере. Каждый агент работает на EvaSay ASR, EvaSay LLM и EvaSay TTS: EvaSay выбирает их для каждого звонка по языку и нагрузке, и они входят в цену звонка; аккаунты других провайдеров настраивать не нужно.
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..."
}'
Любое из этих полей обновляется через PATCH /api/v1/voice/agents/{id}:
| Поле | Примечания |
|---|---|
name, first_message, system_prompt | Текст |
status | published публикует черновик как новую версию; draft прекращает приём звонков |
language | Один из 27 языков, например en, zh, es, de, или multi, чтобы следовать языку звонящего |
temperature | 0-100 |
max_duration_sec, silence_timeout_sec | Ограничения звонка |
interruptible, end_call_enabled | Логические значения |
confirm_details | Логическое значение; агент повторяет вслух числа и имена и просит подтвердить |
ai_disclosure | Логическое значение, по умолчанию true; перед первым сообщением агент говорит на языке звонка, что звонящий разговаривает с ИИ (пропускается, если первое сообщение уже об этом говорит) |
voicemail_detection | Логическое значение; при исходящих звонках агент сначала слушает и завершает звонок, попав на автоответчик |
voicemail_message | Оставляется на автоответчике, может содержать {{variables}}; если не задано — агент вешает трубку |
transfer_number | Номер для перевода звонка, в международном формате |
transfer_destinations | До 9 дополнительных номеров, [{name, number, when}]; агент выбирает один по when |
transfer_briefing | Логическое значение; ответивший первым слышит краткое изложение звонка от агента |
pronunciations | До 100 [{word, say, case_sensitive}]; голос произносит say вместо word |
idle_messages | До 3 напоминаний, которые по очереди произносятся молчащему звонящему, прежде чем звонок завершится |
noise_reduction | Логическое значение; фоновый шум звонящего фильтруется, прежде чем его услышат |
recording_enabled | Логическое значение; звонки записываются в два канала (звонящий слева, агент справа) |
recording_consent | announce, ask (запись сохраняется, только если звонящий согласен) или none |
recording_notice | Уведомление или вопрос, произносимые первыми; пусто — встроенный текст на языке звонка |
handoffs | До 5 [{agent_id, when}]: другие агенты, которым этот может передать звонок |
knowledge_base_id | Одна из ваших баз знаний |
vocabulary_id | Один из ваших словарей; его термины распознаются надёжнее, когда их произносит звонящий |
voice_id | Идентификатор встроенного голоса EvaSay или один из ваших клонированных голосов (clone:…) |
Встроенные голоса зависят от языка: у китайского есть ming-designed-female-clear и ming-designed-female-warm (женские), ming-designed-male-clear и ming-designed-male-friendly (мужские), у остальных языков — F1–F5 (женские) и M1–M5 (мужские). Встроенный голос, который не говорит на языке агента, заменяется голосом того же пола, который на нём говорит. Прежнее имя поля tts_voice_id по-прежнему принимается. Любое другое значение voice_id, а также клонированный голос, который ещё не готов, отклоняется.
analysis_config задаёт, что извлекается из каждого звонка после его окончания: summary (по умолчанию true), summaryLanguage, successCriteria (чего достигает успешный звонок), fields (до 20 {name, type, description} для извлечения, где type — string, number или boolean) и scorecard (до 10 {name, description, weight}, вес 1–5). Результаты — в analysis звонка и в вебхуке call.analyzed.
Изменение полей агента меняет его черновик. Звонки продолжают использовать опубликованную версию, пока вы не опубликуете черновик через POST /api/v1/voice/agents/{id}/publish (при желании с note) или с status: "published" в том же PATCH. Каждая публикация сохраняет пронумерованную версию; published_version в агенте показывает, какую версию используют звонки. Звонок остаётся на версии, которая была активна в момент его начала, поэтому публикация во время звонка его не меняет. Тестовые звонки из панели используют черновик.
GET /agents/{id}/versions выводит список версий, а POST /agents/{id}/versions/{version}/restore откатывает: новые звонки используют эту версию, и черновик становится ею. Название агента, развёртывание, внешний вид виджета и уведомления не версионируются и меняются сразу, как и документы его базы знаний.
Тест разыгрывает один звонок в виде текста: имитированный звонящий играет scenario, агент отвечает по своему черновику (инструкции, первое сообщение и знания), а проверяющий оценивает разговор по success_criteria. Пишите тесты для важных звонков, запускайте их после каждого изменения и публикуйте, когда они проходят.
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 запускает все тесты агента или те, что в test_ids, и отвечает одним прогоном на тест (status: "running"). Опрашивайте GET /test-runs/{id}, пока status не станет passed, failed или error; тогда в прогоне есть transcript и reason проверяющего (на английском или на китайском с reason_language: "zh"). Каждый прогон стоит Credits — сумма показана рядом с тестами в панели; прогоны останавливаются на первом, который не оплачивает баланс, а not_started перечисляет остальные. Прогон, который не удалось провести, возвращается. Тесты проверяют, что говорит агент, а не голос или темп, и не вызывают его функции: их проверьте заранее через POST /tools/{id}/test. У агента до 30 тестов; PATCH и DELETE /agents/{id}/tests/{test_id} меняют или удаляют тест.
Кроме целых звонков (type: "simulation", по умолчанию), тест может проверять один ответ. type: "next_reply" принимает chat_history — предшествующий разговор, который заканчивается словами звонящего ([{role, text}], где role — caller или agent), и success_criteria для следующего ответа агента. type: "tool_call" принимает chat_history и expected_tool {name, arguments, match}: агент должен вызвать name (одну из своих функций или end_call, transfer_call, handoff_to_agent) с arguments, которые сравниваются точно (без пробелов по краям и без учёта регистра) или, с match: "judge", по смыслу; остальные переданные аргументы не проверяются. transcript прогона показывает в tool_calls функции, которые вызвал агент.
POST /agents/{id}/tests/generate предлагает тесты по черновику агента: его инструкциям, знаниям и функциям. Принимает count (1–10, по умолчанию 5), types, focus (что охватить) и language (en или zh, для названий и критериев), стоит Credits как один прогон и возвращает черновики, которые нужно просмотреть и отправить в POST /agents/{id}/tests; с save: true они сохраняются.
hooks что-то делает в определённый момент звонка, до 10 правил: on: "silence" (звонящий молчит seconds, 3–600, с тех пор как агент закончил; один раз, пока звонящий снова не заговорит), on: "time_elapsed" (через seconds после начала звонка, 10–3600) или on: "call_ending". Каждое правило выполняет (do) до 3 действий из {type: "say", text}, {type: "transfer", destination?}, {type: "end_call", text?} и {type: "tool", name, args?}; правило на завершение звонка запускает только функции. У звонка, завершённого правилом, ended_reason равен rule_ended.
В телефонных звонках нажатые звонящим клавиши доходят до агента как ответ: ввод заканчивается на # или после двухсекундной паузы. В исходящих звонках агент может и сам нажимать клавиши, чтобы пройти телефонное меню компании.
transfer_number — основной номер агента для передачи звонящего человеку и номер, на который звонят, когда в рабочем пространстве закончились кредиты. transfer_destinations добавляет именованные номера, у каждого есть пометка, когда его использовать («Бухгалтерия: вопросы по счетам и возвратам»), и агент выбирает. При включённом transfer_briefing агент во время перевода пишет краткую сводку; ответивший человек слышит её голосом EvaSay и на языке сводки агента, прежде чем звонящего соединят. ended_reason звонка тогда равен transferred.
С помощью handoffs агент может передать звонок другому опубликованному агенту вашего рабочего пространства, например администратор передаёт запись агенту по записи. Агент выбирает по when. Другой агент принимает тот же звонок со всем, что было сказано, и говорит со своими инструкциями, голосом, знаниями, инструментами и передачами. Звонок остаётся в списке, анализируется и тарифицируется на первого агента; в его расшифровке отмечена каждая передача.
Номера подключаются в панели через ваш собственный аккаунт оператора. GET /api/v1/voice/phone-numbers перечисляет их, а PATCH /api/v1/voice/phone-numbers/{id} с { "agent_id": "AGENT_ID" } направляет звонки номера агенту (его опубликованной версии), с "agent_id": null — никому; label переименовывает номер.
Словарь содержит названия и термины, которые произносят звонящие и которые распознавание речи иначе услышало бы неверно: названия продуктов, лекарств или улиц. POST /api/v1/voice/vocabularies с { "name": "Products", "terms": ["Lumora", "Vantix Pro"] } (до 1000 терминов по 50 символов, или "preset": "medical" либо "legal" для начала), затем задайте vocabulary_id агента. GET, PATCH (name, terms) и DELETE /vocabularies/{id}; словарь, который использует агент, удалить нельзя.
У каждого номера есть правила звонков на странице «Номера телефонов», они применяются до того, как ответит агент: заблокированные номера или начала номеров (+1555) и звонящие, скрывающие номер, получают отказ без ответа и ничего не стоят; звонящих из списка можно сразу соединить с другим номером; а когда агент не может ответить (не опубликован, нет воркера, закончились кредиты и нет номера для перевода), звонок может пойти на номер или воспроизвести ваше собственное сообщение. Начальный запрос (run_at_start) тоже может отклонить звонящего, ответив {"reject": true, "message": "..."}: агент произносит сообщение, если оно есть, и вешает трубку, а ended_reason равен rejected.
Сделайте один звонок с одного из телефонных номеров вашего рабочего пространства. Каждый запрос требует уникальный Idempotency-Key; повторная отправка того же ключа возвращает тот же звонок, а не набирает номер дважды.
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 обязателен, если в рабочем пространстве больше одного номера телефона. variables подставляются в заполнители вида {{customer_name}} в первом сообщении и промпте агента.
GET /api/v1/voice/calls/{id} возвращает статус звонка, длительность, списанные кредиты, краткое содержание, анализ и расшифровку. Список звонков — GET /api/v1/voice/calls?agent_id=&status=&channel=&user_id=&page= (user_id: ваш собственный идентификатор человека из токена веб-звонка). Его turns — пошаговый отладочный журнал звонка: куда ушло время от конца реплики звонящего до момента, когда он услышал ответ, в миллисекундах — waitMs (конец речи), sttMs (распознавание), llmMs (первое слово модели, плюс по одному на каждый раунд функций), ttsMs (первый звук голоса), firstAudioMs (ответ услышан) — с вызванными функциями, перебиваниями и ошибками. Его показывает и страница звонка.
С recording_enabled каждый звонок записывается как двухканальный WAV: звонящий слева, агент справа, с частотой дискретизации звонка. При recording_consent: "announce" агент сначала сообщает, что звонок записывается; при "ask" — спрашивает, и запись сохраняется, только если звонящий согласен (отсутствие ответа считается отказом); при "none" ничего не говорится. Уточните, что требует закон там, где находитесь вы и ваши абоненты.
Записи хранятся в хранилище EvaSay или в вашем собственном бакете, совместимом с S3 (AWS S3, Cloudflare R2, Google Cloud Storage, Supabase, MinIO), который задаётся в Интеграции → Вебхуки и уведомления → Хранилище записей. Воркер, ведущий звонок, загружает файл прямо в бакет. GET /api/v1/voice/calls/{id} возвращает recordingUrl, действительный один час после сохранения записи, и recording.status (recording, stored, declined или failed).
DELETE /api/v1/voice/calls/{id} удаляет завершённый звонок вместе с расшифровкой, записью (в хранилище EvaSay или в вашем бакете), анализом и отправками вебхуков. Потраченные Credits остаются в истории кредитов. Звонок, который пока нельзя удалить, отвечает 409 с code call_not_deletable_yet: он ещё идёт или только что завершился, ещё оплачивается, запись, возможно, ещё загружается, или по нему есть долг в Credits, который не покрыл баланс (сначала пополните Credits).
Когда человек просит удалить его данные, POST /api/v1/voice/calls/delete-by-caller с { "phone_number": "+14155550123" } удаляет все звонки с этим номером (у входящих — номер звонившего, у исходящих — набранный номер), а с { "user_id": "..." } — все веб-звонки с этим user_id в токене. Ответ: { "deleted": 12, "kept_back": { "settling": 1 }, "more": false }. За один запрос удаляется не более 200 звонков, поэтому повторяйте, пока more равно true, а звонки из kept_back удалите позже. Если номер есть в списке «Не звонить», он там останется. В панели то же самое делает Звонки → Удалить данные звонившего.
Отправьте POST /api/v1/voice/calls/{id}/control, пока звонок идёт. Воркер, ведущий звонок, подхватывает команду примерно за 2 секунды; API отвечает 202. Если воркер, ведущий звонок, недоступен, API отвечает 503 (code call_unreachable), и команда не выполняется.
type | Что происходит |
|---|---|
say | Агент произносит text после текущей фразы или сразу, если указано "interrupt": true |
note | Агенту сообщается text (не произносится), и он учитывает это со следующего ответа; в расшифровке это отмечается |
end | Агент произносит text, если он задан, и вешает трубку; телефонный звонок без text завершается сразу |
ended_reason звонка тогда равен api_ended. На странице звонка в панели есть те же элементы управления.
POST /api/v1/voice/calls/{id}/listen (при желании { "channels": 2 }) возвращает listen_url, которую можно открыть в течение двух минут. Это WebSocket: сначала JSON-сообщение {"type":"start","sampleRate":8000,"channels":1,"encoding":"pcm_s16le"}, затем бинарные кадры 16-битного little-endian PCM с тем, что слышит звонящий (оба голоса смешаны; при channels: 2 звонящий слева, агент справа). Он закрывается, когда звонок завершается. Звук идёт прямо от воркера, ведущего звонок, минуя плоскость управления EvaSay; кнопка «Слушать» на странице звонка в панели использует тот же адрес.
Отправляйте и принимайте аудио звонка из собственной программы, например своей телефонной системы или приложения. Начните звонок:
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 }'
websocket_url в ответе содержит одноразовый токен и может быть открыт в течение 15 минут. encoding — это pcm_s16le (16-битный little-endian PCM с sample_rate 8000, 16000 или 24000) или mulaw (μ-law, 8 кГц). Аудио идёт в обе стороны бинарными кадрами в этом формате, в идеале по 20 мс. Текстовые кадры — JSON:
| Направление | Сообщения |
|---|---|
| Вы → EvaSay | {"type":"dtmf","digits":"12#"} нажатия клавиш; {"type":"end"} — повесить трубку; {"type":"text","text":"…"} слова, набранные звонящим ("input":"button" для выбранного ответа); {"type":"context","text":"…","key":"cart"} то, о чём сообщает ваше приложение (агент на это не отвечает); {"type":"tool_result","id":"…","result":{…}} (или "error") ответ функции страницы |
| EvaSay → вы | start (с callId); clear (звонящий перебил: отбросьте ещё не воспроизведённое аудио агента); transcript (role, text, final и input для набранных слов); user_started_speaking, user_stopped_speaking, agent_started_speaking, agent_stopped_speaking; cards (cards, quick_replies); tool (id, name, arguments: выполните одну из ваших функций страницы); notice (code: too_fast или limit для набранных слов); ended (с reason, затем сокет закрывается) |
Агент здоровается, как только вы подключились. В запрос можно добавить user_id и page_tools (функции страницы, на которые отвечает ваше приложение, см. ниже). Звонки тарифицируются поминутно, как и любые другие, с теми же записями звонков, анализом и вебхуками.
Во время веб-звонка (виджет, размещённая страница, ваше приложение) звонящий видит чат рядом с голосом, поэтому агент может не только говорить. У звонящих по телефону этого нет.
Включите Агент → Инструменты → Отправить абоненту (send_to_caller в API агента), и агент сможет выводить на экран звонящего до пяти карточек одновременно: заголовок, строку под ним, описание, картинку, ссылку и до двух кнопок ответа в каждой; он также может предложить до трёх быстрых ответов под вопросом. Ссылки никогда не зачитываются вслух.
Агент может отправлять только те ссылки и картинки, которые были ему даны в этом звонке: его инструкции и приветствие, база знаний (у документа, прочитанного с веб-страницы, есть её адрес), ответы ваших функций и готовые карточки. Кроме того, ссылка должна вести на один из allowed_hosts (собственный сайт готовой карточки разрешён всегда). Ссылки, которые агент выдумал или которые звонящий продиктовал вслух, не отправляются. Ссылки на эти сайты в словах агента в чате кликабельны.
"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": "" }
]
}
Каждая отправка приходит вам как вебхук call.message_sent (channel, items с title и url, quickReplies, blocked). За звонок отправка происходит не более 10 раз.
Во время звонка звонящий может также вводить текст (адрес электронной почты, номер заказа) или нажимать кнопку ответа; агент отвечает на это как на речь, предварительно замолчав, если говорил. До 500 символов в сообщении, одно в секунду, 100 за звонок. В записи звонка такие реплики помечены как набранные или выбранные нажатием.
Виджет даёт вашей странице объект evasay. Всё необязательно, а кадр звонка слушает вашу страницу, только если она входит в разрешённые сайты виджета (Агент → Развёртывание):
<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 подставляются в {{name}} в инструкциях и приветствии. Они приходят из браузера посетителя, поэтому агенту сообщается, что они не проверены, и они никогда не попадают в ваши функции и начальный запрос.update сообщает агенту, что происходит на странице, не заставляя его отвечать; более новое обновление с тем же key заменяет предыдущее."kind": "page", без url) и зарегистрируйте на своей странице функцию с тем же именем. Агент может вызвать её только в звонках со страницы, которая её зарегистрировала; он ждёт ответа до timeout_sec (1–10, по умолчанию 5), если только wait_for_result не равно false. Она выполняется от имени посетителя, как его собственный клик: запрашивайте подтверждение на своей странице перед всем, что стоит денег или необратимо. Ссылки из её ответа могут отправляться как карточки.Установите Кто может начать звонок в Только с токеном и пусть ваш сервер получает одноразовый токен для вошедшего пользователя:
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 }'
В ответе есть token (на один звонок, в течение expires_in секунд, 30–3600, по умолчанию 600), agent (слаг) и web_call_url. Передайте токен странице через data-token-url="/your/endpoint" в скрипте виджета (он отправляет туда POST с куки посетителя и читает { "token": "…" }) или через agent.setToken(async () => token). variables токена проверены: их могут использовать инструкции, начальный запрос и шаблоны ваших функций, как и user_id, который звонок сохраняет (userId в звонке, user_id для фильтрации звонков). Храните свой API-ключ на сервере.
В собственном приложении отправьте POST { "token": "…", "pageTools": ["add_to_cart"] } на https://evasay.com + web_call_url. В ответе — соединение звонка (адрес WebRTC-предложения или адрес WebSocket с токеном звонка) для клиента RTVI, например клиентских SDK Pipecat для веба, iOS, Android и React Native. Карточки, функции страницы и уведомления приходят как RTVI server-message (evasay.cards, evasay.tool, evasay.notice); набранные слова отправляйте через send-text, а сообщения и ответы страницы — как client-message evasay.context и evasay.tool_result. Либо передавайте аудио сами через Аудио через WebSocket, указав user_id и page_tools.
В разделе Номера телефонов → SIP ваша собственная АТС, SIP-телефоны или SIP-транк могут звонить вашим агентам напрямую; звонки тарифицирует ваш SIP-провайдер:
sip:ev…@<EvaSay's SIP domain>, только входящие звонки, для АТС (3CX, FreePBX, Asterisk…) и SIP-телефонов.from); переводы на человека передаются транку через REFER. Когда транк запрашивает учётные данные, EvaSay отвечает именем пользователя и паролем транка, которые вы указали.Входящие звонки аутентифицируются по digest с именем пользователя и паролем, которые выдаёт EvaSay (realm — SIP-домен); можно также ограничить IP-адреса источников или принимать только по IP, когда ваш провайдер не может авторизоваться (тогда IP обязательны). У каждого SIP-адреса или транка есть лимит одновременных звонков; сверх него звонки получают 486. Пароли показываются один раз — при создании или сбросе. Только UDP и G.711 (PCMU, PCMA); нажатия клавиш по RFC 4733 или SIP INFO; отключите регистрацию (REGISTER) на транке вашей АТС.
Кампания обзванивает список контактов одним агентом в пределах часов обзвона и лимита параллельности (max_concurrency, до 100 звонков одновременно и никогда не больше лимита одновременных звонков вашего рабочего пространства) и повторяет попытки для тех, до кого не дозвонилась.
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" }
]
}'
Контакты можно передать и CSV-текстом с номером телефона в первом столбце. consent_confirmed: true обязателен: им вы подтверждаете, что все из списка согласились на звонки, в том числе голосом ИИ, и EvaSay хранит это подтверждение вместе с кампанией (consent_confirmed_at). Кампания создаётся как черновик; запустите её через POST /api/v1/voice/campaigns/{id}/start, а pause и cancel используйте так же. Перед запуском кампании агент должен быть опубликован.
При включённом voicemail_detection (по умолчанию) исходящий звонок ждёт, пока другая сторона заговорит, прежде чем агент поздоровается; если они молчат, агент здоровается через две–три секунды. Когда агент слышит автоответчик или запись оператора связи, он ждёт конца приветствия и сигнала, оставляет voicemail_message или вешает трубку, если сообщения нет. Такие звонки завершаются с ended_reason voicemail, тарифицируются по фактической длительности и не анализируются и не рассылаются в уведомлениях.
В кампании номер, попавший на автоответчик, набирается снова, как не ответивший, а сообщение оставляется только при последней попытке, чтобы никто не получил одно и то же сообщение дважды.
Каждый исходящий звонок — из кампании или через POST /api/v1/voice/calls — сначала проверяет список «Не звонить» рабочего пространства. Номер из списка никогда не набирается: API отвечает 409 с code do_not_call, а кампания помечает контакт как canceled с outcome do_not_call и переходит к следующему.
Когда человек в звонке, сделанном агентом, просит больше ему не звонить, агент сам вносит его номер в список, сообщает ему об этом, и отправляется вебхук call.do_not_call. Номера можно также добавлять и удалять в Кампании → Список «Не звонить» или через 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" }'
Номера указываются в международном формате; до 1000 за раз. GET /api/v1/voice/do-not-call выводит их, сначала новые (search, limit, page), у каждого есть source (call, dashboard или api). DELETE /api/v1/voice/do-not-call/%2B14155550100 снова убирает номер из списка; делайте это, только когда человек согласился на звонки.
POST /api/v1/voice/voices создаёт голос из WAV-записи одного говорящего (multipart audio, name, language, consent=true). Он отвечает 202 со статусом running; опрашивайте GET /api/v1/voice/voices, пока статус не станет ready, затем задайте voice_id агента равным voice_id голоса. language — язык, на котором говорят в записи, любой из языков агентов, перечисленных выше; голос говорит на всех, кроме китайского, где отвечает встроенный голос того же пола, и больше всего похож на говорящего из записи на языке записи. Каждый клонированный голос стоит кредитов, которые панель показывает до его создания.
Добавьте https-адрес в Интеграции → Вебхуки и уведомления, чтобы получать события звонков:
| Событие | Когда |
|---|---|
call.started | Агент ответил на звонок |
call.ended | Звонок завершился; указаны длительность и кредиты |
call.analyzed | Готовы краткое содержание, итог, извлечённые поля и расшифровка |
call.transcript | Во время звонка, новые строки расшифровки каждые несколько секунд |
call.tool_called | Агент вызвал одну из ваших функций: name, arguments, status, durationMs |
call.handed_off | Звонок передан другому агенту: fromAgentId, toAgentId |
call.transferred | Звонок переведён на человека: destination |
call.voicemail | Исходящий звонок попал на автоответчик |
call.do_not_call | Человек в исходящем звонке попросил больше ему не звонить: phone |
call.recording_ready | Двухканальная запись сохранена: recordingUrl (действителен 24 часа), durationMs |
call.message_sent | Агент показал карточки на экране веб- или приложенческого звонящего: items, quickReplies |
alert.triggered | Одно из ваших оповещений (Вебхуки и уведомления → Оповещения) превысило порог |
alert.resolved | Это оповещение вернулось в норму |
События, отправляемые во время звонка, содержат data.call (id, agentId, channel, direction, номера). Каждый запрос подписан:
EvaSay-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
EvaSay-Delivery: <delivery id>
Проверяйте подпись секретом вебхука, показанным при его создании, и отвечайте статусом 2xx. Неудачные доставки повторяются через 1 минуту, 5 минут, 30 минут, 2 часа и 12 часов с тем же телом и тем же идентификатором EvaSay-Delivery, поэтому повторы можно игнорировать. Последние доставки вебхука с точным отправленным телом и статусом, который ответил ваш endpoint, перечисляются через GET /webhooks/{id}/deliveries и в панели; POST /webhooks/{id}/deliveries/{delivery_id} отправляет одну из них снова.
Помимо электронной почты и вебхуков alert.triggered / alert.resolved, оповещения можно отправлять в канал Slack (incoming webhook), групповой бот WeCom или Telegram в Вебхуки и уведомления → Оповещения → Также отправлять в.
Ошибки имеют единый формат:
{
"error": {
"type": "invalid_request_error",
"code": "invalid_agent_field",
"message": "status must be draft or published",
"param": "status"
}
}
| Статус | Значение |
|---|---|
| 400 | Некорректные данные (code и param указывают, какие именно) |
| 401 | API-ключ отсутствует или недействителен |
| 402 | Недостаточно кредитов для начала звонка |
| 403 | Ключ только для чтения или агент недоступен |
| 404 | В этом рабочем пространстве нет такого агента, звонка или кампании |
| 409 | Неверное состояние (например, запуск уже идущей кампании), аккаунт провайдера не подключён или номер в списке «Не звонить» (do_not_call) |
| 429 | Слишком много запросов; снизьте частоту и повторите |
Запросы рабочего пространства к голосовому API за последние 30 дней (время, endpoint, статус, длительность, ключ и код ошибки; без тел) перечислены в API-ключи → Журнал запросов API.
Чтение ограничено 120 запросами в минуту на ключ, запись и звонки — 30.
Звонки тарифицируются поминутно за счёт кредитов рабочего пространства — того же баланса, что используется в панели. Актуальные тарифы — на странице цен.
Используйте контракт OpenAPI, чтобы сгенерировать клиент для интеграции. Публичный API, панель и управляющий endpoint MCP работают с одними и теми же ресурсами и правами рабочего пространства.
| Возможность | Endpoint |
|---|---|
| Многоразовые инструменты | GET / POST /api/v1/voice/tools |
| Прочитать / обновить / удалить инструмент | GET / PATCH / DELETE /api/v1/voice/tools/{id} |
| Предпросмотр или проверка инструмента | POST /api/v1/voice/tools/{id}/test |
| Привязать инструменты к агенту | PUT /api/v1/voice/agents/{id}/tools |
| Базы знаний | GET / POST /api/v1/voice/knowledge-bases |
| Прочитать / обновить / удалить базу знаний | GET / PATCH / DELETE /api/v1/voice/knowledge-bases/{id} |
| Добавить / вывести список документов | POST / GET /api/v1/voice/knowledge-bases/{id}/documents |
| Прочитать / удалить документ | GET / DELETE /api/v1/voice/knowledge-bases/{id}/documents/{doc_id} |
| Подписки на события звонков | GET / POST /api/v1/voice/webhooks |
| Прочитать / обновить / удалить подписку | GET / PATCH / DELETE /api/v1/voice/webhooks/{id} |
| Отправить подписанное тестовое событие | POST /api/v1/voice/webhooks/{id}/test |
| Обнаружить удалённые инструменты MCP | POST /api/v1/voice/mcp/discover |
| Управляющий endpoint MCP | /api/v1/voice/mcp |
Зарегистрируйте HTTP-инструмент, который обращается к вашему 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 возвращает { "data": { "id": "TOOL_ID", ... } }. Привяжите его определение к агенту через PUT /agents/AGENT_ID/tools с { "tool_ids": ["TOOL_ID"] }. Это заменяет весь список инструментов снимками; привяжите заново, когда инструмент библиотеки изменится. Удаление определения из библиотеки сохраняет существующие снимки. Встроенные определения можно также читать и заменять через поле tools API агента. Через API агента задавайте analysis_config и widget_config для настройки анализа и виджета.
Инструмент может использовать GET, POST, PUT, PATCH или DELETE. query и request_body принимают вложенные JSON-шаблоны вроде { "customer": { "id": "{{customer_id}}" } }. Заполнитель, занимающий всё значение, сохраняет исходный тип JSON. Заполнители в URL кодируются и могут стоять только после фиксированного публичного HTTPS-хоста. Без request_body инструменты, отличные от GET, получают { "arguments": {...}, "call": { "callId": "...", "agentId": "...", "toolCallId": "...", "idempotencyKey": "..." } }. response_path выбирает поле в точечной нотации. Простые parameters по-прежнему принимаются; input_schema дополнительно поддерживает вложенные объекты, массивы, перечисления и числовые границы. Внешние ссылки на схемы и регулярные выражения не принимаются.
static_args: значения, отправляемые с каждым вызовом независимо от того, что напишет модель, например идентификатор магазина; они также подставляются в {{name}} в адресе и теле.extract: [{name, path}] сохраняет поля из ответа функции (data.id) для {{name}} в последующих функциях, так что customer_id из поиска можно передать в следующий заказ, не заставляя модель повторять его. Поля начального запроса сохраняются так же.reject_when: до 5 правил {on: caller|agent, pattern, negate}; пока все они выполняются для последних слов, функция не запускается, а агент получает reject_message.model_says: при каждом вызове модель пишет одну фразу, которая произносится, пока выполняется функция, вместо фиксированного speak_before.Подключите приложение в Интеграции → Вебхуки и уведомления → Подключённые приложения, затем используйте «Добавить готовую функцию» на вкладке «Знания и инструменты» агента; собственный endpoint не нужен:
check_availability (свободное время на дату) и book_appointment (записывает на время, выбранное звонящим). Время в Google Calendar следует заданным вами часовому поясу, рабочим дням, часам и длительности приёма; Cal.com использует собственную доступность типа события и запасной адрес электронной почты, который вы зададите, если звонящий его не назвал.save_to_sheet, которая дописывает строку с перечисленными вами столбцами, а также ID и временем звонка.notify_team, которая публикует в канале Slack (incoming webhook) сообщение, написанное агентом.Плоскость управления EvaSay выполняет эти функции с подключённым вами аккаунтом; ни воркер, ни определение агента не хранят учётные данные приложения. При чтении агента через API они отображаются как kind: "app" (с app, action, connection_id, app_config); отправляйте их обратно без изменений при замене tools, чтобы сохранить. Приложения подключаются только в панели.
Задайте run_at_start: true одному из инструментов агента, и он будет вызван при начале каждого звонка, до того как агент поздоровается или ответит. Параметры с именами caller_number, called_number, direction, channel или call_id заполняются для него автоматически. Возвращаемые им поля подставляются в {{variables}} в first_message, system_prompt и idle_messages (поля на уровень глубже — как {{customer.name}}), а весь ответ передаётся агенту как справочная информация. Приветствие с переменными ждёт ответа до 3 секунд; всё, что не найдено, остаётся пустым. При старте может запускаться один инструмент на агента.
Учётные данные настраиваются в окружении сервера для вашего рабочего пространства и адреса назначения; попросите support@evasay.com выделить credential_ref. Не помещайте API-ключи в определения агентов, заголовки, промпты и параметры инструментов. Ваш адаптер может хранить учётные данные CRM в собственном окружении.
Просмотрите инструмент, прежде чем отправлять запросы в бизнес-систему:
{
"arguments": { "customer_id": "customer-123" },
"dry_run": true,
"mock_result": { "name": "Alex", "order_status": "shipped" }
}
Предпросмотр проверяет параметры и показывает сформированный запрос; он не ведёт разговор и не обращается к бизнес-системе. Установите dry_run=false для реального выполнения. Реальная проверка записи также требует confirmed=true и idempotency_key (8–128 букв, цифр, точек, подчёркиваний, двоеточий или дефисов). Ответы тестов содержат ok, status, duration_ms и ограниченный по размеру результат. Учётные данные они не возвращают. Тесты чтения и записи требуют права записи в рабочем пространстве.
Во время звонка бизнес-запросы несут стабильный Idempotency-Key. Ваш бизнес-endpoint должен его соблюдать. Одинаковые операции записи в рамках звонка дедуплицируются, включая операции с неизвестным исходом после тайм-аута. Другая намеренная операция требует отдельного бизнес-аргумента, например operation_id. EvaSay не повторяет операции записи автоматически. События выполнения инструментов содержат статус и длительность; функция проверки разговора остаётся текстовой симуляцией, а не проверкой выполнения инструментов.
Настройте подключение MCP Streamable HTTP с параметрами:
URL: https://evasay.com/api/v1/voice/mcp
Authorization: Bearer <your EvaSay API key>
Например, в Claude Code:
claude mcp add --transport http evasay https://evasay.com/api/v1/voice/mcp --header "Authorization: Bearer $EVASAY_API_KEY"
Подключение объясняет инструменту, как собрать агента: прочитать каталог, создать черновик, добавить знания, функции и словари, написать и запустить тесты, править черновик, пока они не пройдут, дать вам dashboard_url агента, чтобы его попробовать, а публиковать и назначать номер только после вашего одобрения. Инструменты, читающие документацию, могут начать с https://evasay.com/llms.txt — там ссылки на эту страницу, контракт OpenAPI и адрес MCP.
Используйте безопасную настройку клиента для токена или переменной окружения. Endpoint использует аутентификацию по API-ключу, а не вход через OAuth. Он предоставляет создание и настройку агентов, инструменты библиотеки, знания, вебхуки и записи звонков. Публикация и реальные звонки — операции записи; исходящие звонки расходуют кредиты и оплату оператора связи. Каждый вызов инструмента заново проверяет права в рабочем пространстве. Включите в клиенте подтверждение для операций записи. Endpoint использует JSON-ответы без состояния; потока фоновых уведомлений он не предоставляет.
Пример инструкции для инструмента разработки: «Создай черновик агента EvaSay, зарегистрируй мой endpoint поиска в CRM, просмотри его с тестовыми аргументами, привяжи к агенту и настрой вебхук call.analyzed». Проверьте и явно разрешите публикацию и реальные звонки.
POST /mcp/discover принимает { "url": "https://crm.example.com/mcp", "credential_ref": "crm" } и возвращает инструменты и схемы сервера. Регистрируйте только выбранный инструмент:
{
"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"]
}
}
Агенту доступны только зарегистрированные инструменты. Удалённые подключения поддерживают Streamable HTTP с ответами JSON или SSE на запросы, публичные HTTPS-адреса и заранее настроенные учётные данные. Устаревшие SSE-endpoint, согласование OAuth, инициируемые сервером sampling и elicitation и бинарные вложения не поддерживаются. Обнаружение не даёт автоматически доступ ко всем инструментам сервера. Схема удалённого инструмента должна быть встроенной и использовать поддерживаемое подмножество JSON Schema.
Создайте вебхук через POST /webhooks с { "url": "https://crm.example.com/evasay-events", "events": ["call.analyzed"] }. В ответе — подписка и сгенерированный секрет подписи, возвращаемый только при создании. Проверяйте EvaSay-Signature, дедуплицируйте по EvaSay-Delivery и обновляйте свой CRM. Вебхуки событий — это асинхронные уведомления; инструменты во время звонка возвращают результаты напрямую агенту.