Перейти к основному содержимому

Локальный REST API

У AI Softphone есть REST API для CTI-интеграции: программа на том же компьютере может совершать звонки и управлять ими, читать контакты, историю звонков и SIP-учётные записи и следить за идущими звонками. Никакого SDK, никакого облачного посредника и никакого порта, открытого в сеть. Запросы и ответы — JSON, так что хватит curl или любого HTTP-клиента.

После установки API выключен: пока вы его не включите, ничего не слушает. Включённый, он слушает только петлевой интерфейс — небольшой веб-интерфейс, отвечающий только этому компьютеру, — и недоступен из офисной сети, через VPN или с другой машины.

Используйте API, когда вашей программе нужны данные телефона или нужно управлять звонком. Используйте вебхуки, когда нужно реагировать на звонки по мере их хода, без опроса. Большинство интеграций используют и то и другое; они не зависят друг от друга.

Включение​

Откройте Настройки → Интеграция и перейдите к Локальному управлению.

  1. Включите Разрешить другим программам на этом компьютере управлять телефоном. Сервер запускается сразу.
  2. Оставьте Порт по умолчанию, 8377, если его не занимает другая программа.
  3. При желании задайте Токен. После сохранения поле показывает Сохранено — введите, чтобы заменить.
  4. В Доступе выберите, какие группы открыть: Контакты, История звонков, Звонки и управление ими, Учётные записи, Настройки, Счётчики (метрики). Выключенная группа не фильтруется, а не обслуживается вовсе.
  5. Проверьте: curl http://127.0.0.1:8377/accounts. Если в ответ пришёл JSON, API работает.

Отдельная служба не устанавливается, перезапуск не нужен. Часть программы, которая это делает, отключается в Модулях (Интеграция).

Собственная страница API​

Открыть страницу API открывает http://127.0.0.1:8377 в браузере. По этому адресу отвечает список всего, что обслуживает API, на английском; адреса, которые что-то читают, — ссылки, по ним можно перейти.

Доступ и токен​

Что может программа, зависит от того, меняет ли запрос сохранённые данные, а не от того, читает ли он:

  • Без токена любая программа на компьютере может читать всё в открытых группах и управлять звонками: звонить, отвечать, класть трубку, удерживать, снимать с удержания, переводить и отправлять DTMF.
  • С токеном в заголовке Authorization ей доступны ещё и точки, меняющие сохранённое. Без токена эти точки не обслуживаются и не показываются на странице API.

Токен хранится в хранилище ключей компьютера, а не в файле настроек, и никогда не возвращается /settings.

предупреждение

Без токена любая программа, запущенная на этом компьютере, может управлять телефоном, в том числе отвечать на звонки. На личном рабочем месте это обычно приемлемо. На общей или управляемой машине задайте токен и обращайтесь с ним, как с любым паролем.

Точки доступа​

Базовый адрес — http://127.0.0.1:8377. Точкам ниже токен не нужен.

МетодПутьЧто делает
GET/metricsСчётчики в формате Prometheus.
GET/uiСписок записей в виде HTML-страницы.
GET/ui/recordings/{id}Запись с расшифровкой в виде HTML-страницы.
GET/ui/recordings/{id}/audioЗвук для страницы выше.
GET/contactsКонтакты.
GET/contacts/{id}Один контакт.
GET/historyЖурнал звонков, новые сверху. Принимает ?limit=, ?missed=true и ?declined=true.
GET/callsИдущие звонки.
POST/callsСовершает звонок: {"number": "...", "account_id": "..."}.
POST/calls/{id}/answerОтвечает на звонок.
POST/calls/{id}/hangupКладёт трубку.
POST/calls/{id}/holdСтавит звонок на удержание.
POST/calls/{id}/resumeСнимает с удержания.
POST/calls/{id}/dtmfОтправляет тоны: {"digits": "..."}.
POST/calls/{id}/transferПереводит звонок: {"target": "..."}.
GET/accountsSIP-учётные записи и состояние их регистрации. Никогда — пароль.
GET/settingsВся конфигурация без секретов.
GET/taxonomyКатегории, метки и красные флаги с их кодами.

Каждый идентификатор — UUID, выданный телефоном: id звонка берётся из /calls или из ответа на POST /calls, id учётной записи — из /accounts.

Имена полей — в snake_case, а окончание говорит о типе: _id — ссылка на UUID, _ts — момент в миллисекундах Unix (UTC), _s — длительность в секундах. То же действует для вебхуков; свои имена есть только у /settings. В REST API эти значения — числа JSON, а неизвестный момент — null.

Пример: звонок​

POST /calls совершает исходящий звонок. Тело — JSON с номером number и, при желании, account_id учётной записи, с которой звонить:

curl --location 'http://127.0.0.1:8377/calls' \
--header 'Content-Type: application/json' \
--data '{
"number": "1020",
"account_id": "0d7a4c52-6f2e-4a51-8f46-7d9a3e1b2c90"
}'

В ответ приходит идентификатор нового звонка:

{ "id": "9a3e5c71-2d48-4b6f-8e10-3c5f7a1b9d24" }
  • number обязателен. Без него ответ — 400 {"error":"a call needs a number"}, и ничего не набирается.
  • Номер дополняется на выбранной учётной записи так же, как его дополняет наборник: 1020 уходит как sip:1020@pbx.example.com. POST /calls/{id}/transfer дополняет свой target так же; цель, в которой уже есть схема или @, отправляется как есть.
  • account_id необязателен; берите его из GET /accounts. Без него звонок идёт с учётной записи, выбранной в главном окне.
  • Используйте id в /calls/{id}/…: hangup, hold, resume, dtmf и transfer. Вебхуки этого звонка несут тот же id.

С веб-страницы: звонок по клику​

Страница, обращающаяся к 127.0.0.1, попадает на компьютер, где работает браузер, — тот же, где работает телефон, — поэтому кнопке «позвонить» в CRM собственный сервер не нужен:

async function dial(number) {
const r = await fetch("http://127.0.0.1:8377/calls", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ number }),
});
if (!r.ok) console.warn("softphone:", (await r.json()).error);
}

Что содержат ответы​

Идущие звонки: GET /calls​

У каждого звонка есть id, seance_id, account_id, direction, state, number, name, uri, dialed, muted, event_ts, callstart_ts и callstate_ts.

  • state — dialing, ringing-out, ringing-in, active, hold (удержан этим телефоном), onhold (удержан другой стороной), conference или ended. Если подходит несколько, conference важнее hold, а hold важнее onhold.
  • muted — выключен ли микрофон в звонке; выключение микрофона state не меняет.
  • seance_id — разговор: звонки, связанные переводом, консультацией или конференцией, разделяют его.
  • event_ts — когда был сформирован ответ. Сравните его с callstate_ts, чтобы узнать, сколько звонок в своём состоянии, не полагаясь на собственные часы.

Учётные записи: GET /accounts​

У каждой учётной записи есть id (везде в других местах — account_id), её настройки — transport (udp, tcp или tls), port, registrar, outbound_proxy, expiry_s и другие, — включена ли она (enabled) и её состояние на АТС (state): registered, пока линия работает. Пароли не включаются никогда.

История звонков: GET /history​

Новые сверху, 100 записей, если ?limit= не говорит иного. ?missed=true возвращает только пропущенные звонки, ?declined=true — только отклонённые этим телефоном.

ПолеЗначение
idСобственный идентификатор записи журнала. Это не id звонка из /calls и вебхуков; связывает их seance_id.
outcomeОсновная классификация: answered, missed, declined или failed.
answeredtrue или false.
duration_s0 для звонка, который так и не соединился.
number, uriСобеседник — номером и SIP-адресом.
nameИз Контактов, если номер известен, иначе пусто. Сопоставляйте по number, а не по этому полю.
dialedНабранные цифры — для исходящего звонка; для входящего пусто.
account, account_idЛиния, на которой был звонок.
reasonЧем закончился: local-hangup, remote-hangup, cancelled…
answered_byno, если ответил человек; иначе — что ответило на звонок.

Контакты: GET /contacts​

У каждого контакта есть id, name, number и линия, к которой он относится, — account_id и account; пустой account означает, что контакт не привязан к линии.

Записи​

Записи и расшифровки не отдаются в JSON. API обслуживает их HTML-страницами /ui и /ui/recordings/{id}: ставьте в CRM ссылку на эти страницы вместо того, чтобы перекладывать звук. Ссылка открывается на компьютере, где хранится запись, и звук его не покидает.

Справочники и настройки​

У каждой записи /taxonomy есть постоянный code, title и description на языке интерфейса, kind (category, tag или red_flag) и, для красных флагов, severity. Сопоставляйте по code, никогда — по title: названия приходят на языке, выбранном в телефоне (при русском интерфейсе — Продажи, Поддержка…). Запись с retired: true сохраняется, чтобы старые звонки по-прежнему разрешались, но новым звонкам больше не даётся. Загрузите справочники один раз при запуске, чтобы сопоставить слова телефона с полями вашей системы.

/settings возвращает конфигурацию без секретов: звуковые устройства и громкость, приоритет кодеков, оформление и язык, запуск, горячие клавиши, уровень диагностики и состояние обеих интеграций — это удобно для инструмента поддержки, которому нужно проверить рабочее место без показа экрана. api.disabled перечисляет выключенные группы доступа, webhooks.silenced — выключенные события; пустые списки означают, что включено всё. Пароль SIP, токен API и значение заголовка вебхука в ответ не попадают никогда, как и ключи распознавателей и моделей.

Ошибки​

Каждая ошибка — JSON с единственным ключом error, рассчитанный на людей, а не на разбор программой. Тексты ошибок — на английском.

СтатусТелоЗначение
404{"error":"no such endpoint"}Такого пути нет или его группа доступа выключена; ответ одинаковый намеренно.
404{"error":"no contact with that id"}Путь верный, идентификатор — нет.
400{"error":"no call with that id"}Звонок закончился или его не было.
400{"error":"a call needs a number"}POST /calls без номера. Ничего не набрано.
400{"error":"no digits to send"}POST /calls/{id}/dtmf без цифр.
400{"error":"a transfer needs a target"}POST /calls/{id}/transfer без цели.
400{"error":"the account this call is on is no longer set up"}Учётную запись звонка удалили во время звонка, поэтому цель нельзя дополнить. На АТС ничего не отправлено.

Отклонённые запросы считаются в api_requests_refused_total, так что тихо падающая интеграция видна в метриках, а не только в ваших собственных журналах.

Метрики​

GET /metrics возвращает все счётчики телефона, каждый с пояснением. Собирайте их Prometheus или читайте вручную.

СчётчикЧто считает
calls_incoming_totalПринятые входящие звонки.
calls_outgoing_totalСовершённые исходящие звонки.
calls_answered_totalОтвеченные звонки.
calls_missed_totalВходящие звонки, на которые не ответили.
calls_declined_totalЗвонки, отклонённые здесь или другой стороной.
calls_failed_totalЗвонки, которые не удалось установить.
registrations_succeeded_totalУспешные SIP-регистрации.
registrations_failed_totalSIP-регистрации, отклонённые или не дождавшиеся ответа.
webhooks_delivered_totalВебхуки, принятые получателем.
webhooks_failed_totalВебхуки, отклонённые или не доставленные.
webhooks_dropped_totalВебхуки, выброшенные, потому что очередь была полна.
api_requests_totalЗапросы, обработанные API.
api_requests_refused_totalОтклонённые запросы: неверный токен, выключенная группа или неизвестный путь.

Обновление старой интеграции​

Ранние версии использовали имена в camelCase и короткие идентификаторы. accountId теперь account_id, startedAt — callstart_ts, durationSeconds — duration_s, answeredBy — answered_by, а поле вебхука at — event_ts. Звонки и учётные записи идентифицируются только UUID: runtimeId и идентификаторы вида call-3 или account-2 больше не возвращаются и не принимаются.

Если не работает​

ПризнакЧто проверить
Соединение с 127.0.0.1:8377 отклоненоЛокальное управление выключено, телефон не запущен или порт изменён.
404 {"error":"no such endpoint"} для пути с этой страницыВыключена его группа доступа.
Чтение работает, запись отклоняетсяТочкам, меняющим сохранённые данные, нужен токен в заголовке Authorization.
Названия категорий не на английскомНазвания следуют языку интерфейса. Сопоставляйте по code из /taxonomy.
Нет accountId, startedAt или atИнтеграция написана под старые имена; см. выше.

Если проблема с самой регистрацией или звонком, откройте Диагностику.