API EN
Ранний доступ. Ключи выдаются по запросу. Как получить ключ

POST /v1/campaigns/{id}/leads принимает ваших лидов и готовый первый текст для каждого. Ответ 202 показывает прием пакета, а не отправку сообщений.

#Подготовьте кампанию

#Настройте кампанию в кабинете

Создайте линейную кампанию. Выберите получение первого сообщения из API и откройте прием API-лидов. Первый шаг должен быть готов, но его текст пуст: без AI-персонализации, ротаций слов и вложений. Ручное назначение аккаунтов для этой кампании выключено. Настройте аккаунты, расписание и следующие шаги в кабинете.

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

Кампания должна быть активной, без текущей пересборки. Пустая API-кампания ждет ваши пакеты. Проверяйте first_message_mode=api, accepts_api_leads=true и status=active через чтение кампании. Одной настройки accepts_api_leads для приема недостаточно.

При чтении first_message_mode может быть unknown, а accepts_api_leads может быть null, если значение не установлено. Это не подтверждение готовности кампании к приему.

#Выдайте ключу право

Нужно leads:enroll, а пространство и кампания должны входить в охват ключа. Для проверки отправок отдельно выдайте leads:read, events:read и при необходимости messages:read.

#Тело запроса

Передавайте Content-Type: application/json и объект с единственным полем leads: от 1 до 100 элементов. Все тело до 512 КиБ; даже пакет из 100 лидов может превысить этот размер.

  • username обязателен: ник Telegram, @ник или публичная ссылка на профиль. Регистр и начальный @ убираются. Нормализованный ник содержит от 4 до 32 латинских букв, цифр и _, начинается с буквы, заканчивается буквой или цифрой. Телефон и приглашение в чат не подходят.
  • external_id необязателен, допускает null: от 1 до 128 символов без управляющих знаков. Это ваш ключ участия в конкретной кампании; он отличается от fields.record_id, который связывает карточку с CRM.
  • first_message обязателен: от 1 до 4096 символов Unicode после замены CRLF/CR на LF, не только пробелы. Пустая строка и нулевой символ запрещены. Пробелы по краям сохраняются.
  • fields необязателен: только record_id, first_name, last_name, company, position, website, source, utm_source, utm_medium, utm_campaign. Значения строковые или null; record_id до 128 символов, остальные до 1024. До 50 ключей и 8 КиБ на объект. Лишние поля запрещены на любом уровне запроса.

Архив переписки отдает до 4000 символов с признаком сокращения. Допустимый первый текст длиной 4096 символов не обязательно вернется из архива целиком.

#Идемпотентность и повторы

Обязательный Idempotency-Key содержит от 1 до 255 печатных ASCII-символов. Создайте уникальное значение для одной логической операции и сохраните его вместе с целью и исходным телом до запроса.

  • При 429, 503, таймауте или обрыве повторяйте исходный запрос с тем же ключом. После 429/503 выдержите Retry-After и случайный разброс. Не создавайте новый ключ внутри цикла повтора.
  • Повтор принятого запроса возвращает ту же команду с ее текущим состоянием, без новых лидов и повторного расхода суточной квоты. HTTP-бюджет расходуется и на повторы.
  • Ключ действует в пределах кабинета и сохраняет смысл при перевыпуске API-ключа.
  • Другие тело, порядок лидов, цель или операция с тем же ключом дают 422 idempotency_key_reused. Даже если два текста становятся одинаковыми после нормализации переносов строк, сохраняйте исходное тело при повторе.

Примеры берут заранее сохраненный MELIORA_IDEMPOTENCY_KEY. Пример curl повторяет 429 и временные ошибки с учетом Retry-After; Python и Node.js показывают ограниченный цикл явно.

curl --retry 5 --retry-max-time 300 \
  -X POST https://api.melioralab.io/v1/campaigns/4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14/leads \
  -H "Authorization: Bearer $MELIORA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $MELIORA_IDEMPOTENCY_KEY" \
  --data-raw '{"leads":[{"username":"example_lead","external_id":"crm-1042","first_message":"Здравствуйте, Анна! Можно задать вопрос?","fields":{"record_id":"crm-1042","first_name":"Анна"}}]}'

Если запрос оборвался, сохраненные ключ и тело позволяют повторить его тем же способом после паузы.

#Ответ 202 и результат каждого лида

Ответ содержит объект команды. У каждого входного лида есть элемент items в исходном порядке с input_index от нуля, lead_id, нормализованным username, external_id, status, reason, warnings и queue_state. Текст первого сообщения в ответ не возвращается.

  • accepted: новый лид принят к постановке. Начальный queue_state=waiting.
  • existing: в этой кампании уже есть тот же ненулевой external_id и тот же нормализованный ник. Текст и поля не перезаписываются, повторная отправка не создается; queue_state=not_required.
  • rejected: лид не принят, причина в reason; обычно lead_id=null, queue_state=not_required.

result.accepted считает новых лидов, принятых к постановке. result.inserted считает созданные записи и может включать запись, отклоненную политикой после создания. existing не входит в эти счетчики. Если новых принятых лидов нет, команда может сразу иметь state=done.

Структурно неверное тело дает ошибку всего запроса. При корректном теле возможен частичный прием: всегда проверяйте каждый items, даже при HTTP 202.

#Конфликты и отказы

  • external_id_conflict: этот external_id уже связан с другим ником в кампании.
  • username_already_in_campaign: ник уже есть в кампании, но пара external_id и ника не совпала. Это касается и повторного ника без external_id.
  • stoplisted: контакт в стоп-листе.
  • previous_contact: раньше уже контактировали, а кампания это запрещает.
  • policy_blocked: карточка создана, но политика контакта запретила постановку.
  • invalid_username, invalid_external_id, invalid_first_message, invalid_fields: неверное значение элемента. Часть таких ошибок выявляется раньше и возвращается как HTTP 422 invalid_request для всего запроса.
  • daily_lead_quota_exceeded, waiting_lead_quota_exceeded: квота закончилась; остальные элементы могут быть приняты.

Одинаковые элементы внутри пакета обрабатываются последовательно по тем же правилам. Уникальность external_id ограничена кампанией, а не всеми кампаниями кабинета. Для исправленных отклоненных элементов отправьте новый пакет с новым ключом; неизменный повтор сохраняет прежний итог.

#Очередь и первое сообщение

queue_state показывает обработку элемента:

  • waiting: ждет постановки;
  • queued: задача поставлена;
  • held: постановка удержана, смотрите reason и состояние лида;
  • failed: постановка не удалась;
  • stopped: лид остановлен до постановки;
  • not_required: постановка не требуется для existing или rejected.

Опрашивайте команду до конечного состояния, затем проверяйте лидов и события. queued и done не подтверждают отправку. Первое сообщение уходит, когда наступает расписание и доступны аккаунт, лимиты и действующая подписка. Точное время не обещается. Подтверждение: lead.first_message_sent, message.sent или first_message_sent_at лида. Шаги 2 и далее идут по обычной цепочке кампании. Неизвестный исход не дает основания повторно отправлять текст.

#Квоты и ошибки запроса

Вес запроса 5. На кабинет допускается до 20 активных команд добавления (accepted или running). На пространство: до 5000 новых записей за сутки UTC и до 20000 ожидающих работ. В ожидание входят уже ожидающие и выполняемые задачи плюс принятые, еще не поставленные элементы. Повторы и existing суточную квоту не расходуют.

Квоты могут возвращаться как HTTP 429 или как items[].reason при 202. Ошибка too_many_active_commands требует дождаться завершения предыдущих команд; суточная квота восстанавливается в следующие сутки UTC, квота ожидания после обработки работ. Заголовок Retry-After задает паузу, но не обещает восстановление квоты за это время.

422 campaign_first_message_not_api требует изменить режим кампании в кабинете; campaign_not_accepting требует проверить готовность и активность кампании. 413 payload_too_large требует уменьшить пакет. 403 api_write_disabled означает, что запись для кабинета еще не включена. Полный список в ошибках, бюджеты в лимитах.