Короткий ответ
400 — запрос в принципе некорректен (например, невалидный JSON или отсутствует обязательный параметр); 404 — запрошенный ресурс не найден по указанному адресу; 422 — запрос синтаксически корректен, но данные не проходят бизнес-валидацию (например, нарушено правило или формат поля).
Как это работает подробнее
различие между этими кодами — в том, на каком этапе обработки запрос "отваливается": парсинг синтаксиса (400), поиск ресурса по идентификатору (404), проверка семантики данных относительно бизнес-правил (422). Некоторые команды используют 400 универсально вместо 422 — это допустимо, если так зафиксировано в контракте API и единообразно применяется.
Пример кода
example.py
@app.get("/orders/{order_id}")
def get_order(order_id: int):
order = orders_repo.find(order_id)
if order is None:
return JSONResponse(status_code=404, content={"detail": "Order not found"})
return order
@app.post("/orders")
def create_order(payload: dict):
if "items" not in payload:
return JSONResponse(status_code=400, content={"detail": "items field is required"})
if len(payload["items"]) == 0:
return JSONResponse(status_code=422, content={"detail": "order must contain at least one item"})
return {"ok": True}Что использовать на практике
- Рекомендация. важно согласовать выбор кодов в команде и описать в OpenAPI-схеме, чтобы клиенты могли предсказуемо обрабатывать разные типы ошибок (например, повторный запрос при 400 не поможет, а при временной недоступности зависимости — другой код).
Подводные камни
- Риски. возвращать 404 для "доступ запрещён" вместо 403, чтобы "скрыть" существование ресурса, без явного решения команды об этом как о политике безопасности; возвращать 400 при отсутствии ресурса по ID, путая синтаксическую ошибку запроса с отсутствием данных.