Частые ошибки при работе с HTTP-статусами в REST API?

Содержание статьи

Введение

Консультирую для крупных компании и команды в оптимизации архитектуры, устранении узких мест и снижении стоимости владения IT-продуктами я регулярно сталкиваюсь с тем, что даже в зрелых командах допускаются одни и те же ошибки при работе с HTTP-статусами в REST API.

На первый взгляд, может показаться, что статус ответа сервера – это формальность. Но на практике именно некорректно выбранные HTTP-коды:

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

Эта статья – не просто теория. Это 30 реальных ошибок, которые я наблюдал в промышленных окружениях. Для каждой из них я постараюсь объяснить:

  1. В чём заключается ошибка,
  2. Пример, где это вызвало сложности,
  3. Почему это негативно влияет на продукт,
  4. Как рекомендуется делать,
  5. Почему именно это улучшает архитектуру и разработку в целом.
  6. Пример запроса
  7. Является ли этот пункт спорным

Также в конце статьи вы найдёте таблицу, в которой указано, какие рекомендации строго регламентированы спецификациями (RFC), какие являются best practices, а где следует принимать решение в зависимости от контекста.

Если вы проектируете REST API или отвечаете за сопровождение распределённой системы – это руководство сэкономит вам часы отладки, десятки сообщений в Telegram/Slack и сотни строк избыточного кода.

1. “200 OK” при частичных ошибках

Проблема: Сервер возвращает “200 OK”, даже если не все операции были успешными.

Пример из жизни: Клиент отправляет массив заказов на создание. Один из них содержит недопустимые данные. Сервер создаёт 4 из 5, но возвращает “200 OK”.

В чем именно проблема: Клиент не знает, что один из заказов не создан. Это приводит к потере данных и ошибкам в UI.

Рекомендация: Возвращать “207 Multi-Status” или JSON с перечислением успешных и неуспешных элементов.

В чем именно улучшение: Клиент получает прозрачную информацию о том, что именно обработано.

Пример запроса:

POST /orders/bulk HTTP/1. 1
Content-Type: application/json

[
  {"item": "apple", "quantity": 5},
  {"item": "banana", "quantity": -2},  // ошибка
  {"item": "milk", "quantity": 1}
]

Спорный момент: нет

2. “500 Internal Server Error” при удалённом API

Проблема: API возвращает “500”, если сторонний сервис не отвечает.

Пример из жизни: Ваш API запрашивает курсы валют с внешнего ресурса, который временно недоступен. Клиент получает “500”.

В чем именно проблема: Клиент думает, что ошибка на вашем сервере.

Рекомендация: Возвращать “502 Bad Gateway” или “504 Gateway Timeout”.

В чем именно улучшение: Клиенту ясно, что сбой произошёл не на вашей стороне.

Пример запроса:

GET /exchange-rates HTTP/1. 1
Accept: application/json

Спорный момент: нет

3. Скрытие “403 Forbidden” ошибок за “404 Not Found”

Проблема: Сервер возвращает “404”, если пользователь не имеет доступа к ресурсу.

Пример из жизни: У сотрудника нет прав на просмотр “/admin/metrics”, но он получает “404”, хотя путь существует.

В чем именно проблема: Пользователь не понимает, что ему не хватает прав.

Рекомендация: Использовать “403 Forbidden”, если ресурс есть, но доступ ограничен.

В чем именно улучшение: Клиенту понятно, что он авторизован, но у него нет нужных прав.

Пример запроса:

GET /admin/metrics HTTP/1. 1
Authorization: Bearer eyJhbGci...

Спорный момент:
Иногда уместно «маскировать» наличие ресурса (особенно в безопасности – чтобы не раскрывать структуру API).

Контекст:

  • Если API – публичное и неавторизованное: “404” может быть разумным выбором.
  • Если пользователь авторизован и ожидает результат – “403” лучше.

Вывод:
“403” уместен для доверенных клиентов, “404” – для безопасности в zero-knowledge API.

4. Использование “200 OK” при создании ресурса вместо “201 Created”

Проблема: После POST-запроса сервер возвращает “200 OK”.

Пример из жизни: Клиент отправляет данные нового пользователя. Ресурс создаётся, но возвращается “200”.

В чем именно проблема: Непонятно, был ли ресурс создан или обновлён.

Рекомендация: Возвращать “201 Created” + “Location” заголовок.

В чем именно улучшение: Чёткий сигнал, что ресурс успешно создан.

Пример запроса:

POST /users HTTP/1. 1
Content-Type: application/json

{
  "name": "John",
  "email": "john@mail.com"
}

Спорный момент: нет

5. “200 OK” при ошибках

Проблема: Даже при логических ошибках возвращается “200 OK”.

Пример из жизни: Запрос на несуществующего пользователя возвращает “{“error”: “User not found”}” с “200”.

В чем именно проблема: UI не отображает ошибку, так как статус говорит об успехе.

Рекомендация: Используйте “404 Not Found”, “400”, “401” и др.

В чем именно улучшение: Клиенты обрабатывают ошибки корректно.

Пример запроса:

GET /users/999999 HTTP/1. 1
Accept: application/json

Спорный момент: нет

6. Неиспользование “406 Not Acceptable”

Проблема: Игнорировать “Accept”-заголовок клиента.

Пример из жизни: Клиент запрашивает XML, а получает JSON, который не может распарсить.

В чем именно проблема: Нарушается контракт между клиентом и сервером.

Рекомендация: Возвращать “406 Not Acceptable”.

В чем именно улучшение: Клиент знает, что сервер не поддерживает нужный формат.

Пример запроса:

GET /products HTTP/1. 1
Accept: application/xml

Спорный момент:
Многие современные API не поддерживают множественные форматы, а всегда возвращают JSON.

Контекст:

  • На практике клиенты просто не используют “Accept”, а сервер – не проверяет.
  • Использование “406” оправдано, только если реально поддерживается “Content Negotiation”.

Вывод:
Необязательно проверять “Accept”, если API возвращает только один формат.

7. “400 Bad Request” вместо “415 Unsupported Media Type”

Проблема: Некорректный “Content-Type” обрабатывается как “400”.

Пример из жизни: Клиент отправляет “text/plain”, а сервер не может распарсить.

В чем именно проблема: Ошибка неявная, непонятно в чём дело.

Рекомендация: Возвращать “415 Unsupported Media Type”.

В чем именно улучшение: Указывает на реальную причину ошибки – неправильный формат данных.

Пример запроса:

POST /users HTTP/1. 1
Content-Type: text/plain

name=Ivan

Спорный момент: нет

8. Игнорирование “304 Not Modified”

Проблема: Сервер каждый раз возвращает полный ответ, даже если данные не менялись.

Пример из жизни: Клиент загружает профиль при каждом входе, даже если ничего не изменилось.

В чем именно проблема: Нагрузка на сеть и сервер.

Рекомендация: Используйте “ETag” и “If-None-Match”, возвращайте “304”.

В чем именно улучшение: Оптимизация трафика и производительности.

Пример запроса:

GET /profile HTTP/1. 1
If-None-Match: "abc123"

Спорный момент: нет

9. “404 Not Found” при неверном HTTP-методе

Проблема: Отправка метода, который не поддерживается – “404 Not Found”.

Пример из жизни: “DELETE” по адресу “/users” возвращает “404”, хотя “GET” по тому же адресу работает.

В чем именно проблема: Клиент думает, что ресурса нет.

Рекомендация: Использовать “405 Method Not Allowed” и “Allow”-заголовок.

В чем именно улучшение: Клиент получает корректную информацию о доступных методах.

Пример запроса:

DELETE /users HTTP/1. 1

Спорный момент: нет

10. “200 OK” при DELETE, даже если ничего не удалено

Проблема: Возвращается “200 OK”, даже если ресурс уже удалён.

Пример из жизни: Пользователь нажимает «удалить», получает “200”, но пользователь уже не существует.

В чем именно проблема: Вводит в заблуждение – кажется, что операция успешна.

Рекомендация: Возвращать “404 Not Found” или “204 No Content” в зависимости от результата.

В чем именно улучшение: UI получает достоверную информацию и может отреагировать корректно.

Пример запроса:

DELETE /users/12345 HTTP/1. 1

Спорный момент: нет

11. “201 Created” без “Location”-заголовка

Проблема: После создания ресурса возвращается “201 Created”, но без “Location”.

Пример из жизни: После создания пользователя клиент не знает, где он теперь доступен.

В чем именно проблема: Невозможно сразу перейти к новому ресурсу.

Рекомендация: Возвращать “Location: /users/{id}” в заголовке ответа.

В чем именно улучшение: Клиент может автоматически перейти к ресурсу или сохранить его путь.

Пример запроса:

POST /users HTTP/1. 1
Content-Type: application/json

{
  "name": "John",
  "email": "John@mail.com"
}

Спорный момент: нет

12. “500 Internal Server Error” при ошибке клиента

Проблема: Невалидный запрос приводит к “500”.

Пример из жизни: Отправка пустого запроса в поиск -> “500 Internal Server Error”.

В чем именно проблема: Клиент не понимает, что ошибка в запросе.

Рекомендация: Возвращать “400 Bad Request”.

В чем именно улучшение: Явное указание, что проблема на стороне клиента.

Пример запроса:

GET /search?q= HTTP/1. 1

Спорный момент:
Некоторые ошибки клиента могут привести к настоящей 500, если нет валидации на раннем этапе.

Контекст:

  • Плохая реализация может упасть при “null” в JSON – это реальный баг.
  • Но по смыслу это “400”.

Вывод:
Хорошо валидировать входные данные. Если сервер не выдержал плохой вход – это его вина, но ошибка всё равно от клиента.

13. “500 Internal Server Error” при недоступности базы данных

Проблема: В момент сбоя БД API возвращает “500”.

Пример из жизни: Во время деплоя Postgres не доступен – мониторинг сообщает, что API упал.

В чем именно проблема: Нельзя отличить баг от внешнего сбоя.

Рекомендация: Возвращать “503 Service Unavailable” и “Retry-After”.

В чем именно улучшение: Клиент знает, что сервис временно не работает и стоит подождать.

Пример запроса:

GET /users HTTP/1. 1

Спорный момент: нет

14. “200 OK” при сбое внешнего API

Проблема: Ответ сервера – “200 OK”, даже если внешний API не ответил.

Пример из жизни: API для оплаты не отвечает, но заказ всё равно отображается как «оплачен».

В чем именно проблема: Клиент получает ложную информацию.

Рекомендация: Возвращать “502 Bad Gateway” или “504 Gateway Timeout”.

В чем именно улучшение: Клиент видит, что проблема во внешнем компоненте, и может повторить позже.

Пример запроса:

POST /payment HTTP/1. 1
Content-Type: application/json

{
  "orderId": "abc123",
  "amount": 199.99
}

Спорный момент: нет

15. Ответ без заголовка “Content-Type”

Проблема: Сервер не указывает “Content-Type” в ответе.

Пример из жизни: Клиент получает ответ, не зная, в каком формате – JSON, XML или HTML.

В чем именно проблема: Невозможно безопасно парсить ответ.

Рекомендация: Всегда указывать “Content-Type” в ответах.

В чем именно улучшение: Клиенты могут безопасно обработать содержимое.

Пример запроса:

GET /products HTTP/1. 1

Спорный момент: нет

16. Неправильное использование “401 Unauthorized” для истёкшего токена

Проблема: Токен истёк, и сервер возвращает “401 Unauthorized”.

Пример из жизни: Приложение считает, что пользователь вообще не авторизован и принудительно выходит.

В чем именно проблема: Потеря пользовательского состояния.

Рекомендация: Возвращать “403 Forbidden” с сообщением об истечении токена.

В чем именно улучшение: Клиент может инициировать обновление токена, не разлогинивая пользователя.

Пример запроса:

GET /account HTTP/1. 1
Authorization: Bearer expired_token

Спорный момент:
Стандарты OAuth 2.0 и RFC6750 рекомендуют использовать “401” даже для истекших токенов.

Контекст:

  • “401” означает: нужна повторная авторизация (в том числе токен протух).
  • “403” – если токен действителен, но недостаточно прав.

Вывод:
“401” корректен даже при истечении токена, но в теле лучше явно указать причину (“token_expired”).

17. “200 OK” при ошибках в query-параметрах

Проблема: Параметр запроса некорректный, но сервер возвращает “200 OK” с пустым ответом.

Пример из жизни: Поиск по “?filter=unknown” возвращает пустой список, хотя фильтр не поддерживается.

В чем именно проблема: UI не понимает, что фильтр неправильный.

Рекомендация: Возвращать “400 Bad Request” или “422 Unprocessable Entity”.

В чем именно улучшение: Клиент может уведомить пользователя и предложить исправление.

Пример запроса:

GET /users?filter=unknown_field HTTP/1. 1

Спорный момент: нет

18. Игнорирование “ETag” в GET-запросах

Проблема: Сервер не использует “ETag”, всегда возвращает тело.

Пример из жизни: Страница профиля обновляется каждые 5 сек, даже если ничего не менялось.

В чем именно проблема: Избыточная передача данных.

Рекомендация: Использовать “ETag”, “If-None-Match”, и возвращать “304 Not Modified”.

В чем именно улучшение: Повышение производительности и экономия трафика.

Пример запроса:

GET /profile HTTP/1. 1
If-None-Match: "xyz789"

Спорный момент:
Многие API не реализуют кэширование через “ETag”, так как это сложно и не требуется для бизнес-данных.

Контекст:

  • “ETag” особенно полезен при больших и часто запрашиваемых неизменяемых данных (например, CDN).
  • В REST API можно обойтись ручным кэшированием на клиенте.

Вывод:
“ETag” не обязателен, но полезен для оптимизации; зависит от задач.

19. Неиспользование “202 Accepted” для асинхронных задач

Проблема: Операция выполняется в фоне, но сервер возвращает “200 OK”.

Пример из жизни: Генерация отчёта занимает 30 сек, но клиент получает “200” сразу и думает, что он готов.

В чем именно проблема: Пользователь не получает реального статуса выполнения.

Рекомендация: Возвращать “202 Accepted” и “Location” на ресурс статуса.

В чем именно улучшение: Клиент может отслеживать прогресс и понимать, что задача в процессе.

Пример запроса:

POST /reports HTTP/1. 1
Content-Type: application/json

{
  "type": "monthly"
}

Спорный момент: нет

20. Ошибки в “OPTIONS”-запросах при CORS

Проблема: Сервер не обрабатывает “OPTIONS”-запросы корректно.

Пример из жизни: Браузер делает preflight-запрос, но получает “404”. Основной запрос блокируется.

В чем именно проблема: Кросс-доменные запросы не работают, UI ломается.

Рекомендация: Обрабатывать “OPTIONS” с нужными заголовками: “Access-Control-Allow-Origin”, “Access-Control-Allow-Methods”, и др.

В чем именно улучшение: Браузер получает разрешение и выполняет основной запрос.

Пример запроса:

OPTIONS /api/data HTTP/1. 1
Origin: https://client.app
Access-Control-Request-Method: POST

Спорный момент: нет

21. Использование “302 Found” после POST

Проблема: После успешного POST сервер делает редирект с “302 Found”.

Пример из жизни: После создания заказа браузер автоматически повторяет POST по редиректу – создаётся дубликат.

В чем именно проблема: Может привести к повторной отправке формы и созданию дубликатов.

Рекомендация: Используйте “303 See Other” для безопасного редиректа после POST.

В чем именно улучшение: Браузер делает “GET”, не повторяя POST.

Пример запроса:

POST /checkout HTTP/1. 1
Content-Type: application/json

{
  "cartId": "a1b2c3"
}

Спорный момент: нет

22. “500 Internal Server Error” при ошибке CORS

Проблема: При нарушении CORS возвращается “500”.

Пример из жизни: Запрос с “Origin: otherdomain.com” блокируется, но клиент получает “500” и считает, что сервер сломан.

В чем именно проблема: Это не внутренняя ошибка сервера.

Рекомендация: Возвращать “403 Forbidden” с пояснением, что CORS-политика нарушена.

В чем именно улучшение: Клиент понимает, что ему не разрешён доступ.

Пример запроса:

GET /api/private HTTP/1. 1
Origin: http://untrusted-site.com

Спорный момент:
При ошибках CORS в браузере сервер вообще не доходит до запроса, и ответ не читается.

Контекст:

  • CORS блокируется на уровне браузера, и даже “403” может не дойти до JS.
  • Обычно достаточно правильно настроенных заголовков (“Access-Control-Allow-Origin” и др.).

Вывод:
“403” может быть теоретически верным, но не поможет, если CORS заблокирован на этапе preflight.

23. Отсутствие “Allow”-заголовка при “405 Method Not Allowed”

Проблема: Метод запрещён, но не указано, какие методы допустимы.

Пример из жизни: Клиент пытается сделать “PUT”, получает “405”, но не знает, что делать дальше.

В чем именно проблема: Нельзя программно адаптироваться к API.

Рекомендация: Возвращать “Allow: GET, POST”.

В чем именно улучшение: Клиент может скорректировать свой запрос.

Пример запроса:

PUT /users HTTP/1. 1

Спорный момент: нет

24. “403 Forbidden” вместо “401 Unauthorized”

Проблема: Отсутствие авторизации -> “403 Forbidden”.

Пример из жизни: Пользователь не залогинен, но UI получает “403” и не предлагает авторизацию.

В чем именно проблема: Клиент думает, что пользователь авторизован, но ему запрещено.

Рекомендация: Используйте “401 Unauthorized” с “WWW-Authenticate”.

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

Пример запроса:

GET /private-data HTTP/1. 1

Спорный момент:
Многие путают эти статусы. В реальности:

“401” -> нет авторизации или она невалидна

“403” -> авторизация есть, но доступ запрещён

Контекст:
Часто в реальных системах “403” применяется как универсальный отказ, что может быть приемлемо для простоты.

Вывод:
Важно придерживаться разделения, особенно если у вас детализированная безопасность.

25. “400 Bad Request” при дублирующемся ресурсе (вместо “409 Conflict”)

Проблема: Повторная регистрация пользователя -> “400 Bad Request”.

Пример из жизни: Пользователь пытается создать аккаунт с тем же email, но UI не понимает, в чём дело.

В чем именно проблема: Ошибка не уточняет конфликт.

Рекомендация: Использовать “409 Conflict”.

В чем именно улучшение: Клиент может показать сообщение: “Пользователь с таким email уже существует”.

Пример запроса:

POST /users HTTP/1. 1
Content-Type: application/json

{
  "email": "duplicate@example.com"
}

Спорный момент: нет

26. “200 OK” при неудачной аутентификации

Проблема: Неверный логин/пароль -> “200 OK”.

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

В чем именно проблема: Нет сигнала о провале входа.

Рекомендация: Возвращать “401 Unauthorized”.

В чем именно улучшение: Клиент может показать корректное сообщение об ошибке входа.

Пример запроса:

POST /login HTTP/1. 1
Content-Type: application/json

{
  "username": "user1",
  "password": "wrongpass"
}

Спорный момент: нет

27. Неверный статус при загрузке файлов (“200 OK” вместо “201” или “202”)

Проблема: При загрузке файла сервер возвращает “200 OK”.

Пример из жизни: Пользователь думает, что файл уже обработан, хотя он только принят.

В чем именно проблема: Неправильное понимание состояния операции.

Рекомендация: Использовать “201 Created” или “202 Accepted”.

В чем именно улучшение: Клиент знает, что файл получен и/или ожидает обработки.

Пример запроса:

POST /upload HTTP/1. 1
Content-Type: multipart/form-data; boundary=-

-file-content-

Спорный момент:
Выбор зависит от бизнес-логики:

“201 Created”: если файл уже принят и размещён.

“202 Accepted”: если файл в процессе обработки.

Контекст:
Разные системы интерпретируют загрузку по-разному.

Вывод:
Оба варианта допустимы, но важно явно указать в теле статус обработки.

28. Отсутствие “Retry-After” при “503 Service Unavailable”

Проблема: Возвращается “503”, но клиент не знает, когда можно повторить.

Пример из жизни: Во время технических работ сервер отвечает “503”, клиент делает ретраи в цикле.

В чем именно проблема: Потенциальная перегрузка системы.

Рекомендация: Возвращать “Retry-After: 120”.

В чем именно улучшение: Клиент знает, когда повторить безопасно.

Пример запроса:

GET /api/dashboard HTTP/1. 1

Спорный момент: нет

29. Неиспользование “422 Unprocessable Entity” при ошибках валидации

Проблема: Некорректное значение поля -> “400 Bad Request”.

Пример из жизни: Email без “@” -> “400”, но клиент не знает, что именно не так.

В чем именно проблема: Не различаются синтаксические и валидационные ошибки.

Рекомендация: Возвращать “422 Unprocessable Entity”.

В чем именно улучшение: Чёткая диагностика ошибки: “Поле email должно быть валидным”.

Пример запроса:

POST /users HTTP/1. 1
Content-Type: application/json

{
  "email": "invalid-email"
}

Спорный момент: нет

30. Игнорирование “429 Too Many Requests” при превышении лимита запросов

Проблема: Сервер возвращает “500”, когда пользователь превышает лимит.

Пример из жизни: Скрипт делает много запросов, сервер отвечает “500”, и скрипт продолжает, не понимая, в чём проблема.

В чем именно проблема: Увеличивает нагрузку и вызывает недоверие к стабильности.

Рекомендация: Использовать “429 Too Many Requests” с “Retry-After”.

В чем именно улучшение: Клиенту ясно, что нужно подождать перед следующей попыткой.

Пример запроса:

GET /api/search HTTP/1. 1

Спорный момент: нет

Почему 22 из 30 пунктов считаются обоснованными best practices

Они основаны на официальных спецификациях:

  • RFC 7231 (HTTP/1. 1 Semantics and Content): описывает поведение статусов “200”, “201”, “202”, “204”, “400”, “403”, “404”, “405”, “406”, “409”, “415”, “500”, “501”, “503”.
  • RFC 6585: определяет “429 Too Many Requests”.
  • RFC 4918 (WebDAV): описывает “207 Multi-Status” и “422 Unprocessable Entity”.
  • Все статусы, используемые в этих пунктах, официально задокументированы и предназначены именно для таких сценариев, как описано в статье.

Спорные пункты (сводная таблица)

ПунктОписаниеКомментарий
3403 vs 404Зависит от уровня доверия (безопасность vs удобство)
6406 при AcceptОпционально, если есть поддержка разных форматов
12500 при ошибке клиентаСпорно, если ошибка не отловлена валидатором
16403 при истёкшем токенеСтандарты говорят: 401 с пояснением
18ETagОпционально, но полезно для оптимизации
22403 при CORSБесполезно, если ошибка на уровне браузера
24403 vs 401Часто путаются; важно чётко разграничить
27201 vs 202 при загрузкеЗавит от синхронности операции

“Строгость” рекомендаций (сводная таблица)

#ОшибкаСтрогость
1“200 OK” при частичных ошибкахContext-Sensitive
2`500` при удалённом APIRFC
3Скрытие `403` ошибок за `404`Context-Sensitive
4`200 OK` вместо `201 Created`RFC
5`200 OK` при ошибкахRFC
6Неиспользование `406`Best Practice
7`400` вместо `415`RFC
8Игнорирование `304`Best Practice
9`404` при неправильном методеRFC
10`200 OK` при DELETE ничего не удаленоRFC
11`201 Created` без `Location`Best Practice
12`500` при ошибке клиентаContext-Sensitive
13`500` при недоступности БДRFC
14`200 OK` при сбое внешнего APIRFC
15Нет `Content-Type` в ответеRFC
16`403` при истекшем токенеContext-Sensitive
17`200 OK` при ошибках queryBest Practice
18Игнорирование `ETag`Best Practice
19Неиспользование `202 Accepted`RFC
20Ошибки в `OPTIONS` CORSBest Practice
21`302 Found` после POSTRFC
22`500` при CORSContext-Sensitive
23Нет `Allow` при `405`RFC
24`403` вместо `401`RFC
25`400` вместо `409 Conflict`RFC
26`200 OK` при плохом логинеRFC
27`200 OK` при загрузке файловContext-Sensitive
28Нет `Retry-After` при `503`RFC
29Нет `422` при ошибках валидацииRFC (WebDAV/RFC4918)
30Игнорирование `429 Too Many Requests`RFC (RFC6585)

Вывод

HTTP-статусы – это не просто числа в ответах сервера. Это язык, с помощью которого сервер общается с клиентом. И как в любом языке, значение слов имеет значение.

Мы рассмотрели 30 наиболее распространённых ошибок, которые регулярно встречаются в REST API – от некорректного использования “200 OK” до игнорирования “429 Too Many Requests” и “422 Unprocessable Entity”. Каждая из них на первый взгляд может показаться мелочью, но на практике:

  • они увеличивают количество багов и непредсказуемого поведения,
  • затрудняют сопровождение продукта,
  • ломают UX и сбивают с толку разработчиков фронтенда и мобильных приложений,
  • и, что важно, – увеличивают стоимость владения системой.

Хороший API – это тот, с которым удобно работать, который ведёт себя предсказуемо и честно сообщает клиенту, что происходит. Правильное использование HTTP-статусов – один из самых недооценённых, но мощных инструментов в этом направлении.

Если вы хотите:

  • сократить количество костылей в коде клиентов,
  • упростить логику обработки ошибок,
  • улучшить документацию и дебаг,
  • и сделать систему понятной как новым разработчикам, так и интеграторам –

то начните с исправления этих ошибок. Даже 3–5 корректно применённых статусов могут сделать API значительно удобнее.