Когда у API нет описания — каждый разработчик пишет запросы наугад, клиентские библиотеки генерируются с непонятными именами, а документация устаревает в первый же день. OpenAPI решает эту проблему: он описывает весь API в одном файле, который читают и люди, и инструменты.
Что такое OpenAPI и зачем он нужен
Раньше API документировали в Word-файлах, Confluence-страницах или вовсе «на словах». Проблема понятна: документация живёт отдельно от кода, устаревает и врёт.
OpenAPI — это стандарт машиночитаемого описания REST API в формате YAML или JSON. Один файл описывает все эндпоинты: URL, методы, параметры, тела запросов и ответов, коды ошибок.
Что это даёт на практике:
- Swagger UI / Redoc — интерактивная документация генерируется автоматически.
- Client SDK — openapi-generator создаёт типизированный клиент на Java, TypeScript, Python и других языках прямо из спецификации.
- Postman — коллекции запросов импортируются из файла, не пишутся руками.
- Contract testing — тесты проверяют, что реализация соответствует спецификации.
Хорошо оформленный OpenAPI — это не просто документация, это инфраструктура вокруг вашего API.
operationId: имя операции
Каждый эндпоинт в OpenAPI получает поле operationId. Это уникальное имя операции — именно его используют генераторы SDK как имя метода.
Без operationId генератор придумывает имя сам. Результат обычно выглядит как postOrdersOrderIdConfirm — неудобно читать и ещё хуже использовать.
Правило: уникальный, camelCase, формат действие + ресурс.
/api/v1/orders:
get:
operationId: getOrders # список
post:
operationId: createOrder # создание
/api/v1/orders/{orderId}:
get:
operationId: getOrder # один элемент
put:
operationId: updateOrder # полная замена
patch:
operationId: patchOrder # частичное обновление
delete:
operationId: deleteOrder # удаление
/api/v1/orders/{orderId}/confirm:
post:
operationId: confirmOrder # действие над ресурсом
Так клиентский код выглядит читаемо: orderService.confirmOrder(orderId), а не загадочный автоматически сгенерированный вариант.
Соглашение по именам:
get{Resource}— один объект (getOrder)get{Resources}— список (getOrders)create{Resource}— создание через POSTupdate{Resource}— полная замена через PUTpatch{Resource}— частичное обновление через PATCHdelete{Resource}— удаление{глагол}{Resource}— бизнес-действие (confirmOrder,cancelOrder)search{Resources}— поиск через POST с телом
tags: группировка эндпоинтов
Без группировки Swagger UI показывает плоский список из ста эндпоинтов — найти что-то невозможно. Поле tags решает это: эндпоинты группируются в разделы по ресурсу.
Правило: один тег на ресурс, имя во множественном числе с заглавной буквы.
tags:
- name: Orders
description: 'Управление заказами'
- name: Users
description: 'Управление пользователями'
- name: Payments
description: 'Платежи'
/api/v1/orders:
get:
tags: [Orders]
post:
tags: [Orders]
/api/v1/orders/{orderId}/confirm:
post:
tags: [Orders] # действие относится к тегу родительского ресурса
Частая ошибка — создавать отдельный тег для действий: OrderActions, OrderOperations. Этого не нужно. Эндпоинт POST /orders/{orderId}/confirm относится к тегу Orders, не к придуманному Confirmations.
Параметры пути: уникальные имена в спецификации
Здесь есть тонкость, которая удивляет новичков. В коде и проектировании URL обычно пишут {id} — контекст и так понятен из пути. Но в OpenAPI это нельзя:
# Так не работает в Swagger UI — одинаковые имена в одном пути
/api/v1/orders/{id}/items/{id}:
get:
parameters:
- name: id # какой id? первый или второй?
in: path
Swagger UI и Redoc не умеют работать с одинаковыми именами параметров в одном пути. Поэтому в OpenAPI-спецификации параметры именуют уникально:
/api/v1/orders/{orderId}/items/{itemId}:
get:
parameters:
- name: orderId
in: path
required: true
schema:
type: string
format: uuid
- name: itemId
in: path
required: true
schema:
type: string
format: uuid
Это требование инструментов, а не семантическое правило. В документации к самому API и в коде можно продолжать писать {id} — контекст устраняет неоднозначность.
summary и description
summary — это короткое описание эндпоинта, которое видно в Swagger UI рядом с URL. Правило простое: до 80 символов, фраза, не предложение.
description — развёрнутое объяснение в формате Markdown. Добавляйте его только когда логика эндпоинта неочевидна: особые условия, переходы состояний, ограничения.
/api/v1/orders/{orderId}/confirm:
post:
operationId: confirmOrder
tags: [Orders]
summary: 'Подтвердить заказ'
description: |
Переводит заказ из статуса CREATED в CONFIRMED.
Заказ должен содержать хотя бы одну позицию.
После подтверждения изменение состава заказа невозможно.
Пустой description лучше, чем description: 'Confirm order' — бессмысленное дублирование summary.
Типичные ошибки в REST-дизайне
Это разбор частых проблем, с которыми сталкиваются при проектировании и ревью REST API.
Глагол в URL
# Неправильно
GET /api/v1/getOrders
POST /api/v1/cancelOrder
POST /api/v1/orders/doConfirm
# Правильно
GET /api/v1/orders
POST /api/v1/orders/{id}/cancel
POST /api/v1/orders/{id}/confirm
В REST метод HTTP уже несёт смысл действия. Глагол в URL — это дублирование и нарушение соглашений. Единственное место, где глагол в URL уместен — бизнес-действия (/cancel, /confirm, /approve), и там он стоит в конце пути после ID ресурса.
CamelCase и snake_case в пути
Пути URL пишутся в kebab-case (слова через дефис):
# Неправильно
/api/v1/orderItems
/api/v1/order_items
# Правильно
/api/v1/order-items
Версионирование
Версия всегда идёт в пути и только в пути:
# Неправильно
/api/orders?version=1
/api/orders?v=2
/2026/orders
# Правильно
/api/v1/orders
/api/v2/orders
Минорные версии (v1.1, v1.2) не нужны — если изменение не ломает клиентов, версия не меняется. Если ломает — это v2.
Также: каждый путь должен начинаться с /api/. Это позволяет на уровне nginx или шлюза разделить API и статику без конфликтов.
ID в теле запроса вместо пути
// Неправильно — PUT с ID в теле
PUT /api/v1/orders
{ "id": "123", "status": "confirmed" }
// Правильно — ID в пути
PUT /api/v1/orders/123
{ "status": "confirmed" }
GET с побочным эффектом
GET-запросы должны быть безопасными (idempotent): их можно повторять, кэшировать, логировать без последствий. Если запрос что-то меняет — используйте POST.
# Неправильно
GET /api/v1/orders/123/cancel
# Правильно
POST /api/v1/orders/123/cancel
Query-параметры: массивы и пагинация
Массивы в query-параметрах передают повтором параметра, не через запятую:
# Неправильно
GET /api/v1/orders?status=NEW,CONFIRMED,DELIVERED
# Правильно
GET /api/v1/orders?status=NEW&status=CONFIRMED&status=DELIVERED
Пагинация с нуля сбивает пользователей API: page=0 означает «первая страница», но выглядит как «нулевая». В публичных контрактах используйте page=1 для первой страницы.
Ответы: envelope и null
Envelope — это обёртка вроде { "success": true, "data": {...} }. Она кажется удобной, но ломает стандарты: HTTP-код уже говорит об успехе или ошибке, data добавляет лишний уровень вложенности, а клиентам нужно каждый раз разворачивать обёртку.
// Неправильно — envelope
{ "success": true, "data": { "id": "123", "status": "CONFIRMED" } }
// Правильно — плоский ресурс
{ "id": "123", "status": "CONFIRMED" }
Поля со значением null лучше вовсе не включать в ответ — отсутствие поля и null несут разный смысл, и клиенты часто не умеют их различать. Пустую строку "" тоже не используют для «нет значения» — только отсутствие поля.
Ошибки
Ошибки должны возвращаться в формате RFC 9457 (Problem Details) с content-type application/problem+json:
{
"type": "urn:problem:orders:order-not-found",
"title": "Not Found",
"status": 404,
"detail": "Заказ 123 не найден"
}
Частые проблемы:
type: "about:blank"— бессмысленный тип-заглушка, не несёт информации- stack traces в ответе на 500 — утечка деталей реализации наружу; клиенту нужен
codeи общийdetail, не трассировка стека application/jsonвместоapplication/problem+json— нарушает стандарт
Заголовки и deprecation
Кастомные заголовки не нужно начинать с X- — этот префикс официально устарел. Используйте доменный префикс:
# Устаревший подход
X-Request-Id: abc123
X-Rate-Limit: 100
# Правильно
Shop-Request-Id: abc123
RateLimit-Limit: 100
Если эндпоинт помечается устаревшим — добавьте заголовок Sunset с датой отключения. Без этого клиенты не знают, когда прекратить использование.
При ограничении запросов (429 Too Many Requests) возвращайте Retry-After — клиент должен знать, когда можно повторить запрос.
Коротко
- OpenAPI описывает весь API в одном файле — на основе него генерируются документация, SDK и тесты.
operationId— уникальный, camelCase, форматдействие + ресурс(createOrder,confirmOrder).tags— один тег на ресурс, множественное число с заглавной (Orders,Users); действия относятся к тегу родительского ресурса.- В OpenAPI параметры пути именуют уникально (
{orderId},{itemId}) — требование Swagger/Redoc. summary— до 80 символов;description— только если логика неочевидна.- Глагол в URL для CRUD — ошибка; бизнес-действия выносят в конец пути (
/confirm,/cancel). - Пути в kebab-case, версия в пути (
/api/v1/), массивы — повтором параметра. - Envelope (
success/data) не нужен — используйте плоский ресурс и HTTP-коды. - Ошибки возвращают в формате
application/problem+jsonбез stack traces. - Кастомные заголовки без
X-; устаревшие эндпоинты — с заголовкомSunset.
Что почитать дальше
- URL и ресурсы в REST — правила именования путей, вложенность, ресурсы.
- Методы HTTP и коды ответов — когда какой метод и статус-код использовать.
- Версионирование API — стратегии v1/v2, обратная совместимость.
- Ошибки и Problem Details — RFC 9457 на практике.