Технологии

Обзвон одним запросом: как устроен API AutoCall.kz

← Все статьи

Обычный путь к запуску обзвона через API выглядит так: создать список контактов, дождаться его обработки, загрузить или синтезировать аудио, дождаться конвертации, и только потом запустить кампанию. Четыре запроса, два ожидания и состояние, которое надо где-то хранить между шагами.

В AutoCall.kz это один POST. Не потому что мы срезали углы, а потому что поля запроса умеют принимать не только идентификаторы. Разберём, как это устроено — и заодно те места API, о которые интеграторы спотыкаются чаще всего.

Один запрос вместо четырёх

POST
/api/v1/autocalls
POST /api/v1/autocalls
Authorization: Bearer {ваш_токен}
Content-Type: application/json

{
"name": "Напоминание о приёме",
"type": "regular",
"time_from": "10:00:00",
"time_to": "20:00:00",

// аудио — синтезируем прямо здесь, файла ещё нет
"audio_id": [{
  "type": "synthesis",
  "data": { "text": "Здравствуйте! Напоминаем о приёме завтра." }
 }],

// список — тоже здесь, создавать заранее не нужно
"list_id": [
  { "number": "+77010000001", "variables": { "name": "Иван", "time": "14:30" } },
  { "number": "+77010000002", "variables": { "name": "Пётр", "time": "16:00" } }
 ],

"sms_unanswered": true,
"text_unanswered": "{{ name }}, не дозвонились. Ждём вас в {{ time }}."
}
Как обычно
  1. Создать список
  2. Залить в него номера
  3. Создать аудиозапись
  4. Дождаться обработки
  5. Запустить обзвон
Здесь
  • Один POST
  • В ответе — готовый обзвон
  • Состояние между шагами хранить не нужно
  • Нечему упасть на середине

Последний пункт важнее, чем кажется. Многошаговое создание — это распределённая транзакция без отката: список создался, аудио не загрузилось, и у вас в аккаунте копятся сироты, которые никто не чистит. Здесь либо создалось всё, либо не создалось ничего.

Почему поле — массив, и почему в нём бывают объекты

Это первое, что удивляет в нашем API: audio_id и list_id — массивы, причём разнородные. Разберём по очереди.

Массив — потому что записей может быть несколько

Обзвон проигрывает подряд несколько аудиозаписей. Это не избыточность, а рабочий приём: общее начало кампании одним файлом, переменная часть — другим, юридическая приписка — третьим. Меняете середину, не трогая остального.

С list_id то же самое: обзвон может идти по нескольким спискам сразу. Дубли номеров между списками схлопнутся — один человек получит один звонок, даже если попал и в «клиенты Алматы», и в «должники».

Объект — потому что сущности может ещё не быть

А вот это уже интересное. Элементом массива может быть либо ID существующей записи, либо объект, описывающий, что создать.

Поле Что можно передать
audio_id [5, 7] — ID готовых записей
[{"type":"synthesis","data":{"text":"..."}}] — синтез из текста
[{"type":"upload","data":{"file":"base64..."}}] — загрузка файла
[5, {"type":"synthesis", ...}] — и то, и другое вперемешку
list_id [10, 15] — ID готовых списков
[{"number":"+7701..."}] — номера напрямую
[{"number":"+7701...","variables":{"name":"Иван"}}] — с переменными

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

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

Если же вы гоняете по одной и той же базе регулярно — создайте список один раз через POST /api/v1/lists и дальше передавайте его ID. Тогда контакты не будут дублироваться в вашем аккаунте от кампании к кампании. Оба способа рабочие, выбор за вами — API не навязывает.

Про переменные

Поле variables в контакте — это подстановки для персональных текстов. Работают они в SMS-сообщениях (text, text_unanswered) и в сценарии ИИ-агента.

Важное уточнение, чтобы не было сюрприза: в синтезированное аудио обычного обзвона переменные не подставляются. Аудиозапись одна на всю кампанию — иначе пришлось бы генерировать отдельный файл под каждого абонента. Если нужен именно персональный голос — это либо интерактивный сценарий, либо ИИ-агент.

Почему поля обязательны «иногда»

Второе место, где легко споткнуться: обязательность поля зависит от значения другого поля. В POST /autocalls это выглядит так:

Если передали Становится обязательным Почему
"type": "regular" audio_id Обычный обзвон проигрывает запись — без неё звонить нечем
"type": "interactive" scenario_id Разговором управляет сценарий
"type": "agent" agent_id Разговор ведёт ИИ-агент
"sms": true text Отправить SMS без текста нельзя
"sms_unanswered": true text_unanswered То же для недозвонившихся
time_from time_to И наоборот: границы окна задаются только парой

Так и должно быть. Альтернатива — три отдельных эндпоинта /autocalls/regular, /autocalls/interactive, /autocalls/agent с почти одинаковыми телами, и ваш код дублирует всё, что у них общего: расписание, попытки, SMS, callback_url. Тип обзвона — это свойство обзвона, а не отдельный ресурс.

Если поле не пришло, ответ скажет прямо, какое именно и почему.

422
Unprocessable Entity
← 422 Unprocessable Entity

{
"message": "The given data was invalid.",
"errors": {
  "audio_id": ["Поле audio id обязательно, когда type равно regular."]
 }
}

HTTP-статус — это и есть статус

Отдельно стоит проговорить, потому что это ломает больше интеграций, чем всё остальное вместе взятое.

В нашем API нет поля "status": "ok" в теле ответа. Результат операции — это HTTP-код. Он для этого и придуман.

Код Что произошло Что делать
201 Обзвон создан, деньги заморожены Сохранить id из тела ответа
202 Принято в обработку. Получателей столько, что раскладывать их по отправлениям сервис будет в фоне — ждать этого в HTTP-запросе неправильно Тоже успех. Забрать результат позже — GET по id
402 Не хватило баланса на заморозку Пополнить и повторить. Ретраить «как есть» бесполезно
422 Не прошла валидация Смотреть errors, чинить запрос. Ретрай не поможет
429 Больше 300 запросов в минуту Подождать и повторить — вот это ретраить можно

Разница между «повторять можно» и «повторять бесполезно» — единственное, ради чего эти коды существуют. Когда сервис на любую ситуацию отвечает 200 OK с телом {"status":"error"}, эта разница исчезает: ваш HTTP-клиент считает запрос успешным, ретрай-политика не срабатывает, мониторинг видит зелёные графики, а обзвон не ушёл.

Самый частый баг интеграции выглядит так: клиент проверяет только наличие id в ответе и молча игнорирует 402. Кампания «отправлена», в логах чисто, звонков нет. Проверяйте код ответа, а не форму тела.

Как найти свой обзвон, не храня наш ID

При создании можно передать client_id — ваш собственный идентификатор кампании:

GET
/api/v1/autocalls?client_id=
POST /api/v1/autocalls // { ..., "client_id": "campaign-2026-07-31" }

GET /api/v1/autocalls?client_id=campaign-2026-07-31
← 200 OK // тот самый обзвон, со всеми звонками и счётчиками

Удобно, когда у вас своя нумерация кампаний и не хочется заводить лишнюю колонку под чужие ID. Работает так же в /bulks и /scenarios.

Это ключ для поиска, а не защита от дублей. Два POST с одинаковым client_id создадут два обзвона и заморозят деньги дважды. Идемпотентность ретраев — по-прежнему на вашей стороне.

И немного занудства про REST

Раз уж речь про API, проговорим пару решений, которые выглядят как придирки, но экономят время.

Создание — это POST, а не GET. Не из-за чистоты теории. GET по спецификации безопасен и повторяем, и на это рассчитывает всё вокруг: браузеры и прокси кешируют такие ответы, поисковые роботы ходят по ссылкам сами, логи и история пишут URL целиком. Создание обзвона через GET означает, что кампания может уйти повторно от кнопки «Обновить», а номера абонентов лягут в лог веб-сервера открытым текстом.

Ресурсы во множественном числе. /autocalls, а не /autocall. Коллекция — это множество; /autocalls — все обзвоны, /autocalls/42 — сорок второй. Правило скучное, зато догадаться о любом эндпоинте можно, ни разу не открыв документацию.

Тело запроса — JSON, а не query-параметры. Длина URL ограничена, вложенных структур в query нет, а номера абонентов и тексты сообщений в адресной строке — это утечка в каждом логе на пути запроса.

Что дальше

Полная спецификация — в openapi.yaml: её можно скормить Postman, Insomnia или генератору клиентов и получить готовый SDK на своём языке, ничего не переписывая руками. Обзорная страница с примерами — API и интеграции.

Токен лежит в личном кабинете, в разделе настроек. Тестовый обзвон на один свой номер стоит копейки — с него и стоит начать: отправьте тот самый запрос из начала статьи, подставив свой номер.

Один запрос — и обзвон пошёл

Получите токен и запустите первую кампанию из кода.

Попробовать бесплатно →