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.
Для підпису/НМАС: розрізняйте'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 стане передбачуваним, безпечним і доброзичливим до інтеграторів.