Документация

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:

Ваш эндпоинт: примеры

Это запрос, который платформа отправляет для инструмента выше, и минимальный эндпоинт, который на него отвечает. Сначала проверьте заголовок 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" }
Node.js (Express)
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
<?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 инструмента уже есть в разделе «Инструменты»: добавьте их агенту и попробуйте за несколько минут, ничего не размещая у себя. К каждому инструменту есть документация: параметры, разобранный пример запроса и ответа и код эндпоинта, который на него отвечает; код можно скачать и взять за основу для своего.

Скачать весь код (zip)

У каждого аккаунта своя тестовая среда с одинаковыми начальными данными: три услуги для записи с одним уже занятым приёмом, восемь столиков ресторана с одной бронью, шесть товаров, два контакта и четыре заказа. Она создаётся автоматически, когда вы впервые добавляете агенту инструмент песочницы, либо из раздела «Инструменты», где её также можно вернуть к начальным данным. Среда, которой не пользовались 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-адресу и может звонить всем агентам этого клиента.

Asterisk (chan_sip)
; extensions.conf: внутренний номер 200 вызывает агента
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
 same => n,Hangup()
Asterisk (PJSIP)
; 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()
FreeSWITCH
<!-- 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 / OpenSIPS
# kamailio.cfg / opensips.cfg, в request route: звонки на 200 идут агенту
if ($rU == "200") {
    $ru = "sip:ag_7f3a2b91@pbx.swissai.dev:5060";
    t_relay();
    exit;
}

Звонки с адреса, который вы не указали, отклоняются, а адрес, который продолжает попытки, на время блокируется. Если у вашей АТС меняется IP-адрес, обновите его в панели управления до отправки звонков.

Номера

Номер направляет обычные телефонные звонки агенту. В разделе «Телефония › Номера» для каждого номера показаны учётная запись SIP, с которой он зарегистрирован (имя пользователя, пароль, хост, порт), и агент, который отвечает.

WebRTC-клиенты (звонок по клику)

WebRTC-клиент — это кнопка звонка на сайте: посетитель разговаривает с агентом из браузера, без телефона. Создайте его в разделе «Телефония › WebRTC-клиенты»: выберите агента и перечислите домены, на которых используется кнопка (добавьте localhost, чтобы проверять на своём компьютере). Вы получите имя клиента (wc_…) и секретный ключ (wk_…), который показывается только один раз.

Секретный ключ никогда не должен попадать в браузер. Страница обращается к вашему бэкенду, ваш бэкенд с помощью ключа запрашивает у нас временный пароль и передаёт его странице. Временный пароль действует 60 секунд и только для одного звонка.

1Ваш бэкенд запрашивает временный пароль
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-клиента, например потому, что его агент был удалён.
Node.js (Express)
// ваш бэкенд: секретный ключ остаётся здесь
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
<?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;
2Страница начинает звонок

Подключите наш скрипт и передайте ему ответ как есть. 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Месяц в том виде, как его видит клиент в своём портале: включённые минуты вычтены, секунды округлены вверх до минуты по итогу за месяц.
customernull, если агент 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 минут; карта не нужна. После пробного периода вы выбираете тариф в разделе «Оплата». Ежемесячная плата зачисляется в кошелёк как кредит тарифа, и каждый звонок списывается с него посекундно по поминутной ставке тарифа. Кредит тарифа обнуляется при каждом продлении. Когда он закончится, кошелёк можно пополнить: баланс пополнения не сгорает и расходуется после кредита тарифа. Номера, выданные по запросу, оплачиваются из кошелька каждый месяц. Агенты отвечают, только пока действует пробный период или тариф и в кошельке есть средства.