api-design

Проектирование HTTP API — REST-конвенции (ресурсы, методы, статус-коды), пагинация (cursor vs offset), версионирование, идемпотентность (Idempotency-Key), формат ошибок RFC 7807 (problem+json), безопа

SKILL.md

GitHub

Навык: API Design

REST-конвенции

Ресурсы — существительные во множественном числе; действие выражает HTTP-метод, не URL (POST /orders, а не POST /createOrder). Вложенность — максимум один уровень (/users/{id}/orders), глубже — фильтром (/orders?userId=). Действие вне CRUD — суб-ресурсом: POST /orders/{id}/cancel.

Метод Семантика Идемпотентен Успех Типичные ошибки
GET чтение ✅ 200 404
POST создание / действие ❌ 201 (+ Location) или 200 400, 409, 422
PUT полная замена ✅ 200 404, 409
PATCH частичное обновление ❌ 200 404, 409, 422
DELETE удаление ✅ 204 404

Статусы ошибок: 400 — синтаксически кривой запрос; 422 — валидный JSON, не прошедший бизнес-валидацию; 401 — не аутентифицирован; 403 — аутентифицирован, но нельзя; 409 — конфликт состояния (дубликат, устаревшая версия); 429 — превышен лимит (+ Retry-After).

Пагинация

Offset (?page=3&limit=20) Cursor (?cursor=xyz&limit=20)
Простота ✅ проще, произвольная страница сложнее, только «дальше»
Стабильность при вставках/удалениях ❌ дубли и пропуски между страницами ✅ стабильна
Скорость на глубине ❌ OFFSET 100000 читает и выбрасывает ✅ WHERE (sort_key, id) < cursor по индексу
Когда админки, маленькие статичные списки ленты, публичные API, большие/живые данные

Cursor — непрозрачная строка (base64 от (sort_key, id)), клиент её не парсит. Ответ списка — конверт: { "data": […], "nextCursor": "…", "hasMore": true }. Лимит — с дефолтом и максимумом (например 20/100).

Версионирование

  • Ломающее (удаление/переименование поля, смена типа или семантики) — только в новой версии. Добавление опциональных полей — не ломающее, версии не требует.
  • Дефолт — версия в пути: /v1/… (явно, кэшируемо, просто в роутинге). Альтернатива — заголовок (Accept: …;version=2) для чистых URL.
  • Клиент — tolerant reader: неизвестные поля ответа игнорирует, тогда добавления безопасны.
  • Старую версию не бросай молча: Deprecation/Sunset-заголовки, срок жизни, заметки по миграции.

Идемпотентность

  • GET/PUT/DELETE идемпотентны по контракту — клиент может безопасно ретраить.
  • POST с побочным эффектом (платёж, заказ) — Idempotency-Key: клиент шлёт уникальный ключ заголовком; сервер хранит key → результат (Redis/БД, TTL ~24ч) и на повтор возвращает сохранённый ответ, не выполняя операцию дважды. Обязателен для денежных операций и любых ретраящихся вызовов (вебхуки, очереди).
  • Потребители очередей — идемпотентны всегда: at-least-once означает, что дубли будут.

Ошибки: RFC 7807 (application/problem+json)

Единый формат всех ошибок API:

{
  "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient funds",
  "status": 422,
  "detail": "Balance 5.00 is less than order total 20.00",
  "instance": "/orders/req-7f3a",
  "code": "INSUFFICIENT_FUNDS",
  "errors": [{ "field": "amount", "message": "…" }]
}
  • code — стабильный машинный код: по нему ветвится клиент; человекочитаемый текст может меняться и локализоваться.
  • Валидационные ошибки — списком по полям, все сразу, не по одной.
  • Не течь внутренностями: stack trace, SQL, имена таблиц — только в логи; наружу — generic 500 + instance/requestId для соотнесения с логами.

Пример хорошего эндпоинта

POST /v1/orders
Authorization: Bearer <token>
Idempotency-Key: 3f2a-…
{ "items": [{ "productId": "p_1", "qty": 2 }] }

201 Created
Location: /v1/orders/ord_9x1
{ "id": "ord_9x1", "status": "pending", "total": { "amount": 4200, "currency": "EUR" }, "createdAt": "2026-08-06T10:00:00Z" }

Деньги — минорными единицами + валюта (не float); даты — ISO 8601 в UTC; id — префиксованные строки. Ошибка того же эндпоинта — problem+json с 422 и машинным code.

Безопасность (минимум)

Аутентификация на каждом эндпоинте (Bearer/JWT), авторизация — на уровне ресурса (чужой orderId → 403/404), rate limiting per-user/per-key с 429, вход валидируется схемой (zod) до бизнес-логики.

Углублённо

  • REST vs GraphQL vs gRPC: GraphQL — клиенты с разными потребностями в данных; gRPC — внутренняя сервис-сервис связь с жёсткими контрактами; дефолт публичного API — REST.
  • Контракты и OpenAPI-покрытие → навык swagger-coverage.
  • Коды и обработка ошибок → навык error-handling.
  • Локализация ответов (i18n) → навык i18n: контракт ошибок — стабильный машинный code + параметры (не готовый переведённый текст), выбор языка по Accept-Language/локали пользователя.