Skip to main content

Управление кампанией

Статусы. draft → (запуск) → scheduled (когда starts_at в будущем) или running; scheduled становится running, когда открывается окно; runningpaused; runningcompleted, когда очередь опустела (ничего не ожидает и ничего не в наборе) — кроме кампании с accepts_call_additions: true, которая завершается только по наступлении ends_at; любой нефинальный статус → cancelled. completed и cancelled финальны. Наступление ends_at закрывает любую кампанию. На resume блокеры no_pending_contacts, attestation_required, attestation_stale, contacts_geo_rejected и dnc_screen_required не применяются. Ответы pause / resume / cancel содержат объект кампании без blockers и warnings: эти пути не запускают проверки запуска, а пустой массив читался бы как «проверено, всё чисто». Нужны проверки — вызовите launch-check. Повторить переход — не ошибка. Кампания, уже находящаяся в целевом состоянии, отвечает 200 со своим текущим объектом: повтор после сетевого таймаута — не второе намерение. Переход, невозможный из текущего состояния (возобновить отменённую, поставить на паузу черновик), — 409 campaign_transition_invalid. Разница между «уже сделано» и «сделать нельзя» — ровно та, которая вам нужна. Заблокированный запуск или возобновление — ошибка, а не 200. Создание отвечает 201, потому что кампания и отчёт действительно созданы; launch — равно как и resume, который прогоняет те же проверки, — ничего не запустивший, отвечает 409 launch_blocked, и причины едут в том же ответе:
Правило по всей поверхности кампаний: действие, которое ничего не изменило, — всегда ошибка; успешный ответ означает, что состояние в теле и есть новое состояние.

Добавление номеров в существующую кампанию

Кампании принадлежат агент, привязка набора, правила повторов и рабочие часы; партия несёт только номера, переменные и подпись. Каждый элемент устроен как строка planned_calls (Поля кампании) — его ключи и есть имена колонок. Неизвестные поля верхнего уровня игнорируются: agent_id в теле не меняет агента кампании, а ключ dnc не обходит реестр. Партия принимается в любую кампанию, которая не completed и не cancelled, — то есть в draft, scheduled, running и paused, независимо от того, открыта ли она к добавлению звонков. Закрытая отвечает 409 campaign_closed. Порядок отказов, все до разбора хотя бы одной строки: 422 idempotency_key_required422 calls_required (пустой calls) → 422 calls_batch_too_large422 attestation_invalid404 campaign_not_found409 campaign_closed → идемпотентность (422 idempotency_key_reuse / 409 idempotency_key_in_flight / переигровка). Затем разбираются строки: 409 market_package_not_installed, 422 contact_rows_rejected, 502 contact_file_storage_unavailable — как при создании, — и два отказа партии ниже.
Партия принимается целиком или отклоняется целиком. Одна строка с ошибкой данных отклоняет всю партию кодом 422 calls_rejected; один номер из реестра «не звонить» — кодом 422 calls_dnc_matched, отдельным, чтобы ваша система отличала «исправить строки» от «убрать этих людей из списка». Если в партии проблемы обоих видов, побеждает calls_rejected. Структурные отказы разборщика (нет колонки телефона, нет пригодных строк, …) — тоже calls_rejected. Для отклонённой партии контакты не создаются и подпись не записывается.
Оба отказа несут error.import:
Здесь issues несёт первые 50 замечаний; полный отчёт по строкам — GET /v1/imports/{import_id}/report?only=dnc_match для совпадений с реестром). Отклонённая партия сохраняется записью загрузки со status: "refused" ровно для этого. Успех — 201:
campaign — краткое представление (тот же объект, что элемент списка). blockers означает «принято, но сейчас не набирается, и вот почему»: те же проверки запуска, что у launch-check, минус те, которые свежая партия делает неактуальными (no_pending_contacts, attestation_required, dnc_screen_required, all_contacts_suppressed, dnc_matches), плюс campaign_paused. Элементы на этом пути несут только code и field. Правила, которые стоит знать, прежде чем гнать номера из CRM:
  • Предел — до 1000 строк за запрос по умолчанию (настройка развёртывания): больше — 422 calls_batch_too_large, пустая партия — 422 calls_required. Один запрос считается один раз в лимите ключа.
  • Подписьattestation должна быть ровно {"confirmed": true}: без других ключей и без import_id (иначе 422 attestation_invalid). Каждая принятая партия подписывается отдельно и привязана к своей загрузке; подпись запуска не переиспользуется для строк, которых на момент запуска не существовало.
  • Часовой пояс — в кампании с use_contact_timezone: true под казахстанским рыночным профилем строка без timezone — это ошибка данных (timezone_required), и она отклоняет партию: платформа не подставит зону кампании строке, на которую никто не смотрел. У загрузки файла и у planned_calls при создании подстановка сохраняется.
  • Дубли — внутри партии duplicate_action работает так же, как для файла. Номер, у которого в кампании уже есть контакт — даже уже набранный, — принимается как новый контакт и набирается снова: для адаптера, который сам управляет перезвонами, повтор и есть перезвон. Если перезвонами управляет ваша система, оставьте retry_config.attempts = 1; при большем бюджете перезвоны станут двухуровневыми.
  • Партия никогда не становится запусковой загрузкой кампании — зафиксированный снимок реестра, подпись запуска и dnc_screen в GET /v1/campaigns/{id} продолжают указывать на загрузку, с которой кампанию запускали. Своя сверка партии — в теле 201 и в GET /v1/imports/{import_id}.
  • Идемпотентность — ключ ограничен рамками кампании, поэтому один и тот же ключ с одним и тем же телом, отправленный в две кампании, — это две партии. Отклонённая партия освобождает ключ: исправьте строки и пришлите заново с тем же ключом (создастся вторая отклонённая запись загрузки — диагностически полезно, звонков это не касается). Повтор принятой партии переигрывает ответ; тот же ключ с другим телом — 422 idempotency_key_reuse.

Кампании, открытые к добавлению номеров

Обычная кампания завершает себя сама, едва очередь опустела, — а завершённая кампания звонков не принимает. Чтобы кампания ждала номера, создайте её с "accepts_call_additions": true. Такая кампания:
  • требует ends_at (422 campaign_open_ends_at_required) не дальше горизонта оператора — по умолчанию 30 суток (422 campaign_open_ends_at_too_far);
  • может быть создана вообще без planned_calls и стартует пустой (блокера no_pending_contacts нет);
  • закрывается только наступлением ends_at или отменой, но не опустевшей очередью.
Флаг задаётся при создании и потом не меняется; он возвращается в объекте кампании и в списке.

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

GET /v1/campaigns/{id}/progress:
by_state отдаёт собственные названия состояний платформы — без слоя перевода, потому что разница между «дозвонились» и «попытки кончились» важна именно вам. Состояния с нулевым количеством в ответ не попадают. ready — сколько ожидающих контактов набираемы прямо сейчас; holds — сколько ожидающих удерживается (причины в held_reasons); suppressed_reasons раскладывает состояние suppressed; dnc_suppressed_at_launch + dnc_suppressed_mid_campaign = suppressed_reasons.dnc_suppressed; frequency сообщает, сколько контактов сейчас удержано или заблокировано ограничениями частоты на номер и какие лимиты этим управляют. Ключи причин внутри held_reasons, suppressed_reasons и by_governing_cap — машинные коды, их набор может расти.
Обычная кампания закрывается сама, когда очередь опустела, — кампания, созданная с accepts_call_additions: true, не закрывается (её закрывают только ends_at или отмена). Отдельного вебхука «кампания завершилась» нет в любом случае — опрашивайте progress или GET /v1/campaigns/{id}.

Снятие контакта с обзвона

Остановить набор номера до того, как звонок состоится: долг погашен, номер принадлежит другому человеку, вопрос закрыли в другом канале.
200 OK раскладывает контакты по исходу; все пять ключей присутствуют всегда:
  • Что снимается. Контакт, ожидающий набора (pending, включая запланированный перезвон) или ожидающий вердикта аналитики (awaiting). Он переходит в suppressed с причиной withdrawn_by_client и в этой кампании больше не набирается. У контакта в awaiting звонок уже состоялся — снятие отменяет перезвон, который мог бы назначить вердикт.
  • Что не снимается. not_withdrawable называет причину: in_call — звонок идёт или контакт только что взят в набор (снятие трубку не кладёт: используйте POST /v1/calls/{call_id}/end, а после звонка снимите контакт ещё раз, если перезвон не нужен), done, exhausted или already_suppressed — исключён раньше самой платформой (реестр «не звонить», частотный лимит, география); прежняя причина сохраняется.
  • Повторять безопасно. Idempotency-Key не нужен: уже снятый контакт вернётся в already_withdrawn.
  • Как адресовать. Номер сопоставляется по последним десяти цифрам, поэтому написание значения не имеет, а само значение можно слать строкой или числом. Один номер достаёт все контакты, которые его несут, — в кампании с дублями все сразу. Идентификатор контакта — это metadata.contact_id из вебхуков звонка; контакт, который ещё ни разу не набирали, адресуется номером.
  • Ручка по организации достаёт и кабинетные кампании: все живые кампании организации, как бы они ни были созданы. Черновики и закрытые кампании не трогаются.
  • Частичная обработка. Один плохой адрес не роняет запрос: в invalid вернётся то, что не удалось прочитать (не номер, нет цифр, пустой идентификатор), в not_found — то, под что не нашлось контакта, и то и другое ровно в том виде, в каком вы прислали. По умолчанию не больше 1000 адресов на запрос.
  • Последствия. Если снять последние ожидающие контакты идущей кампании, она завершится на следующем проходе диалера — кроме кампании, открытой к добавлению номеров. Снятый номер вернётся в набор только с новой партией звонков.
Экрана снятия в кабинете пока нет: причина withdrawn_by_client видна в разбивке прогресса кампании, а сама операция доступна только через API.