Коды статуса: 2xx/3xx/4xx/5xx
Урок про то, как код ответа сообщает клиенту исход запроса — и как выбрать правильный код вместо вечного 200.
Код статуса HTTP — трёхзначное число в ответе, которое сообщает клиенту исход запроса машиночитаемо: удалось ли, кто виноват при ошибке и что делать дальше.
Код статуса — это первое, что читает клиент, ещё до тела ответа. По нему HTTP-библиотеки решают: бросить исключение или нет, ретраить или нет, идти по редиректу или нет. Если ваш API на любой исход отвечает 200 OK и прячет реальный результат в JSON-поле {"error": true}, вы заставляете каждого клиента вручную разбирать тело и ломаете всю автоматику HTTP. Правильный код — это контракт, понятный без чтения документации.
Четыре класса кодов
Первая цифра задаёт класс — общий смысл ответа:
| Класс | Смысл | Кто «виноват» |
2xx | Успех — запрос принят и обработан | — |
3xx | Перенаправление — нужен ещё шаг | — |
4xx | Ошибка клиента — запрос неверен | клиент |
5xx | Ошибка сервера — сервер не справился | сервер |
Граница между 4xx и 5xx — важнейшая. 4xx значит «исправь запрос, повторять как есть бесполезно». 5xx значит «с твоим запросом всё ок, проблема у нас — возможно, поможет ретрай». От этого зависят алертинг (5xx будят дежурного, 4xx обычно нет) и поведение клиента.
2xx — успех
200 OK— универсальный успех с телом-ответом. Подходит дляGET, удачногоPUT/PATCH,POST-операций, возвращающих результат.201 Created— создан новый ресурс. Сопровождается заголовкомLocationс его адресом. Типичный ответ наPOST, создавший сущность.202 Accepted— запрос принят, но обработка ещё идёт (асинхронно). Используют для очередей и долгих задач: «приняли, проверь статус позже».204 No Content— успех без тела. Идеален дляDELETEи дляPUT/PATCH, когда возвращать нечего. Тела у204быть не должно.
HTTP/1.1 201 Created
Location: /orders/43
HTTP/1.1 204 No Content
(пустое тело)
3xx — перенаправления
301 Moved Permanently— ресурс навсегда переехал на новый URL (в заголовкеLocation). Клиенты и поисковики обновляют ссылку.302 Found— временное перенаправление; исходный URL остаётся каноничным.304 Not Modified— кэш-ответ. На условныйGETсIf-None-Match/If-Modified-Sinceсервер говорит «не изменилось, бери из кэша» и не шлёт тело, экономя трафик.
curl -i https://api.shop.test/orders/42 \
-H 'If-None-Match: "v7"' # -> 304 Not Modified, тела нет
4xx — ошибки клиента
| Код | Когда |
400 Bad Request | тело/синтаксис запроса битые: невалидный JSON, отсутствует обязательное поле |
401 Unauthorized | не аутентифицирован: токена нет, он истёк или неверен |
403 Forbidden | аутентифицирован, но нет прав на это действие |
404 Not Found | ресурса по этому URL не существует |
405 Method Not Allowed | метод не поддержан для ресурса; нужен заголовок Allow |
409 Conflict | конфликт с текущим состоянием: дубль уникального поля, конкурентное изменение |
422 Unprocessable Entity | синтаксис верен, но данные не проходят бизнес-валидацию (email не email, qty < 0) |
429 Too Many Requests | превышен лимит запросов (rate limit); часто с заголовком Retry-After |
Тонкая, но частая пара — 400 против 422. 400 — запрос невозможно даже разобрать (сломанный JSON). 422 — разобрать удалось, поля на месте, но значения не проходят правила предметной области. Многие API упрощают и всё валидационное отдают как 400 — это допустимо, но 422 точнее.
{
"error": "validation_failed",
"details": [
{"field": "qty", "message": "должно быть больше нуля"},
{"field": "email", "message": "некорректный адрес"}
]
}
5xx — ошибки сервера
500 Internal Server Error— необработанное исключение, баг на сервере. Тело отдаём общее, без стектрейсов наружу.502 Bad Gateway— шлюз/прокси получил неверный ответ от вышестоящего сервиса.503 Service Unavailable— сервис временно недоступен (перегрузка, деплой, обслуживание). Часто сRetry-After: «вернись через N секунд».
Как работает под капотом
Код статуса — это число в стартовой строке ответа, сразу за версией протокола:
HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 41
{"error": "order 999 not found"}
Текст после числа («Not Found») — пояснение для людей, машина смотрит только на число. HTTP-клиенты опираются на класс: requests в Python кидает исключение по raise_for_status() для 4xx/5xx; браузерный fetch ставит response.ok = false при коде вне 2xx; редиректы 3xx многие клиенты проходят автоматически. Вот почему правильный код — это не косметика: на нём построено поведение всей экосистемы.
Полезные коды-спутники несут заголовки: 201 — Location, 429/503 — Retry-After, 405 — Allow. Они превращают код из простого «да/нет» в инструкцию к действию.
Частые ошибки
- Тело в 204: по контракту
204 No Contentне имеет тела. Если хотите вернуть данные — берите200. - 201 без Location: создали ресурс, но не сказали где он. Клиенту приходится гадать URL.
- 200 на ошибку: «успех» с
{"error":...}внутри ломает обработку ошибок у клиента (подробно — в следующем уроке). - 500 на ошибку клиента: невалидный ввод — это
400/422, а не500. Иначе вы будите дежурного из-за чужой опечатки.
Итог
- Класс кода — это смысл:
2xxуспех,3xxредирект,4xxвина клиента,5xxвина сервера. 201идёт сLocation;204— успех без тела;202— принято, обработка асинхронна.401— «кто ты?»,403— «тебе нельзя»;400— битый запрос,422— невалидные данные;409— конфликт состояния;429— лимит.5xxсигналит о проблеме сервера и может оправдывать ретрай;4xxтребует исправить запрос.