> For the complete documentation index, see [llms.txt](https://waba.docs.olchat.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://waba.docs.olchat.io/ispolzovanie/rest-api-dlya-rassylok-i-otchetnosti.md).

# REST API для рассылок и отчётности

Клиентское REST API позволяет запускать рассылки по шаблонам WhatsApp Business API и забирать статистику доставки из своих систем: CRM, BI-панели, скрипты автоматизации.

API работает по модели «запрос — ответ»: вы сами обращаетесь к сервису тогда, когда вам нужны данные. Исходящих вебхуков на вашу сторону сервис не отправляет.

{% hint style="info" %}
По умолчанию в ответах приходят метаданные сообщения, значения параметров шаблона и статусы доставки — без содержимого переписки. Тексты сообщений передаются, если вы явно запросите их параметром `include_text`; это сделано для того, чтобы уже написанная интеграция не начала получать переписку без вашего ведома.
{% endhint %}

## Список методов

| Метод                       | Назначение                                                            |
| --------------------------- | --------------------------------------------------------------------- |
| `messages.send.template`    | Отправить одно сообщение по шаблону                                   |
| `messages.status.get`       | Статусы доставки по идентификаторам сообщений или по номеру           |
| `messages.history.list`     | История сообщений линии за период с фильтрами и пагинацией            |
| `messages.stats.get`        | Счётчики за период: отправлено, доставлено, прочитано, ошибки, ответы |
| `templates.list`            | Шаблоны линии, одобренные провайдером                                 |
| `templates.analytics.get`   | Аналитика провайдера по шаблонам рядом с нашими счётчиками            |
| `campaigns.create`          | Создать черновик рассылки                                             |
| `campaigns.recipients.add`  | Загрузить получателей в черновик                                      |
| `campaigns.start`           | Запустить рассылку                                                    |
| `campaigns.cancel`          | Остановить выполняющуюся рассылку                                     |
| `campaigns.list`            | Список рассылок портала                                               |
| `campaigns.stats.get`       | Воронка по рассылке                                                   |
| `campaigns.recipients.list` | Построчный отчёт по получателям рассылки                              |
| `line.status.get`           | Состояние одной линии                                                 |
| `portal.status.get`         | Состояние всех линий портала                                          |

Пустой метод (обращение к базовому адресу) отдаёт этот же список в машинном виде — с обязательными и необязательными параметрами каждого метода и действующими лимитами.

## Получение токена

Токен выдаётся в приложении Олчат в Битрикс24: «Настройки приложения» — вкладка «REST API» — кнопка «Выдать / перевыпустить токен». Рядом показывается базовый адрес API.

Выдать, посмотреть или отозвать токен может только администратор портала Битрикс24: право проверяется в самом Битрикс24 в момент запроса. У остальных сотрудников вкладка показывает надпись «Токен доступен администратору портала».

Токен принадлежит порталу целиком, а не отдельной линии: одним токеном вы работаете со всеми подключёнными номерами портала, указывая нужный в параметре `line_number`.

Повторное нажатие «Выдать / перевыпустить токен» выдаёт новое значение и одновременно обесценивает прежнее — все интеграции на старом токене сразу начнут получать ошибку `401`. Кнопка «Отозвать» отключает доступ полностью.

## Формат запросов

Базовый адрес:

```
https://waba.olchat.io/rest/client/v2/<метод>/
```

Токен передаётся заголовком `OLChat-Api-Token`. В адресе и в query-строке токен не принимается.

Поддерживаются `GET` и `POST`. Простые параметры можно передавать query-строкой или формой, составные (списки получателей, параметры шаблона) — только JSON-телом с `Content-Type: application/json`.

Пример:

```bash
curl -X POST 'https://waba.olchat.io/rest/client/v2/messages.stats.get/' \
  -H 'OLChat-Api-Token: ВАШ_ТОКЕН' \
  -H 'Content-Type: application/json' \
  -d '{"line_number": 12, "date_from": "2026-07-01", "date_to": "2026-07-31"}'
```

Даты принимаются в формате ISO-8601 (`2026-07-01` или `2026-07-01T10:00:00+03:00`) либо числом секунд epoch. Все даты в ответах — ISO-8601.

Список доступных методов и действующие лимиты отдаёт сам сервис по базовому адресу без указания метода.

## Ограничения

| Ограничение                         | Значение                                        |
| ----------------------------------- | ----------------------------------------------- |
| Частота запросов                    | 5 запросов за 3 секунды на пару «токен + метод» |
| Глубина статистики                  | 92 дня                                          |
| Размер страницы                     | до 500 записей, по умолчанию 100                |
| Получателей в одном запросе         | до 1000                                         |
| Аналитика провайдера                | до 90 дней, обновляется раз в сутки             |
| Одновременно выполняющихся рассылок | до 5 на портал                                  |
| Окно ожидания ответа                | от 1 до 720 часов                               |

Лимит частоты считается отдельно для каждого метода: обращение к `messages.stats.get` не расходует лимит `campaigns.start`. Ответ `429` означает, что запрос не выполнен — его нужно повторить, а не считать неудачей операции.

## Отправка сообщений

### Одно сообщение по шаблону

`messages.send.template` отправляет одно сообщение по одобренному шаблону. Заводить ради одного получателя рассылку не нужно.

| Параметр           | Тип  | Описание                                                     |
| ------------------ | ---- | ------------------------------------------------------------ |
| line\_number\*     | int  | Номер линии                                                  |
| phone\*            | str  | Номер абонента в любой записи                                |
| template\_name\*   | str  | Имя шаблона                                                  |
| template\_language | str  | Язык шаблона, если одно имя используется в нескольких языках |
| template\_params   | list | Значения подстановок в порядке их следования в шаблоне       |

В ответе — блок `message` в том же виде, что и в истории: идентификатор сообщения, номер, время создания, имя и язык шаблона, значения подстановок и блок статусов. Идентификатор из ответа используйте дальше в `messages.status.get`.

Число переданных подстановок должно совпадать с числом подстановок в шаблоне, иначе метод ответит ошибкой `template_params_mismatch` и сообщение отправлено не будет. Отправка на линию с просроченной оплатой или без подключения отклоняется с `line_not_active`.

## Отчётность

### История сообщений

`messages.history.list` — сообщения линии за период с пагинацией и фильтрами.

| Параметр              | Тип  | Описание                                     |
| --------------------- | ---- | -------------------------------------------- |
| line\_number\*        | int  | Номер линии                                  |
| date\_from\*          | date | Начало периода                               |
| date\_to\*            | date | Конец периода                                |
| direction             | str  | `incoming`, `outgoing` или `all`             |
| phone                 | str  | Номер абонента                               |
| template\_name        | str  | Имя шаблона                                  |
| sent, delivered, read | bool | Фильтр по статусу                            |
| errors\_only          | bool | Только сообщения с ошибкой                   |
| page, page\_size      | int  | Страница и её размер                         |
| include\_text         | bool | Добавить текст сообщения и описание вложения |

Параметр `include_text: true` добавляет к каждому сообщению его текст. Работает и для входящих, и для исходящих. У сообщения с вложением приходит блок `attachment` с типом вложения и именем файла; само содержимое файла и ссылка на скачивание через API не передаются. Без этого параметра поля `text` и `attachment` в ответе отсутствуют.

В ответе — `total`, `page`, `page_size` и массив `messages`. Каждое сообщение содержит `message_id`, `line_number`, `direction`, `phone`, `created_at`, имя, язык и значения параметров шаблона, блок `status` со значениями `sent`/`delivered`/`read` и временем каждого, `status_source`, текст ошибки, признак тарификации и модель тарификации.

### Статус конкретных сообщений

`messages.status.get` — состояние доставки по идентификаторам либо по паре «номер абонента + период».

| Параметр             | Тип  | Описание                                        |
| -------------------- | ---- | ----------------------------------------------- |
| line\_number\*       | int  | Номер линии                                     |
| message\_ids         | str  | Идентификаторы через запятую                    |
| phone                | str  | Номер абонента, если идентификаторы не известны |
| date\_from, date\_to | date | Период, обязателен при запросе по номеру        |
| include\_text        | bool | Добавить текст сообщения и описание вложения    |

Идентификатор, не принадлежащий линиям вашего портала, просто отсутствует в ответе — запрос при этом успешен. За один раз принимается не более 500 идентификаторов: на большем количестве метод отвечает ошибкой `too_many_message_ids`, а не отдаёт часть молча.

### Агрегированная статистика

`messages.stats.get` — счётчики за период: отправлено, доставлено, прочитано, ошибок, ответов.

Обратите внимание на базы счётчиков: `sent`, `delivered`, `read`, `errors` и `total` считаются по всем исходящим сообщениям линии — операторским, роботным и рассылочным. `replied` считается только по получателям рассылок: ответы отслеживаются в окне ответа кампании. Чтобы доля ответов была осмысленной, рядом отдаётся `campaign_sent` — база именно для `replied`. Оба поля есть при `group_by=total`.

| Параметр                 | Тип  | Описание                                     |
| ------------------------ | ---- | -------------------------------------------- |
| line\_number\*           | int  | Номер линии                                  |
| date\_from\*, date\_to\* | date | Период, не длиннее 92 дней                   |
| group\_by                | str  | `total` (по умолчанию), `day` или `template` |

Счётчик `replied` считается по получателям рассылок: сообщения вне рассылок в него не попадают.

### Состояние линий

`line.status.get` (параметр `line_number`) и `portal.status.get` (без параметров) отдают номер телефона, статус подключения, признак активности, признак демонстрационного режима и дату оплаты. Ключи доступа к провайдеру в ответах не передаются.

### Список шаблонов

`templates.list` (параметр `line_number`) — доступные шаблоны линии: имя, идентификатор, язык, статус согласования, категория и число параметров.

Список берётся из того же источника, против которого проверяется отправка, поэтому шаблон из этого метода гарантированно пригоден для рассылки. Если линия не подключена к провайдеру, метод отвечает `line_not_connected`.

## Рассылки

Рассылка проходит четыре шага: создание черновика, загрузка получателей, запуск, наблюдение за воронкой.

### Создание рассылки

`campaigns.create`

| Параметр             | Тип  | Описание                                   |
| -------------------- | ---- | ------------------------------------------ |
| line\_number\*       | int  | Номер линии                                |
| template\_name\*     | str  | Имя шаблона                                |
| name                 | str  | Название рассылки                          |
| template\_language   | str  | Язык шаблона, по умолчанию `ru`            |
| template\_params     | list | Значения параметров шаблона по умолчанию   |
| reply\_window\_hours | int  | Окно ожидания ответа, по умолчанию 72 часа |

Число значений в `template_params` проверяется по составу шаблона: несовпадение отклоняется с ошибкой `template_params_mismatch`.

### Загрузка получателей

`campaigns.recipients.add` — до 1000 получателей за запрос, черновик можно наполнять несколькими запросами.

| Параметр       | Тип  | Описание               |
| -------------- | ---- | ---------------------- |
| campaign\_id\* | int  | Идентификатор рассылки |
| recipients\*   | list | Список получателей     |

Персональные параметры получателя передаются списком в том же порядке, что и подстановки шаблона. Значение другого типа (например объект `{"1": "Иван"}`) отклоняется с причиной `invalid_params`: раньше такой получатель принимался и получал текст с параметрами кампании вместо своих.

Получатель — либо номер строкой, либо объект:

```json
{
  "campaign_id": 17,
  "recipients": [
    "79001234567",
    {"phone": "79007654321", "external_id": "CRM-4821", "params": ["Иван", "12 августа"]}
  ]
}
```

Персональные значения `params` замещают значения рассылки по умолчанию. Поле `external_id` возвращается обратно в отчётах — по нему удобно сопоставлять получателей со своими записями.

Ответ содержит число принятых и список отклонённых с причиной: `invalid_phone` (номер не похож на телефон) или `duplicate` (номер уже есть в этой рассылке). Повторная загрузка того же номера дубля не создаёт, поэтому запрос можно безопасно повторить после обрыва связи.

### Запуск и отмена

`campaigns.start` (параметр `campaign_id`) переводит рассылку в работу. Перед запуском проверяются подключение и оплата линии; повторный запуск отвечает `409`.

Отправка идёт порциями в фоне, поэтому метод возвращает управление сразу, не дожидаясь конца рассылки.

`campaigns.cancel` (параметры `campaign_id`, `reason`) останавливает рассылку: неотправленные получатели переводятся в `skipped`, следующая порция не запускается. Уже отправленные сообщения отменить нельзя — они у абонентов.

В ответе кроме `skipped` приходит `in_flight` — число получателей, по которым обращение к провайдеру было разрешено до того, как отмена дошла. Только по ним сообщение ещё может уйти: отозвать отправленный по сети запрос нельзя. Получатели, просто взятые обработчиком в работу, но не дошедшие до отправки, в это число не входят — они переводятся в `skipped` без обращения к провайдеру. Если `in_flight` больше нуля, окончательные статусы этих получателей смотрите в `campaigns.recipients.list` через минуту-другую.

### Наблюдение

`campaigns.stats.get` (параметр `campaign_id`) отдаёт воронку: `total`, `pending`, `sent`, `delivered`, `read`, `replied`, `errors`, `skipped`, `no_whatsapp`, `unknown`, а также статус рассылки и время запуска и завершения.

`campaigns.recipients.list` (параметры `campaign_id`, `status`, `page`, `page_size`) — построчный отчёт: номер, `external_id`, статус, ошибка, идентификатор сообщения, время отправки, доставки, прочтения и ответа.

`campaigns.list` (параметры `line_number`, `status`, `page`, `page_size`) — список рассылок портала.

Статусы рассылки: `draft`, `running`, `completed`, `cancelled`.

Статусы получателя: `pending` (ожидает), `processing` (в обработке), `success` (отправлено), `error` (ошибка), `skipped` (пропущен при отмене), `no_whatsapp` (на номере нет WhatsApp), `unknown` (результат неизвестен).

Статус `unknown` появляется, если обработчик прервался между отправкой и получением ответа провайдера. Автоматически такой номер повторно не отправляется: сообщение могло уйти и уже быть оплачено, а повтор означал бы второе сообщение абоненту и второй счёт. Решение по таким номерам принимаете вы.

## Аналитика шаблонов

`templates.analytics.get` показывает значения провайдера рядом с нашими за один и тот же период.

| Параметр                 | Тип  | Описание                |
| ------------------------ | ---- | ----------------------- |
| line\_number\*           | int  | Номер линии             |
| date\_from\*, date\_to\* | date | Период                  |
| template\_name           | str  | Один шаблон вместо всех |

Данные провайдера выгружаются фоновой задачей раз в сутки, поэтому за сегодняшний день их может ещё не быть. Признак `has_provider_data` и поле `fetched_at` показывают, есть ли выгрузка и насколько она свежая; наши счётчики отдаются в любом случае.

Фоновая выгрузка забирает у провайдера последние 30 дней, а запросить можно период до 92 дней. Чтобы частичное покрытие не выглядело полным, в ответе есть блок `provider_coverage` с фактическими границами дат, за которые выгрузка у нас есть. Наши счётчики покрывают весь запрошенный период.

Небольшое расхождение между колонками — норма: провайдер считает по своим суткам и включает переходы по кнопкам, которых в нашей статистике нет.

## Коды ошибок

Ошибка возвращается телом `{"error": "<код>"}` с соответствующим HTTP-статусом.

| Код                               | Статус | Причина                                                           |
| --------------------------------- | ------ | ----------------------------------------------------------------- |
| unauthorized                      | 401    | Токен отсутствует, неизвестен или отозван                         |
| rate\_limit\_exceeded             | 429    | Превышена частота запросов                                        |
| unknown\_method                   | 404    | Метод не существует                                               |
| line\_not\_found                  | 404    | Линия не найдена или принадлежит другому порталу                  |
| campaign\_not\_found              | 404    | Рассылка не найдена или принадлежит другому порталу               |
| line\_number\_required            | 400    | Не передан номер линии                                            |
| period\_required                  | 400    | Не передан период                                                 |
| invalid\_period                   | 400    | Начало периода позже его конца                                    |
| period\_too\_long                 | 400    | Период длиннее 92 дней                                            |
| invalid\_datetime                 | 400    | Дата не разобрана                                                 |
| invalid\_direction                | 400    | Недопустимое значение `direction`                                 |
| invalid\_group\_by                | 400    | Недопустимое значение `group_by`                                  |
| invalid\_pagination               | 400    | Недопустимые `page` или `page_size`                               |
| message\_ids\_or\_phone\_required | 400    | Не передан ни идентификатор, ни номер                             |
| line\_not\_connected              | 400    | Линия не подключена к провайдеру                                  |
| template\_not\_found              | 400    | Шаблон не найден среди доступных линии                            |
| template\_params\_mismatch        | 400    | Число значений не совпадает с числом параметров шаблона           |
| recipients\_required              | 400    | Не передан список получателей                                     |
| batch\_too\_large                 | 400    | Больше 1000 получателей в одном запросе                           |
| no\_recipients                    | 400    | Запуск рассылки без получателей                                   |
| line\_not\_paid                   | 400    | Линия не оплачена                                                 |
| campaign\_not\_draft              | 409    | Получателей можно добавлять только в черновик                     |
| campaign\_already\_started        | 409    | Рассылка уже запущена                                             |
| campaign\_not\_running            | 409    | Отменить можно только запущенную рассылку                         |
| too\_many\_running\_campaigns     | 409    | Больше пяти одновременно выполняющихся рассылок на портал         |
| invalid\_reply\_window            | 400    | Окно ожидания ответа вне диапазона 1–720 часов                    |
| invalid\_params                   | 400    | Параметры шаблона переданы не списком                             |
| phone\_required                   | 400    | Не передан номер абонента                                         |
| template\_name\_required          | 400    | Не передано имя шаблона                                           |
| invalid\_phone                    | 400    | Номер не распознан                                                |
| template\_params\_mismatch        | 400    | Число подстановок не совпадает с шаблоном                         |
| template\_not\_found              | 404    | Шаблон не найден среди одобренных на линии                        |
| line\_not\_active                 | 403    | Линия не подключена или оплата просрочена                         |
| templates\_unavailable            | 503    | Список шаблонов у провайдера временно недоступен, повторите позже |
| provider\_error                   | 502    | Провайдер отклонил отправку                                       |
| send\_failed                      | 502    | Сбой связи с провайдером                                          |
| too\_many\_message\_ids           | 400    | Больше 500 идентификаторов в одном запросе статусов               |
| invalid\_datetime                 | 400    | Дата не разобрана: нужен ISO‑8601 или epoch в секундах            |
| internal\_error                   | 500    | Внутренняя ошибка сервиса                                         |

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

## О чём стоит знать заранее

**Время статусов.** С 3 августа 2026 года время доставки и прочтения записывается таким, каким его сообщил провайдер, а не временем обработки события на нашей стороне. У каждого сообщения есть поле `status_source`: `provider` — время провайдера, `internal` — время обработки. Сообщения, отправленные до этой даты, остались со значением `internal`; при сравнении периодов «до» и «после» это стоит учитывать.

**Сопоставление ответа с рассылкой.** Ответ абонента засчитывается получателю рассылки, если он пришёл после отправки и внутри окна ожидания (по умолчанию 72 часа). Если один и тот же номер участвует в нескольких рассылках с пересекающимися окнами, ответ засчитывается ближайшей по времени отправке — разделить, на какую именно рассылку ответил абонент, по одному входящему сообщению невозможно.

**Приостановка рассылки.** Отдельного метода паузы нет. Останавливает рассылку только `campaigns.cancel`, и возобновить отменённую нельзя — оставшихся получателей переносят в новую рассылку.

**Фильтр по имени шаблона и старая переписка.** Отбор истории и группировка статистики по `template_name` опираются на структурированные данные сообщения. Сообщения старше 10 июля 2024 года хранятся в прежнем формате и под такой отбор не попадают: за периоды до этой даты запрашивайте историю без фильтра по шаблону.

**Срок хранения истории.** Сообщения хранятся год. Данные старше года удаляются, поэтому история и статистика доступны за последние 12 месяцев. Если вам нужна более длинная история, выгружайте её к себе регулярно.

**Формат номера и дат.** Номер в фильтрах принимается в любой записи — `+7 999 123-45-67` и `79991234567` дают один результат. Даты принимаются в ISO‑8601 (`2026-08-01`, `2026-08-01T10:00:00Z`), в сжатом виде (`20260801`) и как epoch в секундах.
