К содержанию
ActionPulse

Коды ответа: 400, 401, 402, 403, 404, 409, 413, 422, 429

Разбор каждого кода отказа ActionPulse по симптомам — какие машинные коды за ним стоят, как отличить причины друг от друга, чем проверить и что безопасно исправить.

Кому
Разработчикам, получающим отказ от API
Проверено
На этой странице

Код HTTP говорит только о классе отказа. Настоящую причину несёт поле error.code в теле ответа: за одним 403 стоят восемь разных кодов, за 429 — три, а 413 внутри вообще помечен как validation_failed. Отсюда первое правило: читайте тело, а не только статус.

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

Как читать ответ

Все отказы приходят в одном конверте:

{ "error": { "code": "validation_failed", "message": "batch is empty", "details": {} } }

code — машинный, по нему пишут условия в коде. message — человекочитаемый и может меняться, не завязывайтесь на него. details — необязательный объект, состав которого зависит от кода: адрес оплаты, признак «можно повторить».

Учитывайте, что поверхностей две и они отвечают немного по-разному:

Поверхность База Авторизация Особенность
Приём событий адрес приёма, /v1/track и другие /v1/* ключ проекта 202 приходит и при частичных отказах
Основной API /api/v1 токен доступа отказ по правам скрывается за 404

Отказ авторизации и приостановка доступа

401 — запрос отклонён как неавторизованный

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

Что произошло Где встречается
Заголовка Authorization нет вовсе сообщение missing credential
Значение не разбирается, отозвано или истекло сообщение invalid credential
Браузерный токен или серверный ключ передан в адресе ?key= в адресе принимается только pp_wk_…
Не браузерный токен в поле credential тела запроса это поле только для pp_bt_…
Токен доступа основного API просрочен срок жизни токена доступа — 15 минут

Как проверить. Отправьте на приём заведомо пустой пакет: авторизация проверяется до разбора тела, поэтому 422 означает, что ключ принят, а любой другой ответ — что дело в ключе. Событий такой запрос не создаёт.

curl -sS -o /dev/null -w '%{http_code}\n' \
  -X POST "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/track" \
  -H "Authorization: Bearer $ACTIONPULSE_SERVER_KEY" \
  -d '{"batch":[]}'

Для основного API тот же смысл несёт любой безопасный GET — например /api/v1/projects/YOUR_PROJECT_ID с заголовком Authorization.

Безопасное исправление. Сверьте префикс ключа — первые 8 символов после второго подчёркивания — со списком credentials проекта: там видно тип, срок и статус. Если ключ активен, а запрос всё равно 401, проблема в сборке заголовка: должно быть ровно Authorization: Bearer <значение>. Для основного API обновите токен доступа, а не выпускайте новые ключи.

Если не помогло. Значение могло быть обрезано при копировании или подстановке шаблонизатором: у браузерных и серверных ключей секрет — ровно 32 символа. Полный разбор — в ключах и origin.

403 — доступ есть, но запрещён именно этот запрос

Вероятная причина. Аутентификация прошла, отказала авторизация. Отличать причины нужно строго по error.code:

Код Что означает
forbidden роли пользователя не хватает для действия
credential_scope_forbidden тип ключа или его область действия не подходят для этого запроса
credential_origin_forbidden origin браузерного запроса не совпал со списком токена
credential_rotation_required ключ выпущен до разделения прав на связывание, нужна ротация
feature_locked тариф действует, но возможность в него не входит
feature_locked_trial возможность откроется после оплаты, в пробном периоде закрыта
trial_pending_review аккаунт на проверке, приём приостановлен
license_invalid только в установке на своих серверах: лицензия недействительна или просрочена

Как проверить. Посмотрите error.code и details.feature, если он есть: последний прямо называет заблокированную возможность. Для forbidden сравните свою роль с требуемой (роли в организации: owner, admin, analyst, viewer), для credential_* — посмотрите тип ключа в списке credentials проекта.

Безопасное исправление. Отказ по роли снимает администратор организации — повысив роль или выполнив действие сам. Отказ по ключу лечится ключом нужного типа: связывание идентификаторов — серверным, записи сессий — браузерным. Отказ по тарифу — переходом на подходящий тариф, правки запроса его не обходят.

Если не помогло. credential_rotation_required приходит с details.rotation_required и касается только операций идентификации: ротация ключа сохраняет весь профиль и решает вопрос. Разбор 403 по симптомам — в ключах и origin.

404 — ресурс не найден, хотя он существует

Вероятная причина. У 404 три смысла, и они неразличимы намеренно: объекта действительно нет; объект есть, но принадлежит другой организации или другому проекту — отказ по правам скрыт под «не найдено», чтобы перебором нельзя было узнать чужие идентификаторы; маршрут не смонтирован в этом режиме установки — методы оплаты отсутствуют в установке на своих серверах, а установка лицензии в облаке.

Как проверить. Запросите список, а не конкретный объект: GET /api/v1/projects возвращает только доступные вам проекты. Если идентификатор из неудачного запроса в списке отсутствует — это второй случай, а не первый.

Безопасное исправление. Проверьте, что работаете в нужной организации: токен доступа привязан к организации, и проект другой организации не виден. Если нужен доступ — попросите администратора пригласить вас. Для третьего случая сверьтесь с режимом установки: в облаке GET /api/v1/billing отвечает полем mode, а в установке на своих серверах сам отвечает 404 — это и есть признак режима.

Если не помогло. Убедитесь, что не потеряли базовый путь: у основного API это /api/v1, а приём живёт на другом адресе и не под /api/v1 — запрос к приёму по адресу кабинета даст 404 независимо от прав.

402 — оплата или пробный период

Вероятная причина. Приём и изменения приостановлены по биллингу, а не по технической причине. Два кода:

Код Что означает
trial_expired пробный период закончился без оплаты
subscription_required нет действующей подписки и нет живого пробного периода

Ответ приходит на приём событий, на изменяющие запросы и на зависящие от тарифа возможности. В details.billing_url, когда он есть, лежит адрес страницы оплаты.

Как проверить. Откройте раздел оплаты в кабинете или запросите состояние подписки — GET /api/v1/billing (метод доступен в облаке).

Безопасное исправление. Оплатите тариф в кабинете. Никакие изменения клиента не помогут; повторы бессмысленны, пока подписка не восстановлена. Данные при этом не удаляются: приём стоит на паузе.

Если не помогло. Проверьте, что смотрите на нужную организацию: квоты, подписка и пробный период — свойства организации, а не проекта. У только что зарегистрированного аккаунта отказ может быть другим — 403 trial_pending_review.

Запрос не принят

413 — тело запроса больше предела

Вероятная причина. Превышен байтовый предел метода. Код внутри — validation_failed, отдельного кода у 413 нет. Пределы разные:

Запрос Предел тела
POST /v1/track 256 КБ
POST /v1/identify и POST /v1/alias 64 КБ
POST /v1/replay 800 КБ вместе с обёрткой JSON и base64
POST /v1/picker/capture 8 КБ

Пределы остальных методов в контракте не зафиксированы — не рассчитывайте на конкретное число там, где оно не названо.

Как проверить. Измерьте тело, которое реально уходит, а не исходные данные: в оболочке это printf '%s' "$BODY" | wc -c, в коде — длина сериализованной строки в байтах.

Безопасное исправление. Для приёма режьте пакет: не больше 500 элементов и не больше 256 КБ в теле, по тому пределу, который наступит раньше. Если одно событие не влезает в одиночку, уменьшайте свойства: предел значения — 8 КБ.

Если не помогло. Считайте байты, а не символы: русская буква в UTF-8 — два байта, эмодзи — четыре. Подробности — в лимитах приёма.

422 — запрос не прошёл проверку

Вероятная причина. Основной код — validation_failed, и разбор тела строгий: неизвестное поле в любом месте структуры отклоняет весь запрос, а не игнорируется. У нескольких методов 422 несёт свой код:

Код Когда
validation_failed форма запроса, пустой пакет, превышение числа элементов
confirmation_required не передан обязательный заголовок подтверждения опасного действия
disposable_email домен адреса при регистрации принадлежит сервису одноразовой почты
license_invalid установка лицензии отклонена (только на своих серверах)

Отдельный случай — правила именованных событий: у них в details.reason приходят устойчивые значения segment_not_found, segment_cycle, segment_complexity_limit, unsupported_segment_rule.

Как проверить. Сравните тело с контрактом метода в справочнике API и проверьте его на лишние поля. Для приёма самые частые сообщения — invalid JSON body, batch is empty, batch exceeds 500 items.

Безопасное исправление. Уберите свои поля из структуры запроса: на приёме всё своё кладётся в props, а не рядом с ним. Для опасных операций добавьте требуемый заголовок подтверждения — он существует, чтобы случайный запрос не удалил данные.

Если не помогло. Сверьтесь с реестром событий проекта: в строгом режиме несовпадение схемы приходит не как 422, а как поэлементный отказ schema_violation внутри 202.

400 — точечный отказ вместо 422

Вероятная причина. 400 встречается ровно на трёх группах методов: захват элемента выбором на странице; операции с конкретным правилом именованных событий — чтение, изменение, удаление, включение и выключение; публичная страница отписки от писем. Код внутри тот же validation_failed; на страницах отписки вместо конверта приходит HTML.

Как проверить. Посмотрите путь запроса. У /v1/picker/capture проверьте тело: selector до 128 байт, url до 2048 байт и обязательно http/https, text до 64 байт. У правила именованных событий — идентификатор в пути: он целый и положительный.

Безопасное исправление. Приведите тело к пределам метода. Для захвата элемента проще перезапустить выбор из кабинета: токен захвата одноразовый и живёт 10 минут, повторно использованный даёт 409.

Если не помогло. Отказ 400 от промежуточного узла — прокси, WAF, балансировщика — выглядит иначе: обычно это HTML или пустое тело без конверта error. Проверьте, доходит ли запрос до ActionPulse вообще.

Конфликт состояния

409 — состояние не позволяет выполнить запрос

Вероятная причина. Запрос корректен, но состояние не позволяет его выполнить. Коды различаются сильно, и лечатся они по-разному:

Код Когда
privacy_job_active идёт удаление данных субъекта; identify и alias временно закрыты, details.retryable = true
picker_token_consumed одноразовый токен захвата элемента уже использован
demo_project операция недоступна для демонстрационного проекта: ключи приёма, импорты, повторное заполнение
idempotency_conflict тот же ключ идемпотентности прислан с другим телом
domain_has_org при регистрации: организация на этом корпоративном домене уже есть
phone_already_used номер уже использовался для пробного периода менее 90 дней назад
sms_unavailable подтверждение по SMS к текущему состоянию аккаунта не применимо
без отдельного кода имя события или версия схемы в реестре уже заняты; задание импорта уже завершилось и не отменяемо

Как проверить. Для privacy_job_active посмотрите активные задания удаления: GET /api/v1/projects/YOUR_PROJECT_ID/gdpr/forget. Для idempotency_conflict сравните тело повторного запроса с первым: при совпадении тот же ключ даёт 200 и заголовок Idempotency-Replayed.

Безопасное исправление. privacy_job_active — единственный случай, который лечится ожиданием: повторите после завершения задания, признак details.retryable для этого и стоит. Для конфликтов имён выберите другое имя или измените существующую запись. Для одноразового токена — выпустите новый.

Если не помогло. Не «переоткрывайте» операцию новым ключом идемпотентности, если не уверены, что первая не применилась: сначала прочитайте состояние объекта. Отметку о деплое, например, безопаснее найти в списке отметок.

Ограничения и время

429 — слишком часто или закончился объём

Вероятная причина. Один статус, три разных кода, и путать их дорого:

Код Что означает Повтор помогает
rate_limited превышена частота запросов да, после Retry-After
quota_exceeded исчерпан объём событий тарифа нет
trial_quota_exceeded исчерпан объём событий пробного периода нет

rate_limited означает «слишком быстро» и лечится задержкой; оба остальных — «объём закончился», и повторы только жгут лимит частоты.

Как проверить. Прочитайте и код, и заголовки:

curl -sS -D - -o /dev/null \
  -X POST "https://YOUR_ACTIONPULSE_INGEST_HOST/v1/track" \
  -H "Authorization: Bearer $ACTIONPULSE_SERVER_KEY" \
  -d '{"batch":[]}'

Retry-After в секундах приходит вместе с rate_limited. У ответов про объём его нет — это надёжный признак, что ждать бесполезно.

Безопасное исправление. Соблюдайте Retry-After, дальше повторяйте с экспоненциальной задержкой. Отправляйте пакетами: стоимость запроса на приёме считается как большее из двух — число событий в пакете и размер тела в килобайтах. При quota_exceeded меняйте тариф или дожидайтесь периода.

Если не помогло. Из браузера заголовок Retry-After скрипту недоступен: приём намеренно не открывает странице список заголовков ответа для запросов записи. Смотрите его во вкладке «Сеть». Подробности про бюджеты — в ограничениях частоты.

408 и 504 — запрос не успел

Вероятная причина. Оба статуса несут один код query_timeout: запрос не уложился в предел времени этого метода. Какой статус придёт, зависит от метода, поэтому обрабатывайте 408 и 504 одинаково. Единого предела времени у API нет — он свой у каждого метода.

Как проверить. Убедитесь, что это query_timeout, а не таймаут вашего клиента или прокси: у отказа от ActionPulse есть конверт error, у обрыва соединения — нет.

Безопасное исправление. Сузьте запрос: короче интервал дат, меньше фильтров и разбивок. Тяжёлые выборки берите выгрузкой, а не запросом отчёта. Повтор без сужения почти всегда даёт тот же результат.

Если не помогло. У потоковых выгрузок отказ выглядит иначе: статус остаётся 200, а результат приходит в трейлере X-PP-Export-Status со значением timeout. Разбирайте каждую строку и проверяйте трейлер — его отсутствие не является доказательством успеха.

503 — сервис временно недоступен

Вероятная причина. Недоступна одна из подсистем: реестр событий, конфигурация сбора, состояние записей сессий, хранилище захвата. Важная деталь: часть ответов 503 означает, что изменение уже применено и ожидает только побочного эффекта. Считать 503 за «ничего не произошло» нельзя.

Как проверить. Посмотрите message — он называет подсистему. Затем проверьте живость: GET /healthz отвечает 200 у обоих процессов, когда их зависимости на связи.

Безопасное исправление. Для чтений — повторите позже. Для изменений с ключом идемпотентности повтор с тем же ключом безопасен и штатен. Для изменений без ключа сначала прочитайте состояние объекта.

Если не помогло. Если 503 приходит на конфигурацию сбора, трекер остаётся в безопасном состоянии и выключает необязательный сбор: базовые события продолжат идти, автосбор и записи — нет.

Что повторять, а что нет

Код Повторять
408, 504, 429 rate_limited, 503, 5xx да, с задержкой; при 429 — после Retry-After, при таймауте — сузив запрос
409 privacy_job_active да, после завершения задания
400, 401, 402, 403, 404, 413, 422 нет, повтор не изменит результат
429 quota_exceeded, trial_quota_exceeded нет, нужен другой тариф или следующий период

Что проверить

  1. В коде клиента ветвление идёт по error.code, а не только по статусу HTTP.
  2. Ответы приёма проверяются на непустой rejected202 не означает «всё принято».
  3. Повторяются только коды из таблицы выше, с экспоненциальной задержкой и уважением к Retry-After.
  4. В логи приложения попадают код, статус и префикс ключа — и никогда само значение ключа.
  5. Для выгрузок проверяется трейлер X-PP-Export-Status, а не код ответа.