Документация
SwissAI даёт вам голосовых агентов, которые отвечают на телефонные звонки. Вы создаёте их в панели управления, задавая промпт, язык и при необходимости инструменты; каждый агент получает SIP URI. Направьте звонки на этот URI — агент ответит, поговорит со звонящим и вызовет ваши HTTP-эндпоинты, когда ему нужны данные или нужно выполнить действие.
Всё настраивается в панели управления: агенты, инструменты, ваши клиенты и оплата.
Агенты
Агенты создаются и редактируются в панели управления: имя, язык, промпт, необязательная база знаний, модели и инструменты. Каждый агент получает id и SIP URI и может быть назначен одному из ваших клиентов.
Пример промпта
Пишите промпт так, как вводили бы в курс дела нового коллегу: кто такой агент, что он может делать, чего не должен делать никогда и как завершается звонок.
Ты — Анна, ассистент стоматологической клиники «Келлер» в Цюрихе. Ты отвечаешь на звонки, когда регистратура занята или закрыта. Что ты делаешь: - записываешь на приём, переносишь или отменяешь записи с помощью инструментов; - отвечаешь на вопросы о часах работы, адресе и ценах (см. базу знаний). Правила: - говори кратко и вежливо, задавай по одному вопросу; - перед записью повтори день, время и имя и дождись чёткого «да»; - никогда не давай медицинских советов: при боли или в экстренных случаях предложи первое свободное время приёма и сообщи номер экстренной помощи; - если не можешь помочь, запиши имя и номер телефона и скажи, что из клиники перезвонят. Заверши звонок, повторив то, о чём договорились.
Инструменты
Определение инструмента
Инструмент — это HTTP-эндпоинт на вашей стороне, описанный для модели с помощью JSON-схемы. Модель решает, когда его вызвать и с какими аргументами; HTTP-запрос выполняет платформа.
{
"name": "book_appointment",
"description": "Записывает на приём. Вызывай его только после того, как звонящий подтвердил день, время и имя.",
"method": "POST",
"url": "https://api.example.ch/clinics/{{clinic}}/appointments",
"headers": { "Authorization": "Bearer sk_…" },
"content_type": "application/json",
"body": "{ \"start\": \"{{start}}\", \"name\": \"{{name}}\", \"phone\": \"{{phone}}\" }",
"schema": {
"type": "object",
"properties": {
"clinic": { "type": "string", "description": "Код клиники: всегда 'zurich'" },
"start": { "type": "string", "description": "Начало, yyyy-MM-dd HH:mm, напр. 2026-10-06 10:30" },
"name": { "type": "string", "description": "Имя и фамилия звонящего" },
"phone": { "type": "string", "description": "Номер телефона звонящего, только цифры" }
},
"required": ["clinic", "start", "name", "phone"]
}
}
| Поле | Для | Описание |
|---|---|---|
| name | модель | Имя функции: буквы и символы подчёркивания. |
| description | модель | Что он делает и когда его вызывать. Пишите для модели. |
| schema | модель | JSON-схема аргументов. |
| response_schema | модель | Необязательно. Структура вашего ответа, чтобы модель могла его прочитать. |
| url | среда выполнения | Ваш эндпоинт, HTTPS. |
| method | среда выполнения | GET (аргументы в строке запроса) или POST (аргументы в теле JSON). |
| content_type | среда выполнения | Обычно application/json. |
| headers | среда выполнения | Необязательно. Заголовки, отправляемые при каждом вызове, например Authorization; значения могут содержать {{parameters}}. |
| body | среда выполнения | Необязательно. Шаблон тела с {{parameters}}. Пусто: параметры, не использованные в url или заголовках, отправляются в строке запроса (GET, DELETE) или в теле JSON. |
Параметры и плейсхолдеры
Пишите {{name}} в url, значениях заголовков или body. Каждый плейсхолдер должен быть объявлен в schema.properties с описанием: модель заполняет его из разговора, а среда выполнения подставляет значение (с URL-кодированием в url, с экранированием в теле JSON). Панель управления строит схему по плейсхолдерам и проверяет инструмент с помощью Claude перед сохранением.
Как агент вызывает вас
Каждый запрос содержит заголовок token со значением tool_token агента. Отвечайте кодом 200 и телом JSON:
- Успех: { "success": true, "message": "…" } и любые данные. Сообщение предназначено для передачи звонящему.
- Ошибка бизнес-логики: { "success": false, "error": "…" }. Пишите ошибку как фразу для модели: что спросить или предложить взамен.
- 401 / 500: агент извиняется и предлагает обратный звонок. Отвечайте не дольше нескольких секунд: звонящий ждёт на линии.
Ваш эндпоинт: примеры
Это запрос, который платформа отправляет для инструмента выше, и минимальный эндпоинт, который на него отвечает. Сначала проверьте заголовок token: это токен инструментов, который вы задали для агента.
POST /clinics/zurich/appointments HTTP/1.1 Host: api.example.ch Authorization: Bearer sk_… token: 5f1c… # токен инструментов агента Content-Type: application/json { "start": "2026-10-06 10:30", "name": "Anna Keller", "phone": "41791234567" }
app.post('/clinics/:clinic/appointments', express.json(), async (req, res) => { // 1. вызывать может только ваш агент if (req.get('token') !== process.env.SWISSAI_TOOL_TOKEN) return res.status(401).end(); const { start, name, phone } = req.body; // 2. бизнес-ошибка: фраза, по которой агент может действовать if (!(await isFree(req.params.clinic, start))) return res.json({ success: false, error: "Это время занято. Предложи 11:00 или 14:30 в тот же день." }); // 3. успех: сообщение — это то, что агент говорит звонящему await book(req.params.clinic, start, name, phone); res.json({ success: true, message: "Запись оформлена на " + start + '.' }); });
<?php // appointments.php header('Content-Type: application/json'); if (($_SERVER['HTTP_TOKEN'] ?? '') !== getenv('SWISSAI_TOOL_TOKEN')) { http_response_code(401); exit; } $in = json_decode(file_get_contents('php://input'), true); if (!is_free($in['start'])) { echo json_encode(['success' => false, 'error' => "Это время занято. Предложи 11:00 или 14:30 в тот же день."]); exit; } book($in['start'], $in['name'], $in['phone']); echo json_encode(['success' => true, 'message' => "Запись оформлена на " . $in['start'] . '.']);
Раздел «Инструменты»
Инструменты хранятся в одном списке — в разделе «Инструменты» панели управления, а агенты используют их по ссылке: измените инструмент один раз, и он обновится у всех агентов, которые его используют. Инструмент, назначенный клиенту, могут использовать только агенты этого клиента; инструмент без клиента могут использовать все ваши агенты. В редакторе агента вы выбираете инструменты из списка или создаёте новый, который добавляется в список.
Песочница: готовые инструменты и примеры кода
Песочница — это работающий набор примеров эндпоинтов с демонстрационными данными, отдельными для каждого аккаунта: календарь, столики ресторана, склад, обратные звонки, контакты, заказы. Её 22 инструмента уже есть в разделе «Инструменты»: добавьте их агенту и попробуйте за несколько минут, ничего не размещая у себя. К каждому инструменту есть документация: параметры, разобранный пример запроса и ответа и код эндпоинта, который на него отвечает; код можно скачать и взять за основу для своего.
У каждого аккаунта своя тестовая среда с одинаковыми начальными данными: три услуги для записи с одним уже занятым приёмом, восемь столиков ресторана с одной бронью, шесть товаров, два контакта и четыре заказа. Она создаётся автоматически, когда вы впервые добавляете агенту инструмент песочницы, либо из раздела «Инструменты», где её также можно вернуть к начальным данным. Среда, которой не пользовались 30 дней, удаляется, и её можно создать заново.
Достаточно добавить агенту инструмент песочницы: панель управления подставит в адрес ключ вашей среды. Чтобы вызывать эндпоинты самостоятельно, передавайте ключ в заголовке Authorization: Bearer ws_… (в панели управления в документации каждого инструмента показана команда с вашим ключом). Вызовы — GET с аргументами в строке запроса или POST с аргументами в JSON.
Каждый ответ имеет статус 200 и тело JSON: success true с сообщением для звонящего и данными либо success false с ошибкой, сформулированной как фраза для агента, например какое время свободно взамен. Дни — в формате yyyy-MM-dd, время — HH:mm, номера телефонов — только цифры. Ниже — каждая область: что это, где используется и все её вызовы, каждый с примером запроса и реальным ответом.
Клиенты
Клиент — это ваш собственный заказчик. Назначьте клиенту агентов в редакторе агента; клиент получит приглашение по email и сможет войти (пароль, Google или Apple), чтобы видеть своих агентов, звонки и расход за месяц по заданной вами ставке. Клиент платит вам: SwissAI только показывает цифры.
Пригласить, изменить, удалить
При создании клиента в панели управления отправляется приглашение. При удалении клиента его агенты снимаются с назначения, а доступ клиента отзывается.
Назначение агента
В редакторе агента выберите клиента, который будет видеть его звонки; вариант «Личное использование» оставляет агента только вам.
Телефония
Агент отвечает на звонки, которые поступают на его SIP URI на нашем коммутаторе. Есть три способа направить туда звонок, все настраиваются в меню «Телефония» панели управления: внешняя АТС, разрешённая по IP-адресу, телефонный номер и WebRTC-клиент на сайте. Из панели управления можно также позвонить любому агенту из браузера, чтобы его проверить; тестовые звонки расходуют минуты, как и любые другие.
SIP URI и внешние АТС
У каждого агента есть SIP URI, он показан в списке агентов и выглядит так: sip:ag_7f3a2b91@pbx.swissai.dev. Ваша АТС отправляет звонок на этот адрес: часть перед @ определяет агента.
Звонки принимаются только от АТС, которые вы указали. В разделе «Телефония › Внешние АТС» добавьте АТС, указав название, клиента, которому она принадлежит (или «Личное использование»), и IP-адрес, с которого приходят её звонки. Имени пользователя и пароля нет: АТС распознаётся по IP-адресу и может звонить всем агентам этого клиента.
; extensions.conf: внутренний номер 200 вызывает агента
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
same => n,Hangup()
; pjsip.conf: коммутатор SwissAI как исходящий эндпоинт, без аутентификации [swissai] type=endpoint context=from-swissai disallow=all allow=alaw,ulaw aors=swissai [swissai] type=aor contact=sip:pbx.swissai.dev:5060 ; extensions.conf: внутренний номер 200 вызывает агента exten => 200,1,Dial(PJSIP/ag_7f3a2b91@swissai,60) same => n,Hangup()
<!-- dialplan: внутренний номер 200 вызывает агента -->
<extension name="swissai_agent">
<condition field="destination_number" expression="^200$">
<action application="bridge" data="sofia/external/ag_7f3a2b91@pbx.swissai.dev"/>
</condition>
</extension>
# kamailio.cfg / opensips.cfg, в request route: звонки на 200 идут агенту if ($rU == "200") { $ru = "sip:ag_7f3a2b91@pbx.swissai.dev:5060"; t_relay(); exit; }
Звонки с адреса, который вы не указали, отклоняются, а адрес, который продолжает попытки, на время блокируется. Если у вашей АТС меняется IP-адрес, обновите его в панели управления до отправки звонков.
Номера
Номер направляет обычные телефонные звонки агенту. В разделе «Телефония › Номера» для каждого номера показаны учётная запись SIP, с которой он зарегистрирован (имя пользователя, пароль, хост, порт), и агент, который отвечает.
- Номер, который у вас уже есть: через «Новый номер» введите номер и учётную запись SIP, выданную вашим оператором: хост, порт, имя пользователя и пароль. Мы регистрируемся у вашего оператора, и звонки на этот номер поступают выбранному вами агенту.
- Новый номер: через «Запросить номер» выберите, для кого номер (клиент или «Личное использование»), нужный префикс и приложите документ, удостоверяющий личность, а если номер для компании — ещё и выписку из торгового реестра. Префикс должен соответствовать месту жительства заявителя. Запрос остаётся в списке со статусом ожидания поставщика; когда номер назначен, он становится активным, и вам остаётся только выбрать агента.
WebRTC-клиенты (звонок по клику)
WebRTC-клиент — это кнопка звонка на сайте: посетитель разговаривает с агентом из браузера, без телефона. Создайте его в разделе «Телефония › WebRTC-клиенты»: выберите агента и перечислите домены, на которых используется кнопка (добавьте localhost, чтобы проверять на своём компьютере). Вы получите имя клиента (wc_…) и секретный ключ (wk_…), который показывается только один раз.
Секретный ключ никогда не должен попадать в браузер. Страница обращается к вашему бэкенду, ваш бэкенд с помощью ключа запрашивает у нас временный пароль и передаёт его странице. Временный пароль действует 60 секунд и только для одного звонка.
curl -X POST https://developers.swissai.dev/api/webrtc/token \ -H "X-Api-Key: wk_your_secret_key" \ -H "Content-Type: application/json" \ -d '{"client": "wc_3f9a1c2b7d4e", "identity": "visitor-1842"}'
// 200 { "wss": "wss://pbx.swissai.dev:8089", "domain": "pbx.swissai.dev", "user": "w4c1f09ab37de5521", "pass": "9b2e61f0c4a87d35e1f2a6b90c7d4e88", "target": "sip:ag_7f3a2b91@pbx.swissai.dev", "expires_in": 60 }
| Поле | Описание |
|---|---|
| client | Обязательно. Имя WebRTC-клиента, показанное в панели управления. |
| identity | Необязательно. Ваш идентификатор посетителя, например id пользователя. |
| 401 | Неизвестный клиент или неверный ключ. |
| 503 | Звонки из браузера недоступны для этого WebRTC-клиента, например потому, что его агент был удалён. |
// ваш бэкенд: секретный ключ остаётся здесь app.post('/call-token', async (req, res) => { const r = await fetch('https://developers.swissai.dev/api/webrtc/token', { method: 'POST', headers: { 'X-Api-Key': process.env.SWISSAI_CLIENT_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ client: 'wc_3f9a1c2b7d4e' }) }); res.status(r.status).json(await r.json()); });
<?php // call-token.php: секретный ключ остаётся на вашем бэкенде $ch = curl_init('https://developers.swissai.dev/api/webrtc/token'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('SWISSAI_CLIENT_KEY'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['client' => 'wc_3f9a1c2b7d4e']), ]); $body = curl_exec($ch); http_response_code(curl_getinfo($ch, CURLINFO_HTTP_CODE)); header('Content-Type: application/json'); echo $body;
Подключите наш скрипт и передайте ему ответ как есть. onState получает connecting, calling, connected и idle; onError получает причину, если звонок не удаётся начать.
<button id="call">Позвонить нам</button> <button id="hangup" hidden>Завершить</button> <p id="status"></p> <script src="https://developers.swissai.dev/js/call.js"></script> <script> const call = document.getElementById('call'), hangup = document.getElementById('hangup'), status = document.getElementById('status'); call.onclick = async () => { const params = await (await fetch('/call-token', { method: 'POST' })).json(); // ваш бэкенд, шаг 1 SwissCall.start(params, { onState: s => { status.textContent = s; call.hidden = s !== 'idle'; hangup.hidden = s === 'idle'; }, onError: m => { status.textContent = m; } }); }; hangup.onclick = () => SwissCall.hangup(); </script>
GetCalls: звонки и их стоимость для вашего клиента
С именем и секретным ключом WebRTC-клиента ваш бэкенд может получить звонки клиента, которому принадлежит этот WebRTC-клиент (по всем агентам этого клиента), и стоимость каждого звонка для клиента по прайс-листу, который вы задали в разделе «Клиенты».
curl "https://developers.swissai.dev/api/calls?client=wc_3f9a1c2b7d4e&month=2026-10" \ -H "X-Api-Key: wk_your_secret_key"
// 200 { "month": "2026-10", "customer": { "customer_id": 7, "name": "Dental Practice Keller" }, "currency": "CHF", "rate": 0.25, "free_minutes": 100, "calls": [ { "call_ref": "c-91f3a2", "agent_id": "ag_7f3a2b91", "agent_name": "Anna", "source": "webrtc", "from": "", "started": "2026-10-06 10:41:03", "seconds": 84, "outcome": "appointment", "tool_calls": 2, "cost": 0.35 } ], "total": { "minutes": 312.4, "calls": 148, "billable_minutes": 213, "amount": 53.25 } }
| Поле | Описание |
|---|---|
| client | Обязательно. Имя WebRTC-клиента, показанное в панели управления. |
| month | Необязательно. yyyy-MM; если не указан — текущий месяц. До 500 звонков, сначала новые. |
| cost | Длительность звонка по цене за минуту для клиента, до вычета включённых минут. |
| total | Месяц в том виде, как его видит клиент в своём портале: включённые минуты вычтены, секунды округлены вверх до минуты по итогу за месяц. |
| customer | null, если агент WebRTC-клиента предназначен для личного использования: это звонки ваших агентов без клиента, по цене платформы. |
Оба вызова можно попробовать, с готовым для копирования кодом, в разделе «Тестовая зона» панели управления.
Чат: текстовые сообщения агенту
Тот же агент, который отвечает по телефону, может отвечать письменно: на вашем сайте, в вашем приложении или в канале обмена сообщениями, которым вы управляете. Ваш бэкенд отправляет то, что написал пользователь, и получает ответ в виде текста; отвечая, агент может вызывать свои инструменты так же, как по телефону.
Вызов выполняется с именем и секретным ключом WebRTC-клиента, которые определяют агента, и должен исходить от вашего бэкенда: ключ никогда не должен попадать в браузер. Состояние не хранится: каждый раз передавайте последние реплики в history.
curl -X POST https://developers.swissai.dev/api/messages \ -H "X-Api-Key: wk_your_secret_key" \ -H "Content-Type: application/json" \ -d '{ "client": "wc_3f9a1c2b7d4e", "user": "visitor-1842", "name": "Laura Meier", "message": "В четверг в 10 ещё свободно?", "history": [ { "direction": "in", "text": "Здравствуйте, я хотел бы записаться на осмотр." }, { "direction": "out", "text": "Конечно. В какой день вам было бы удобно?" } ] }'
// 200 { "reply": "Да, четверг в 10:00 свободен. Записать вас?" }
| Поле | Описание |
|---|---|
| client | Обязательно. Имя WebRTC-клиента, показанное в панели управления. |
| message | Обязательно. Текст, написанный пользователем, до 4000 символов. |
| user | Необязательно. Ваш идентификатор того, кто пишет, например id пользователя или номер телефона. |
| name | Необязательно. Имя того, кто пишет, если оно вам известно. |
| history | Необязательно. Последние реплики, сначала старые, до 20: direction — in для пользователя и out для агента, text — сообщение. |
| reply | Ответ агента, который нужно показать пользователю; пустой, если агенту нечего сказать. |
| 401 | Неизвестный клиент или неверный ключ. |
| 402 | Пробный период или тариф не активен, либо на балансе кошелька нет средств. |
| 502 | Агент не смог ответить: попробуйте ещё раз. |
Пообщаться в чате с любым агентом можно в разделе «Телефония › Чат», а попробовать этот вызов с готовым для копирования кодом — в разделе «Тестовая зона».
WhatsApp Business
Агент может отвечать в WhatsApp двумя способами. Либо вы подключаете номер в панели, и SwissAI получает сообщения и отвечает, либо оставляете своё приложение Meta и свой вебхук на своём сайте и передаёте каждое сообщение агенту вызовом чата, описанным выше.
A. Подключить номер в панели
Телефония › WhatsApp › Подключить через WhatsApp. Откроется окно Meta: вы входите с аккаунтом Facebook вашей компании, выбираете бизнес-портфолио (или создаёте его) и номер; подключение завершается само. Вы выбираете отвечающего агента и можете изменить его позже в списке.
Два способа использовать номер: только для агента (номер больше не используется в приложении на телефоне) или оставить WhatsApp Business на телефоне: отвечает агент, а вы продолжаете пользоваться приложением. Второй вариант требует обновлённого приложения WhatsApp Business и номера, который реально используется в нём не менее недели, иначе Meta его отклонит.
Каждое входящее сообщение передаётся агенту вместе с последними 20 сообщениями этой переписки, а ответ отправляется обратно в WhatsApp; голосовые сообщения расшифровываются. Каждый обмен виден в Логах. Агент отвечает только пока активен пробный период или тариф и на кошельке есть средства; сообщения средства не расходуют. Meta выставляет счета за переписки по своим тарифам и требует способ оплаты в вашем бизнес-аккаунте, прежде чем агент сможет отвечать.
Отключение на той же странице: номер освобождается из Cloud API, а история переписки на портале удаляется.
B. Ваше приложение Meta и ваш вебхук
Если вы уже используете WhatsApp Cloud API со своим приложением Meta, оставьте его. Ваш вебхук получает сообщение, передаёт его агенту вызовом чата (user — номер отправителя, name — имя профиля, history — последние сообщения, которые вы храните сами: вызов не хранит состояние) и отправляет ответ через Graph API. Агент — тот, что указан у названного вами WebRTC-клиента; его секретный ключ остаётся на вашем сервере.
// ваш бэкенд: вебхук Meta → агент SwissAI → Graph API app.post('/webhook', express.json(), async (req, res) => { res.sendStatus(200); // сразу ответьте Meta, затем обрабатывайте for (const entry of req.body.entry || []) for (const change of entry.changes || []) { const v = change.value, m = (v.messages || [])[0]; if (!m || m.type !== 'text') continue; const name = ((v.contacts || [])[0]?.profile || {}).name || ''; const r = await fetch('https://developers.swissai.dev/api/messages', { method: 'POST', headers: { 'X-Api-Key': process.env.SWISSAI_CLIENT_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ client: 'wc_3f9a1c2b7d4e', user: '+' + m.from, name: name, message: m.text.body, history: lastExchanges(m.from) // сохранённые вами последние сообщения, от старых к новым }) }).then(r => r.json()); if (!r.reply) continue; // ответ уходит через Graph API с вашего номера await fetch('https://graph.facebook.com/v25.0/' + v.metadata.phone_number_id + '/messages', { method: 'POST', headers: { Authorization: 'Bearer ' + process.env.WA_TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ messaging_product: 'whatsapp', to: m.from, type: 'text', text: { body: r.reply } }) }); } });
Проверка вебхука (GET с hub.verify_token и hub.challenge), проверка подписи каждого события (X-Hub-Signature-256) и подписка на поле messages — это стандарт WhatsApp Cloud API: см. документацию Meta. Храните последние сообщения по каждому отправителю, чтобы передавать их в history: агент знает только то, что вы ему передаёте.
Оба пути используют одного и того же агента с его scope и инструментами: выберите A, если не хотите держать вебхук, и B, если WhatsApp уже часть вашей платформы.
Оплата
Каждый аккаунт начинается с бесплатного пробного периода на 30 дней, включающего 100 минут; карта не нужна. После пробного периода вы выбираете тариф в разделе «Оплата». Ежемесячная плата зачисляется в кошелёк как кредит тарифа, и каждый звонок списывается с него посекундно по поминутной ставке тарифа. Кредит тарифа обнуляется при каждом продлении. Когда он закончится, кошелёк можно пополнить: баланс пополнения не сгорает и расходуется после кредита тарифа. Номера, выданные по запросу, оплачиваются из кошелька каждый месяц. Агенты отвечают, только пока действует пробный период или тариф и в кошельке есть средства.