API ЭТОЧАТБОТ

Ошибки, ограничения и частые вопросы

Все коды ошибок API ЭТОЧАТБОТ в одной таблице, честный разбор ограничений мессенджеров и ответы на вопросы, которые возникают у всех при первой интеграции.

Эта статья — справочник, к которому возвращаются, когда что-то пошло не так. Здесь все коды ошибок API, объяснение ограничений (почему нельзя написать человеку первым) и ответы на частые вопросы.

Базовый адрес API: https://etochat.bot/api/v2/{channel}. Авторизация — заголовок Authorization: Bearer <ключ>.

Как выглядит ошибка

Любая ошибка приходит одинаково: JSON с объектом error и осмысленный HTTP-статус.

{
  "error": {
    "code": "recipient_blocked_bot",
    "message": "Человек остановил бота"
  }
}

В своём коде ориентируйтесь на error.code, а не на текст message: код стабилен, текст может быть переформулирован. HTTP-статус удобен для грубой логики (повторять запрос или нет), code — для точной.

Таблица кодов ошибок

HTTPcodeЧто значитЧто делать
401unauthorizedЗаголовок Authorization не передан вообщеДобавьте заголовок Authorization: Bearer etchtbt_bot_…. Частая причина — прокси или библиотека, вырезающая заголовки
401invalid_keyКлюч неверен, отозван или бот отключёнПроверьте ключ через GET /{channel}/me. Если ключ был отозван — выпустите новый: кабинет → бот → «Для разработчиков» → «Ключи API»
400unsupported_channelКанал бота не поддерживается этим APIInstagram-боты через клиентское API не работают. См. раздел «Частые вопросы»
400recipient_requiredНе указан ни chat_id, ни external_idПередайте один из них в теле запроса
400chat_id_invalidchat_id пустой или в неверном форматеБерите chat_id из GET /{channel}/chats и передавайте строкой
400text_requiredПоле text отсутствует или пустоеТекст сообщения обязателен
400text_too_longТекст длиннее 4000 символовСократите или разбейте на несколько сообщений
400external_id_requiredВ POST /{channel}/contacts/link не передан external_idДобавьте поле external_id
400external_id_too_longexternal_id длиннее 190 символовИспользуйте короткий идентификатор — id пользователя, а не длинную строку с метаданными
402subscription_inactiveПодписка на боте неактивна, отправка запрещенаПродлите подписку в кабинете. До этого запросы на отправку будут отклоняться
404contact_not_foundУ этого бота нет контакта с таким chat_idПроверьте, что chat_id от нужного бота: ключ привязан к одному боту и чужие контакты не видит
404external_id_not_linkedВнешний идентификатор никому не привязанСначала вызовите POST /{channel}/contacts/link, потом отправляйте по external_id
404no_flowНет включённого сценария с триггером «Из другой автоматизации»Создайте такой сценарий в конструкторе и включите его
409recipient_blocked_botЧеловек остановил бота или запретил сообщенияНе повторяйте запрос. Пометьте контакт у себя как недоступный
502send_failedМессенджер не принял сообщениеВременная неполадка на стороне мессенджера. Повторите запрос с паузой

Ограничения

Написать можно только тем, кто уже писал боту

Это главное ограничение, и оно не наше — так устроены сами мессенджеры. До первого обращения человека к боту адреса доставки просто не существует: нет chat_id, некуда отправлять.

  • Telegram. Бот может писать только тем, кто хотя бы раз запускал его — нажал «Старт» или отправил сообщение. Это правило платформы, обойти его нельзя ничем.
  • ВКонтакте. Сообщество может писать пользователю, только если тот разрешил сообщения от сообщества. Обычно разрешение появляется, когда человек сам начинает диалог.
  • Чат на сайте. chat_id появляется в момент, когда посетитель открыл виджет и написал. Пока диалога нет, писать некому.
  • МАКС. Так же: диалог должен быть начат пользователем.

Практический вывод: план «выгрузим базу клиентов из CRM и разошлём всем напоминания в Telegram» не сработает. Работает другой план — сначала привести человека в бота (кнопкой на сайте, ссылкой в письме, QR-кодом), а потом уже общаться с ним через API.

Подписка должна быть активной

Если подписка на боте неактивна, отправка отклоняется с 402 subscription_inactive. Проверить состояние можно заранее:

curl -s https://etochat.bot/api/v2/{channel}/me \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ"
{
  "bot": {"id": 83, "name": "Мой бот", "channel": "webchat", "username": ""},
  "can_send": true
}

Поле can_send: false — сигнал, что отправка сейчас невозможна. Удобно вызывать GET /{channel}/me в мониторинге раз в час: увидите проблему раньше, чем не уйдёт важное сообщение.

Человек мог остановить бота

Любой пользователь в любой момент может нажать «Стоп» или заблокировать бота. Тогда отправка вернёт 409 recipient_blocked_bot. Мы такие контакты отмечаем — в списке контактов у них "blocked_bot": true.

curl -s "https://etochat.bot/api/v2/{channel}/chats?limit=100" \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ"

Перед массовой отправкой имеет смысл пропускать контакты с blocked_bot: true — сэкономите время и не будете разбирать лишние ошибки.

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

ЧтоОграничение
text в POST /{channel}/messagesдо 4000 символов, поддерживает HTML-разметку
buttonsдо 8 штук, только кнопки-ссылки: text + url, url обязан быть http или https
vars в POST /{channel}/triggerдо 20 переменных, значение до 500 символов
Имена переменныхлатиница, цифры и подчёркивание
external_idдо 190 символов, регистр сохраняется
limit в GET /{channel}/chatsпо умолчанию 50, максимум 100
Ключи APIдо 5 живых ключей на одного бота

Ключ привязан к одному боту

Ключ открывает ровно один бот — тот, для которого он выпущен. Он не даёт доступа к другим ботам того же владельца. Если у вас три бота и вы интегрируете все три, выпустите три ключа и храните их отдельно.

Ключ показывается один раз при выпуске. В базе лежит только его хэш (sha256), восстановить исходное значение невозможно — потеряли, значит отзываете и выпускаете новый.

Частые вопросы

Можно ли разослать сообщение по всей клиентской базе?

Нет — только тем из вашей базы, кто уже писал боту. Список таких людей вы получаете через GET /{channel}/chats. Всем остальным доставить сообщение технически невозможно: у них нет диалога с ботом, а значит нет и адреса доставки.

Если задача — именно охватить всю базу, сначала нужно завести людей в бота: ссылка на бота в письме, кнопка на сайте, виджет чата на страницах, QR-код на упаковке. По мере того как люди пишут боту, они появляются в GET /{channel}/chats и становятся доступны для API.

Можно ли написать человеку первым?

Да, но только тому, кто раньше уже обращался к боту. «Первым» в смысле «мы начинаем новый разговор с существующим контактом» — можно и нужно: напомнить о брошенной корзине, сообщить о смене статуса заказа, предупредить об окончании подписки.

«Первым» в смысле «человек никогда не взаимодействовал с ботом» — нельзя ни в одном канале.

Поддерживается ли Instagram?

Нет. Клиентское API работает с четырьмя каналами: чат на сайте, Telegram, ВКонтакте, МАКС. У Instagram жёсткие правила Meta на исходящие сообщения, поэтому произвольная отправка через API там невозможна.

Запрос ключом от Instagram-бота вернёт:

{
  "error": {
    "code": "unsupported_channel",
    "message": "Канал бота не поддерживается"
  }
}

Для Instagram есть отдельное партнёрское API — напишите нам, если оно вам нужно.

Что будет при повторной привязке external_id?

Привязка переедет на новый контакт. Повторный POST /{channel}/contacts/link с тем же external_id, но другим chat_id, отвяжет идентификатор от старого контакта и привяжет к новому.

curl -s -X POST https://etochat.bot/api/v2/{channel}/contacts/link \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"chat_id":"9995735656","external_id":"user-42"}'
{"linked": true, "external_id": "user-42", "chat_id": "9995735656"}

Это удобно, когда человек пришёл к боту с нового устройства или из другого канала: вы просто перепривязываете user-42 на актуальный chat_id, и вся ваша логика продолжает работать по вашему же идентификатору.

Обратите внимание: регистр сохраняется, User-42 и user-42 — разные идентификаторы.

Почему chat_id — строка, а не число?

Потому что у чата на сайте он длиннее 16 цифр, и JavaScript при разборе такого JSON молча теряет точность — получится другой идентификатор, и сообщение уйдёт «в никуда». Поэтому мы всегда отдаём chat_id строкой и просим хранить его строкой: в базе — VARCHAR, в коде — строковый тип, никаких parseInt и приведения к int.

Как понять, что сообщение доставлено?

Успешный ответ выглядит так:

{"sent": true, "message_id": 16641, "chat_id": "9995735656"}

Он означает, что мессенджер принял сообщение в доставку и оно записано в диалог — вы увидите его в кабинете. Это не то же самое, что «человек прочитал»: признака прочтения API не отдаёт.

Если мессенджер сообщение не принял, вы получите ошибку — 502 send_failed при технической неполадке или 409 recipient_blocked_bot, если человек остановил бота. Состояния «вроде отправилось, а вроде нет» не бывает: либо sent: true, либо ошибка с кодом.

Поле message_id стоит сохранять у себя — по нему удобно сопоставлять ваши логи с диалогом в кабинете при разборе жалоб.

Что делать при 502?

502 send_failed — это «мессенджер сейчас не принял, попробуйте ещё раз». Причины обычно временные: перегрузка на стороне мессенджера, сетевой сбой. Правильная реакция — повторить запрос через несколько секунд, 2–3 раза, с увеличивающейся паузой.

Если 502 повторяется на всех сообщениях подряд дольше нескольких минут — скорее всего, проблема на стороне мессенджера. Поставьте отправку в очередь и разберите её позже, а не выжигайте попытки вхолостую.

Что означает 404 no_flow?

Вы вызвали POST /{channel}/trigger, но у бота нет включённого сценария с триггером «Из другой автоматизации». Такой сценарий надо создать в конструкторе автоматизаций и обязательно включить — выключенный сценарий тоже даёт no_flow.

Откуда взять chat_id?

Два способа. Первый — GET /{channel}/chats: там все, кто писал боту. Второй — на странице «Для разработчиков» можно указать до 3 адресов, куда мы будем зеркалить входящие сообщения; в такое уведомление приходит и chat_id ровно в тот момент, когда человек написал боту. Второй способ удобнее, если вы хотите привязывать external_id сразу, без периодического опроса списка контактов.

Рекомендации по надёжности

Повторять при 502, не повторять при 4xx

Простое правило:

  • 502 — временная неполадка, повторяйте с растущей паузой: 1 с, 3 с, 9 с. Дальше — в очередь на потом.
  • 4xx — повторять бессмысленно, ответ не изменится. Ошибка в вашем запросе или в состоянии контакта, её нужно чинить, а не ретраить.
  • 402 subscription_inactive — не повторяйте в цикле. Остановите отправку, оповестите ответственного, продлите подписку.
  • 409 recipient_blocked_bot — не повторяйте никогда. Пометьте контакт у себя и исключите из будущих отправок.
  • 404 external_id_not_linked — единственное разумное исключение из правила «4xx не повторяем»: сделайте POST /{channel}/contacts/link и повторите отправку один раз.

Пример на bash — три попытки, повтор только на 502:

#!/bin/bash
KEY="etchtbt_bot_ВАШ_КЛЮЧ"
BODY='{"chat_id":"9995735656","text":"Заказ №1024 собран и ждёт вас"}'

for delay in 1 3 9; do
  RESP=$(curl -s -w '\n%{http_code}' -X POST \
    https://etochat.bot/api/v2/{channel}/messages \
    -H "Authorization: Bearer $KEY" \
    -H "Content-Type: application/json" \
    -d "$BODY")

  CODE=$(printf '%s' "$RESP" | tail -n1)
  JSON=$(printf '%s' "$RESP" | sed '$d')
  echo "etochat $CODE $JSON"

  if [ "$CODE" = "502" ]; then
    sleep "$delay"
    continue
  fi
  break
done

Та же логика на JavaScript:

async function sendMessage(chatId, text) {
  const delays = [1000, 3000, 9000];

  for (let i = 0; i < delays.length; i++) {
    const res = await fetch('https://etochat.bot/api/v2/{channel}/messages', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + process.env.ETOCHAT_KEY,
        'Content-Type': 'application/json'
      },
      // chat_id всегда строка: без Number() и parseInt()
      body: JSON.stringify({ chat_id: String(chatId), text: text })
    });

    const data = await res.json();
    console.log('etochat', res.status, JSON.stringify(data));

    if (res.status === 502 && i < delays.length - 1) {
      await new Promise(r => setTimeout(r, delays[i]));
      continue;
    }
    if (!res.ok) throw new Error(data.error.code);
    return data;
  }
}

Логируйте ответы у себя

Сохраняйте по каждому вызову: время, эндпоинт, chat_id (или external_id), HTTP-статус, error.code при ошибке и message_id при успехе. Этого достаточно, чтобы через неделю ответить на вопрос «а почему клиенту не пришло напоминание» за минуту, а не за час.

Отдельно считайте долю ошибок по кодам. Резкий рост recipient_blocked_bot — сигнал, что вы пишете слишком часто или не по делу. Рост contact_not_found — скорее всего, у вас в базе протухли chat_id или запросы уходят с ключом не от того бота.

Ключи API в логи не пишите. Храните их в переменных окружения или в секретах, а не в коде и не в репозитории. Если ключ всё же засветился — отзовите его в кабинете и выпустите новый, это занимает несколько секунд.

Не держите отправку в основном потоке

Если сообщение уходит в ответ на действие пользователя на вашем сайте (оформил заказ — получил уведомление в боте), выносите вызов API в фоновую очередь. Тогда медленный ответ мессенджера или временный 502 не затормозят оформление заказа и не покажут вашему клиенту ошибку.


Если ошибка не описана в таблице или поведение API кажется неправильным — напишите нам в поддержку и приложите код ошибки, HTTP-статус, время запроса и id бота из GET /{channel}/me. С этими данными мы найдём ваш запрос в логах и ответим по существу.