Поля кампании
Настройка повторов
retry_config нормализуется при сохранении и возвращается нормализованным.
Номера: planned_calls
Каждый объект — одна строка списка контактов, и его ключи и есть имена колонок. Понимается всё, что понимает загрузка файла: колонки пакета рынка (zip, state, city), колонки договорных групп (contract_id, role), timezone, name и любая переменная, которую читает сценарий агента. Вложенный объект variables — синтаксический сахар, он раскладывается в те же колонки (ключ верхнего уровня побеждает такой же ключ внутри variables). Значения приводятся к тексту (логические → true/false, объекты → компактный JSON), поэтому ведущие нули сохраняются.
phone обязательна в каждой строке; значение меньше чем с десятью цифрами отклоняется как phone_unreadable — отклоняется строка, а не запрос. Построчные коды замечаний — в Отчёт по загрузке номеров.
Привязка набора
Внутренних идентификаторов транков и групп у вас нет, поэтому платформа разрешает их сама: не присылайтеtrunk_id, route_id и from_number вместе — и группы, привязанные к вашему агенту, будут найдены по всем транкам, видимым вашей организации.
Неоднозначность никогда не решается выбором первого кандидата: набор с другого транка потратил бы другой номер и другую квоту, а узнали бы вы об этом по счёту. Прочитать кандидатов:
{"dialing_routes": [{"trunk_id", "trunk_name", "route_id", "route_name", "agents", "caller_numbers"}]} — без настроек подключения транка и без учётных данных. Пустой agent_id перечисляет все маршруты, видимые вашей организации.
trunk_id — якорь. Назовите его, и остальное следует существующим правилам платформы: группа разрешается из транка и агента при запуске, а номер выбирает сама группа по своей политике исходящего номера. route_id или from_number без trunk_id отклоняются кодом 422 dialing_binding_incomplete: платформа не станет угадывать, какой транк вы имели в виду. Транк, названный явно, но непригодный (агента нет в его группе, пуст пул), при создании не отклоняется — он становится блокером binding_invalid.Подтверждение согласия
Для набора нужно подтверждение согласия:{"attestation": {"confirmed": true}}. Запись хранится неизменяемо против той версии данных, которую покрывает, и против формулировки типа согласия кампании, а подписантом выступает ваш API-ключ — выпуск ключа и есть человеческое действие, которому приписывается подпись (платформа записывает, кто и когда выпустил ключ).
Подтверждение одной загрузки не переносится на новую; поэтому в POST …/launch поле attestation.import_id обязательно (→ 422 attestation_import_id_required), а import_id, не являющийся текущей загрузкой кампании, даёт блокер attestation_stale. Партии добавляемых звонков подписываются отдельно.
Сверка с реестром «не звонить»: dnc_screen
dnc_screen — авторитетный результат. Проверяется каждая читаемая исходная строка до отбраковки по рынку и до дедупликации, поэтому matched_rows может быть больше suppressed_contacts.
import.dnc_matches — совместимая проекция items (те же объекты плюс алиас row для row_number); import.dnc_matched равно matched_rows (0, когда сверка недоступна).
Совпадения останавливают запуск блокером dnc_matches. Присланное dnc: "skip" записывает то же аудируемое решение, что человек принимает в кабинете. Оно действует для этой конкретной загрузки и этого fingerprint: новое совпадение или заменённая запись реестра делают решение устаревшим, и launch-check/409 возвращают пересчитанную сверку. Если совпали все ожидающие контакты, all_contacts_suppressed остаётся безусловным. skip при чистой сверке ничего не записывает, и запуск идёт как обычно.
Значение по умолчанию dnc: "block" при явном запуске тоже записывается решением, и более поздний block отзывает любой прежний skip. Поэтому явный запуск без поля dnc никогда не едет на пропуске, который кто-то одобрил в кабинете: присылайте dnc: "skip" сами, если запуск означает именно это. Решению нужна загрузка, к которой применяться: dnc при запуске кампании без загрузки → 409 dnc_decision_without_upload; на загрузке без записанной проекции → 409 dnc_screen_required.
До первого запуска сверка и отчёт только по совпадениям следуют за живым реестром. Операция запуска пересчитывает сверку непосредственно перед переходом и атомарно фиксирует все совпавшие строки вместе с отметкой загрузки, подписью и состоянием кампании. После запуска чтения и отчёты используют этот снимок (as_of_launch: true), даже когда реестр меняется. Контакты, совпавшие по ходу кампании, всё равно снимаются в момент набора; progress считает их отдельно.
Идемпотентность запросов по кампаниям
POST /v1/campaigns и POST /v1/campaigns/{id}/calls работают по одному протоколу:
- Ключ занимается до начала работы, атомарно. Второй запрос с тем же ключом, пока первый ещё выполняется, получает
409 idempotency_key_in_flight— дождитесь первого ответа, а не повторяйте снова. - Тело фингерпринтится вместе с ключом (канонический JSON, порядок ключей не важен). Тот же ключ с другим телом —
422 idempotency_key_reuse, и никогда не молчаливая переигровка прежнего ответа. - Тот же ключ с тем же телом переигрывает сохранённый
201байт в байт. - Ключи ограничены вашим API-ключом, эндпоинтом и — для
calls— кампанией, и помнятся сутки. - При создании отказ до появления кампании освобождает ключ; отказ после — оставляет занятым. У
callsлюбой отказ освобождает ключ.