Skip to content

Коды ошибок (V2 и ACP) ​

В V2 и ACP все ошибки возвращаются в формате:

json
{
  "time": 1700000000,
  "error": {
    "code": "string",
    "message": "string",
    "details": { "key": "value" }
  }
}
  • code: машинночитаемый код.
  • message: краткое описание.
  • details: детали (валидатор, поля и т.п.); присутствует всегда, но может быть null.

HTTP-статус передаётся только в статус-строке ответа и в теле не дублируется.

Поле message не привязано к таблицам ниже: если доменная ошибка несёт собственное описание, оно подставляется в message поверх дефолтного текста бакета (not_found, invalid, conflict, unsupported, internal), поэтому клиент и логи видят конкретную причину, а не общий текст. Неклассифицированная ошибка тоже отдаётся со статусом 500 и кодом system.internal, но message несёт её реальный текст, а не заглушку «Internal server error». Ошибки разбора и валидации тела запроса приводятся к validation.invalid_input (422).

Источники:

  • тип ошибки API и конструкторы: interfaces/api/apierrors/app_error.go — тонкий ре-экспорт общей библиотеки shared/apierrors
  • маппинг доменных ошибок в ответ и выбор HTTP-статуса: shared/apierrors (Map, StatusForCode)
  • рендер тела ответа: middleware interfaces/api/middlewares/error_handler.go (делегирует в shared/apierrors)
  • доменные коды: domain/errors/errors.go — ре-экспорт shared/domainerr; MARV-специфичные расширения (version vector) в domain/errors/version_conflict.go

Стандартные ошибки API (apierrors) ​

codehttp_statusmessage
auth.unauthorized401Unauthorized
auth.invalid_headers400Invalid headers
auth.invalid_token401Invalid token
auth.token_expired401Token expired
auth.session_replaced401Session replaced by a newer login
auth.client_version_too_low400Client version too low
auth.banned403User is banned
auth.forbidden403Forbidden
validation.invalid_input422Invalid input
validation.conflict409Conflict
system.internal500Internal server error
system.try_again_later503Please try again later
db.select_failed500Database select failed
db.update_failed500Database update failed

Дополнительно при маппинге доменных ошибок:

codehttp_statusmessageисточник
resource.not_found404Not foundmapper
system.unsupported501Not implementedmapper

Ошибки привязки идентичностей (identity) ​

Возвращаются эндпоинтами портируемых аккаунтов (/v2/identities/connect, /disconnect, /resolve-conflict). Определены в interfaces/api/controllers/v2/identities.go.

codehttp_statusmessage
identity.conflict409Login belongs to another account
identity.last_identity422Cannot remove the last identity
identity.last_strong422Cannot remove the last strong login while a guest login remains
identity.not_owned404Identity not linked to this account
identity.unauthorized_link422Cannot link a guest login to a secured account

Доменные коды (domain/errors) ​

Эти коды маппятся в AppError через mapper.go:

domain codeНазначениеМаппинг в API
unauthorizedнет авторизацииauth.unauthorized (401)
forbiddenзапрет доступаauth.forbidden (403)
not_foundресурс не найденresource.not_found (404)
invalidневерные данныеvalidation.invalid_input (422)
conflictконфликт состоянияvalidation.conflict (409)
unsupportedне поддерживаетсяsystem.unsupported (501)
internalвнутренняя ошибкаsystem.internal (500)

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

Доменная ошибка может нести стабильный machine-код (slug) в поле code ответа — тогда клиент ветвится по нему, а message показывает человеку. HTTP‑статус всё равно берётся из бакета (conflict→409, invalid→422, forbidden→403 и т.д.).

Клановые коды (clans) ​

Специфичные code для клан-эндпоинтов (/v2/clans/*), чтобы клиент понимал причину:

codehttp_statusпричина
clan.tag_taken409Тег клана уже занят (глобально уникален)
clan.already_in_clan409Игрок уже состоит в клане
clan.duplicate_request409Активная заявка на вступление уже существует
clan.full422Клан заполнен (member_cap)
clan.not_open422Клан не open — прямой вход недоступен
clan.open_join_directly422Клан open — вступать нужно напрямую (не заявкой)
clan.invite_only422Клан только по приглашению
clan.request_limit422Превышен лимит активных заявок игрока
clan.not_in_clan422Игрок не состоит в клане
clan.leader_must_transfer422Лидер должен передать лидерство перед выходом
clan.target_cannot_lead422Лидерство нельзя передать участнику, которому закрыты кланы: он остаётся в составе, но ничего не может
clan.name_tag_required422Имя и тег клана обязательны
clan.badwords422Имя/тег содержат запрещённые слова
clan.leader_only403Менять настройки клана может только лидер
clans.restricted403Игроку закрыты кланы (ограничение аккаунта clans/block) — ответ на любое его действие с кланом: create, join, join-request, update, leave, member/role, member/kick, transfer-lead, snapshot/update, request/list, request/resolve. Чтения показывают его без клана
clan.platform_mismatch422Клан на другой стороне разделения по платформе (мобильные / остальные), см. split_mobile
clan_chat.already_muted422Участник уже замьючен — активный мьют не перекрывается, ждём истечения
clan.invalid_join_policy422Некорректная политика приёма (join_policy)
clan.invalid_epoch_gate422min_epoch_gate должен быть >= 0
clan.conflict409Прочий конфликт уникальности на клан-эндпоинте

Коды clan.tag_taken / clan.already_in_clan / clan.duplicate_request возвращаются в т.ч. на гонках (перевод Postgres unique_violation в 409, а не 500).

Коды клановых войн (clan_war) ​

Специфичные code для эндпоинтов войны (/v2/clans/war/*). Все, кроме clan_war.participation_locked (bucket forbidden → 403), — bucket invalid → HTTP 422; клиент ветвится по code, а не по общему validation.invalid_input.

codeHTTP статусОписание
clan_war.no_active_war422Активной войны нет
clan_war.no_active_phase422Нет активной фазы войны
clan_war.no_battle_phase422Текущая фаза — не боевая (battle)
clan_war.no_squad_phase422Отрядная фаза не активна
clan_war.not_points_phase422Текущая фаза — не очковая (points)
clan_war.battle_phase_over422Боевая фаза уже завершена
clan_war.no_attacks422У игрока не осталось атак в фазе
clan_war.invalid_target422Некорректная цель атаки
clan_war.target_beaten422Цель уже побеждена в этой фазе
clan_war.battle_in_progress422Цель уже под атакой (эксклюзивный лок) — атака не тратится
clan_war.invalid_points422Некорректное значение очков (должно быть > 0)
clan_war.participation_locked403Игрок отстранён от клановых войн (ушёл из клана во время войны или ограничен оператором): points/report, attack/start, attack/result, final/result. Клиенту достаточно перезапросить war/current — там my_participation

Коды чата кланов (clan_chat) ​

codeHTTP статусОписание
clan_chat.bad_length422Пустое сообщение или длиннее max_message_length
clan_chat.muted422Отправитель замьючен в клане или ему закрыты все клановые чаты (ограничение clans.chat/block)
clan_chat.too_fast422Не выдержан минимальный интервал между сообщениями
clan_chat.rate_limited422Исчерпан лимит сообщений в минуту (burst, отдельный бюджет от интервала)
clan_chat.forbidden403Модерация без ранга coleader+ (delete, mute, все reports)
clan_chat.report_not_pending422Эскалация жалобы, которая не в статусе pending
clan_chat.target_not_member422Цель mute не состоит в клане модератора

clans/chat/send и clans/chat/report без клана возвращают clan.not_in_clan (422, см. таблицу clans).

Коды ограничений аккаунта (restriction, ACP) ​

Возвращают /acp/restrictions/issue и /acp/restrictions/lift. Все — bucket invalid → HTTP 422.

codeHTTP статусОписание
restriction.unsupported422Эта область (scope) не поддерживает этот режим (mode) — выдать можно только проверяемые пары
restriction.past_deadline422Срок until не в будущем
restriction.reason_required422Не указана причина (reason из одних пробелов)
restriction.not_active422Снимаемое ограничение уже снято или истекло

Коды экспериментов (experiment) ​

codeHTTP статусОписание
experiment.has_assignments409У эксперимента есть история назначений — удалить нельзя, его завершают

| experiment.not_deletable | 409 | Удалить можно только черновик (created) — запущенный завершают | | experiment.not_editable | 409 | Параметры правятся только у черновика (created) — создайте новый | | experiment.invalid_transition | 409 | Недопустимый переход: только created → running → finished |

Ошибки version vector (vclock) ​

Version vector используется для оптимистичного контроля конкурентности при обновлении data пользователя. Ошибки возвращаются в стандартном формате AppError c HTTP 409/422 и details содержащим обе версии:

json
{
  "time": 1700000000,
  "error": {
    "code": "version.dominated",
    "message": "Version dominated by server",
    "details": {
      "server_version_vector": { "server": 2, "xxx-device": 3000 },
      "client_version_vector": { "xxx-device": 2999 }
    }
  }
}
codehttp_statusописание
version.dominated409Сервер имеет более новую версию (incoming ≤ server)
version.concurrent409Конфликт: часть полей новее, часть старше
version.server_key_update409Клиент пытается инкрементить ключ «server»
version.unparsable422Невалидный формат version_vector

V1 маппинг: в V1 API те же ошибки возвращаются через числовые статус-коды:

V1 statuscodeописание
3001ErrorVersionDominatedСервер доминирует
3002ErrorVersionConcurrentКонкурентный конфликт
3003ErrorVersionInvalidНевалидный вектор
3004ErrorVersionServerKeyUpdateПопытка инкремента ключа «server»
3005ErrorVersionUnparsableНевозможно распарсить