Обычный путь к запуску обзвона через API выглядит так: создать список контактов, дождаться его обработки, загрузить или синтезировать аудио, дождаться конвертации, и только потом запустить кампанию. Четыре запроса, два ожидания и состояние, которое надо где-то хранить между шагами.
В AutoCall.kz это один POST. Не потому что мы срезали углы, а потому что поля
запроса умеют принимать не только идентификаторы. Разберём, как это устроено — и заодно те
места API, о которые интеграторы спотыкаются чаще всего.
Один запрос вместо четырёх
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 }}."
}
- Создать список
- Залить в него номера
- Создать аудиозапись
- Дождаться обработки
- Запустить обзвон
- Один
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.
Тип обзвона — это свойство обзвона, а не отдельный ресурс.
Если поле не пришло, ответ скажет прямо, какое именно и почему.
{
"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=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 и интеграции.
Токен лежит в личном кабинете, в разделе настроек. Тестовый обзвон на один свой номер стоит копейки — с него и стоит начать: отправьте тот самый запрос из начала статьи, подставив свой номер.