Stage 4. Response Status Codes
HTTP-статус — это первый и самый короткий ответ сервера о результате операции. Клиентские библиотеки, ретраи, мониторинг и алерты в первую очередь ориентируются именно на код ответа. Поэтому корректные статусы — не “косметика API”, а часть эксплуатационной надежности.
Полезно воспринимать коды как систему из трёх больших групп. 2xx означает успешную обработку. 4xx означает, что проблема в запросе, правах или контексте клиента. 5xx означает сбой на стороне сервера. Если команда стабильно придерживается этой классификации, диагностика инцидентов ускоряется уже на уровне логов и дашбордов.
На практике особенно важны несколько кодов. 200 OK подходит для успешного чтения или обновления с телом ответа. 201 Created показывает, что появился новый ресурс. 204 No Content полезен, когда операция успешна, но тело не требуется. Для ошибок клиента чаще всего нужны 400, 401, 403, 404, 409, 422. Для внутренних сбоев — 500.
Проблема “всегда возвращаем 200” кажется удобной только в начале. Потом это ломает протокольную семантику: клиент вынужден читать кастомные поля в теле, мониторинг перестаёт отличать успех от ошибки, а автоматические ретраи начинают работать неправильно. В результате растёт сложность клиента и ухудшается предсказуемость всей системы.
Статус должен согласовываться с телом ошибки. Если сервер возвращает 409 Conflict, тело должно объяснять конфликт состояния, а не писать абстрактное “something went wrong”. Эта связка особенно важна при интеграциях: партнёрские команды часто автоматизируют реакцию именно по паре “код + тип ошибки”.
{
"error": "conflict",
"message": "order already confirmed",
"traceId": "a733ed51"
}
Отдельно нужно различать 401 и 403. 401 означает, что клиент не аутентифицирован или токен невалиден. 403 означает, что аутентификация есть, но прав недостаточно. Когда эти коды путают, клиенты неправильно строят UX: вместо запроса логина показывают “доступ запрещён” и наоборот.
Код 422 Unprocessable Entity полезен там, где JSON синтаксически корректен, но бизнес-правила не выполняются. Это позволяет отделить “плохой формат” (400) от “понятный запрос, но недопустимое действие” (422). Для сложных форм и валидации это различие повышает качество обратной связи пользователю.
Важно, чтобы одинаковые сценарии в разных endpoint-ах возвращали одинаковые статусы. Если один и тот же тип ошибки в одном месте даёт 409, а в другом 400, клиенту приходится писать лишние ветки обработки, а QA теряет целостную матрицу ожиданий.
Справочная таблица HTTP-статусов
| Класс | Код | Название | Практический смысл |
|---|---|---|---|
| 1xx | 100 | Continue | Сервер получил заголовки запроса, клиент может отправлять тело. |
| 1xx | 101 | Switching Protocols | Сервер согласился переключиться на другой протокол. |
| 1xx | 103 | Early Hints | Предварительный ответ, чтобы клиент начал preload ресурсов. |
| 2xx | 200 | OK | Стандартный успешный ответ. |
| 2xx | 201 | Created | Запрос успешен, создан новый ресурс. |
| 2xx | 202 | Accepted | Запрос принят в обработку, но ещё не завершён. |
| 2xx | 203 | Non-Authoritative Information | Успех, но часть данных может быть из промежуточного/внешнего источника. |
| 2xx | 204 | No Content | Успешно, но без тела ответа. |
| 2xx | 205 | Reset Content | Успешно, без тела; клиенту нужно сбросить форму/представление. |
| 2xx | 206 | Partial Content | Возвращена только часть ресурса (range-запрос). |
| 3xx | 300 | Multiple Choices | Доступно несколько вариантов ресурса/перехода. |
| 3xx | 301 | Moved Permanently | Ресурс навсегда переехал на новый URL. |
| 3xx | 302 | Found | Ресурс временно находится по другому URL. |
| 3xx | 303 | See Other | Результат нужно запрашивать по другому URL (часто после POST). |
| 3xx | 304 | Not Modified | Ресурс не изменился, можно использовать кэш. |
| 3xx | 307 | Temporary Redirect | Временный редирект с сохранением исходного HTTP-метода. |
| 3xx | 308 | Permanent Redirect | Постоянный редирект с сохранением исходного HTTP-метода. |
| 4xx | 400 | Bad Request | Сервер не может обработать запрос из-за некорректного синтаксиса/формы. |
| 4xx | 401 | Unauthorized | Нет валидной аутентификации или она не пройдена. |
| 4xx | 402 | Payment Required | Зарезервированный код, обычно не используется в обычных API. |
| 4xx | 403 | Forbidden | Запрос понятен, но операция запрещена. |
| 4xx | 404 | Not Found | Запрошенный ресурс не найден. |
| 4xx | 405 | Method Not Allowed | Для ресурса не поддерживается указанный HTTP-метод. |
| 4xx | 406 | Not Acceptable | Сервер не может отдать ответ в формате из Accept. |
| 4xx | 407 | Proxy Authentication Required | Сначала нужна аутентификация у прокси. |
| 4xx | 408 | Request Timeout | Сервер не дождался запроса в допустимое время. |
| 4xx | 409 | Conflict | Конфликт с текущим состоянием ресурса. |
| 4xx | 410 | Gone | Ресурс удалён и более недоступен. |
| 4xx | 411 | Length Required | Отсутствует обязательный заголовок Content-Length. |
| 4xx | 412 | Precondition Failed | Условие в precondition-заголовках не выполнено. |
| 4xx | 413 | Request Too Large | Тело запроса превышает лимит сервера. |
| 4xx | 414 | Request-URI Too Long | URI слишком длинный для обработки сервером. |
| 4xx | 415 | Unsupported Media Type | Неподдерживаемый тип данных запроса. |
| 4xx | 416 | Range Not Satisfiable | Невозможно отдать запрошенный диапазон байтов. |
| 4xx | 417 | Expectation Failed | Сервер не может выполнить требования заголовка Expect. |
| 5xx | 500 | Internal Server Error | Общая внутренняя ошибка сервера. |
| 5xx | 501 | Not Implemented | Сервер не поддерживает запрошенный метод/поведение. |
| 5xx | 502 | Bad Gateway | Шлюз/прокси получил некорректный ответ от upstream-сервиса. |
| 5xx | 503 | Service Unavailable | Сервис временно недоступен (перегрузка/обслуживание). |
| 5xx | 504 | Gateway Timeout | Шлюз/прокси не дождался своевременного ответа от upstream. |
| 5xx | 505 | HTTP Version Not Supported | Версия HTTP в запросе не поддерживается. |
| 5xx | 511 | Network Authentication Required | Для доступа к сети требуется аутентификация. |
Практический сценарий
В платёжном API конфликт повторной операции в одном endpoint-е возвращал 409, а в другом — 200 с полем success=false. Из-за этого мобильный клиент в части случаев считал операцию успешной и не показывал пользователю ошибку повторного платежа. После унификации статусов и error-format поведение стало предсказуемым: клиент корректно отличает успех от конфликта, а мониторинг начал показывать реальную картину проблем.