API коды ошибок и best practices
1) Зачем стандартизировать ошибки
Предсказуемость для клиентов: единый формат и поведение ретраев.
Ускорение дебага: `trace_id`/`request_id`, стабильные `error_code`.
Безопасность: не утекут SQL/stack traces/конфиги.
Наблюдаемость: отчетность по таксономии ошибок (валидация, квоты, таймауты и т. д.).
2) Базовые принципы
1. Единый формат ответа для всех 4xx/5xx (и для 2xx с частичными ошибками — отдельная схема).
2. Четкая семантика HTTP: верный статус важнее всего.
3. Два уровня кода: транспортный (`status`) и доменный стабильный `error_code`.
4. Retriable vs Non-retriable: указывайте явно и давайте подсказку по бэк-оффу.
5. Безопасность по умолчанию: подробности — только клиенту с правами; без внутренних трасс.
6. Локализация: машинный код остается стабильным, текст — переводим.
3) Единый формат ошибки (на базе RFC 7807)
Рекомендуемый JSON (расширенный `application/problem+json`):json
{
"type": "https://api. example. com/errors/validation_failed",
"title": "Validation failed",
"status": 422,
"error_code": "VAL_001",
"detail": "Field 'email' must be a valid address",
"instance": "req_01HZY...93",
"trace_id": "a1b2c3d4e5f6",
"retriable": false,
"errors": [
{"field": "email", "code": "email_invalid", "message": "Invalid email"}
],
"hint": "Fix payload and retry",
"meta": {"docs": "https://docs. example. com/errors#VAL_001"}
}
Обязательные: `type`, `title`, `status`, `error_code`, `trace_id`.
Опционально: `errors[]` (по полям), `retriable`, `hint`, `meta`.
- `Content-Type: application/problem+json`
- `X-Request-ID`/`Traceparent` (W3C)
- (для 429/503) `Retry-After` (секунды или дата)
4) Семантика HTTP статусов (слияние «классики» и практики)
2xx (успех с нюансами)
200 OK — обычный успех.
201 Created — создан ресурс (Location).
202 Accepted — асинхронно в очереди (дайте `status_url`).
207 Multi-Status — частичный успех (избегайте, если можно).
4xx (ошибка клиента)
400 Bad Request — синтаксис/формат, но не валидация полей (лучше 422).
401 Unauthorized — нет/неверный токен. Давайте `WWW-Authenticate`.
403 Forbidden — токен валиден, но прав не хватает (RBAC/ABAC/лимиты).
404 Not Found — ресурс/эндпоинт отсутствует.
409 Conflict — конфликт версий/состояния (optimistic locking, idempotency).
410 Gone — эндпоинт навсегда убран.
412 Precondition Failed — ETag/If-Match не прошел.
415 Unsupported Media Type — неправильный `Content-Type`.
422 Unprocessable Entity — валидация бизнес-правил.
429 Too Many Requests — превышены квоты/скорость (см. §7).
5xx (ошибка сервера)
500 Internal Server Error — внезапная ошибка; не разглашать детали.
502 Bad Gateway — ошибка апстрима.
503 Service Unavailable — деградация/перегруз, дайте `Retry-After`.
504 Gateway Timeout — таймаут бэкенда.
5) Таксономия доменных `error_code`
Рекомендуем диапазоны:- `AUTH_` — аутентификация/авторизация.
- `VAL_` — валидация входных данных.
- `RATELIMIT_` — квоты и скорость.
- `IDEMP_` — идемпотентность/дубликаты.
- `CONFLICT_` — версии/состояние.
- `DEP_` — зависимости (PSP/DNS/SMTP).
- `PAY_` — бизнес-ошибки платежного домена.
- `SEC_` — безопасность (подписи, HMAC, mTLS).
- `INT_` — внутренние внезапные.
- Стабильность во времени (back-compat).
- Описания и примеры в каталоге ошибок (docs + machine-readable JSON).
6) Retriable vs Non-retriable
Поля:- `retriable: true|false`
- Если `true` — обязательно `Retry-After` (в секундах) или контракт «экспоненциальный бэк-офф (начиная с 1–2 с, макс 30–60 с)».
Retriable обычно: `502/503/504`, некоторые `500`, `429` (после окна).
Non-retriable: `400/401/403/404/409/410/415/422`.
7) Rate limit & quota ошибки (429)
Тело:json
{
"type": "https://api. example. com/errors/rate_limited",
"title": "Rate limit exceeded",
"status": 429,
"error_code": "RATELIMIT_RPS",
"detail": "Too many requests",
"retriable": true
}
Заголовки:
- `Retry-After: 12`
- `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
- Для квот: `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset`
8) Идемпотентность и конфликты
В запросах на запись — `Idempotency-Key` (уникален в пределах 24–72 ч).
Конфликт повторной операции → 409 Conflict с `error_code: "IDEMP_REPLAY"`.
Конфликт версий ресурса по ETag → 412 Precondition Failed.
В ответе приложите `resource_id`/`status_url` для безопасного повторного запроса.
9) Валидация и 422
Возвращайте список ошибок по полям:json
{
"status": 422,
"error_code": "VAL_001",
"errors": [
{"field":"email","code":"email_invalid","message":"Invalid email"},
{"field":"age","code":"min","message":"Must be >= 18"}
]
}
Правила:
- Не дублируйте то же в 400 — 422 предпочтительнее для бизнес-валидации.
- Сообщения — человекочитаемые; `code` — машинный.
10) Безопасность ошибок
Никогда: stack traces, SQL, пути файлов, приватные имена хостов.
Редактируйте PII; следите за GDPR/DSAR.
Для подписи/HMAC: различайте `SEC_SIGNATURE_MISMATCH` (403) и `SEC_TIMESTAMP_SKEW` (401/403) с подсказкой «проверьте время ±5 мин».
11) Корреляция и наблюдаемость
Всегда добавляйте `trace_id`/`X-Request-ID` и прокидывайте в логи/трассы.
Ошибки агрегируйте по `error_code` и `status` → дашборды «топ-ошибки», «new vs known».
Алерты: всплеск 5xx/422/429, латентность p95, share of errors.
12) gRPC/GraphQL/Webhooks — маппинги
gRPC ↔ HTTP
GraphQL
Транспорт 200, но `errors[]` внутри — добавляйте `extensions.code` и `trace_id`.
Для «фатала» (аутентификация/квоты) — лучше реальный HTTP 401/403/429.
Webhooks
Считайте успешным только 2xx получателя.
Ретраи с экспоненциальным бэк-оффом, `X-Webhook-ID`, `X-Signature`.
410 от получателя — остановить ретраи (endpoint удален).
13) Версионирование ошибок
`type`/`error_code` — стабильные; новые — только добавлять.
При изменении схемы тела — повышайте минорную версию API или `problem+json; v=2`.
Документация: таблица кодов + примеры; changelog ошибок.
14) Документация (OpenAPI фрагменты)
Глобальные ответы
yaml components:
responses:
Problem:
description: Problem Details content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
schemas:
Problem:
type: object required: [type, title, status, error_code, trace_id]
properties:
type: { type: string, format: uri }
title: { type: string }
status: { type: integer }
error_code: { type: string }
detail: { type: string }
instance: { type: string }
trace_id: { type: string }
retriable: { type: boolean }
errors:
type: array items:
type: object properties:
field: { type: string }
code: { type: string }
message: { type: string }
Пример эндпоинта
yaml paths:
/v1/users:
post:
responses:
'201': { description: Created }
'401': { $ref: '#/components/responses/Problem' }
'422': { $ref: '#/components/responses/Problem' }
'429': { $ref: '#/components/responses/Problem' }
'500': { $ref: '#/components/responses/Problem' }
15) Тестирование и качество
Контракт-тесты: соответствие `application/problem+json`, обязательные поля.
Negative tests: все ветви 401/403/404/409/422/429/500.
Chaos/latency: проверка ретраев на 5xx/503/504/429 (`Retry-After`).
Security tests: отсутствие внутренних сообщений, корректная маска PII.
Backward-compat: старые клиенты понимают новые поля (добавляйте, не ломайте).
16) Чек-лист внедрения
- Единый `problem+json` + стабильные `error_code`.
- Корректная семантика HTTP/гRPC/GraphQL.
- Retriable/non-retriable + `Retry-After`/рекомендации бэк-оффа.
- Rate-limit заголовки и 429 поведение.
- Идемпотентность (`Idempotency-Key`, 409/412).
- Безопасность: без stack traces/секретов, PII-редакция.
- `trace_id`/`X-Request-ID` во всех ошибках.
- Документация каталога ошибок и примеры.
- Мониторинг по таксономии ошибок.
- Автотесты негативных сценариев.
17) Мини-FAQ
Чем 400 отличается от 422?
400 — сломанный запрос (синтаксис/тип содержимого). 422 — валидный по синтаксису, но бизнес-правила не прошли.
Когда 401, а когда 403?
401 — нет/неверный токен; 403 — токен есть, прав не хватает.
Нужен ли всегда `Retry-After`?
Для 429/503 — да; для остальных retriable — желательно давать явную рекомендацию.
Итог
Хорошо спроектированные ошибки — это контракт: корректный HTTP-статус, единый `problem+json`, стабильные `error_code`, явные подсказки по ретраям и строгая безопасность. Стандартизируйте формат, документируйте таксономию, добавьте телеметрию и тесты — и ваш API станет предсказуемым, безопасным и дружелюбным к интеграторам.