Уявімо реальну ситуацію. Команда бекенду каже: «Ендпоінт готовий, просто виклич POST /v1/orders». Команда фронтенду питає:
created_at?Authorization?422, а який 409?Якщо відповіді на ці питання живуть лише в коді сервера, то клієнтський розробник змушений або читати чужий бекенд, або експериментувати через Postman, або ставити десятки уточнювальних питань. Це дорого, повільно і небезпечно, бо API перетворюється з контракту на набір припущень.
OpenAPI розв'язує цю проблему: він формалізує HTTP API в машинозчитуваний документ. Такий документ може одночасно читати людина, IDE, генератор клієнтів, тестові інструменти та система перевірки контракту.
Що ви отримаєте
paths, responses, components, schemasПререквізити
OpenAPI Specification (OAS), або просто OpenAPI, це стандарт опису HTTP API у форматі YAML або JSON.
Ключова думка: OpenAPI описує контракт взаємодії, а не реалізацію сервера. У специфікації немає SQL-запитів, внутрішніх сервісів, middleware чи класів доменної моделі. Там є лише те, що бачить зовнішній споживач API:
Тут часто виникає термінологічний хаос, тому розмежуємо поняття одразу.
| Термін | Що це таке | Роль |
|---|---|---|
| OpenAPI | Стандарт специфікації | Описує контракт API |
| Swagger Specification | Історична назва попередніх версій стандарту | Сьогодні майже завжди мають на увазі OpenAPI |
| Swagger UI | Вебінтерфейс для візуалізації специфікації | Показує документацію та дає робити тестові запити |
| Swagger Editor | Редактор специфікацій | Дає писати і перевіряти YAML/JSON |
| Code Generator | Інструмент генерації клієнта або серверного каркаса | Використовує специфікацію як вхідні дані |
Проблема в тому, що в побутовій мові розробники часто кажуть «Swagger», маючи на увазі будь-що з цього списку. Але технічно коректніше розділяти:
OpenAPI — це формат договору;Swagger UI — це спосіб показати договір;generator — це спосіб використати договір.OpenAPI цінний не тому, що «гарно виглядає в браузері». Його сила в тому, що один документ стає єдиним джерелом правди для кількох ролей одночасно.
Практична користь:
| Сценарій | Як допомагає OpenAPI |
|---|---|
| Документація | Swagger UI або інші сайти будують reference автоматично |
| Розробка клієнта | Генеруються DTO, методи виклику, типи помилок |
| Тестування | Можна перевіряти, чи сервер не порушив контракт |
| Рев'ю API-дизайну | Контракт видно до реалізації коду |
| Onboarding | Новий інженер читає специфікацію, а не 30 контролерів |
OpenAPI-документ зазвичай має кілька великих частин. Кожна відповідає на окремий клас запитань.
Секції openapi, info і servers відповідають на питання:
Секція paths описує URL та HTTP-методи. Саме тут живуть get, post, put, delete, параметри маршруту, query-параметри, requestBody і responses.
Секція components потрібна, щоб не дублювати структури всюди вручну. Тут зберігають:
schemas для типів даних;parameters для спільних параметрів;responses для типових відповідей;securitySchemes для опису авторизації.Секції securitySchemes і security формалізують, які механізми аутентифікації потрібні: Bearer token, API key, OAuth2 тощо.
openapi: 3.1.0
info:
title: Coffee API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/v1/orders:
post:
summary: Create order
components:
schemas: {}
securitySchemes: {}
Цей каркас нічого корисного ще не описує, але показує головну ідею: документ читається зверху вниз, від загальних метаданих до конкретних операцій і повторно використовуваних типів.
До цього моменту ми бачили лише великий каркас. Але реальна робота з OpenAPI майже завжди відбувається на рівні маленьких фрагментів: сьогодні вам треба згадати, як описати multipart/form-data, завтра — як оформити oneOf, післязавтра — як документувати query-параметр-масив.
Нижче наведено саме такий довідковий шар: короткі приклади, які можна читати незалежно один від одного.
info: не лише title і versionСекція info може містити не тільки назву та версію, а й метадані для людей, юристів і партнерів.
info:
title: Billing API
version: 2.3.0
summary: API для рахунків, платежів і повернень
description: >
Публічний контракт платіжної платформи для партнерів.
termsOfService: https://example.com/terms
contact:
name: API Support
email: api-support@example.com
url: https://example.com/support
license:
name: Commercial License
url: https://example.com/license
Практичний сенс:
summary дає короткий контекст;contact зменшує кількість «кому писати, якщо щось не працює?»;license та termsOfService важливі для публічних або партнерських API.servers: фіксовані URL та змінніНайпростіший варіант ми вже бачили: список готових URL. Але servers підтримує і змінні.
servers:
- url: https://api.example.com
description: Production
- url: https://sandbox.example.com
description: Sandbox
servers:
- url: https://{environment}.example.com/{basePath}
description: Configurable endpoint
variables:
environment:
default: api
enum: [api, sandbox, staging]
basePath:
default: v1
Змінні корисні, коли адреса API структурно стабільна, але середовище або базовий префікс можуть змінюватися.
tags: не декоративна дрібницяБагато хто сприймає tags як косметику для UI. Насправді це основний інструмент навігації у великих документах.
tags:
- name: Orders
description: Операції із замовленнями
- name: Payments
description: Платежі, повернення та статуси транзакцій
- name: Admin
description: Адміністративні endpoint-и
Якщо у вас 80 операцій і немає тегів, документація швидко стає непридатною до використання.
parameters: чотири місця розташуванняПараметри в OpenAPI описуються не абстрактно, а з чітким in.
parameters:
- name: orderId
in: path
required: true
schema:
type: string
format: uuid
Для path параметрів required: true є фактично обов'язковим, бо інакше сам шлях втрачає сенс.
parameters:
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
Типовий сценарій: фільтри, сортування, пагінація, пошук.
parameters:
- name: X-Correlation-Id
in: header
required: false
schema:
type: string
Зручно для correlation id, feature flags, ідемпотентності, умовних ревізій.
parameters:
- name: session_id
in: cookie
required: true
schema:
type: string
Цей варіант рідше зустрічається в публічних API, але корисний для browser-oriented сценаріїв.
style і explode: як серіалізуються параметриОдин і той самий масив можна передати кількома способами. Саме для цього існують style і explode.
in | Типовий style | Що це означає |
|---|---|---|
path | simple, label, matrix | як параметр вбудовується в URL-шлях |
query | form, spaceDelimited, pipeDelimited, deepObject | як параметр потрапляє в query string |
header | simple | як значення кодується в заголовку |
cookie | form | як значення кодується в cookie |
parameters:
- name: tags
in: query
style: form
explode: true
schema:
type: array
items:
type: string
Такий опис зазвичай означає формат на кшталт:
?tags=coffee&tags=arabica&tags=discount
Ще один приклад:
parameters:
- name: filter
in: query
style: deepObject
explode: true
schema:
type: object
properties:
status:
type: string
minPrice:
type: number
Це вже відповідає формату:
?filter[status]=active&filter[minPrice]=100
style і explode, якщо ваш бекенд насправді не підтримує таку серіалізацію. Це один із найпідступніших способів зробити документацію формально валідною, але практично хибною.requestBody: не лише JSONrequestBody потрібен не всім методам, але коли він є, OpenAPI дозволяє дуже точно вказати тип контенту.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
Підходить для класичних login/form сценаріїв.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
folder:
type: string
Підходить для завантаження файлів.
responses: фіксовані коди, default, headers, linksБільшість статей обмежуються прикладом 200 і 404. Але OpenAPI підтримує значно більше.
responses:
'200':
description: Успішна відповідь
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
default:
description: Непередбачена помилка
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
Корисно, коли ви хочете зафіксувати загальну fallback-помилку для невідомих статусів.
responses:
'201':
description: Створено
headers:
Location:
description: URL нового ресурсу
schema:
type: string
format: uri
responses:
'201':
description: Створено
links:
GetCreatedOrder:
operationId: getOrderById
parameters:
orderId: '$response.body#/id'
links не виконує логіку сам по собі, але формально документує зв'язок: після одного виклику можна переходити до іншого.
content: одна відповідь, кілька media typeOpenAPI дозволяє описати кілька форматів представлення тієї самої відповіді.
responses:
'200':
description: Товар знайдено
content:
application/json:
schema:
$ref: '#/components/schemas/Product'
application/xml:
schema:
$ref: '#/components/schemas/Product'
У сучасних HTTP API найчастіше достатньо application/json, але стандарт не обмежується лише ним.
components: не лише schemasУ багатьох командах components асоціюється лише зі schemas. Це спрощення. Насправді components може бути центральним місцем для всіх повторно використовуваних шматків контракту.
components:
schemas: {}
responses: {}
parameters: {}
examples: {}
requestBodies: {}
headers: {}
securitySchemes: {}
links: {}
callbacks: {}
Найчастіше реально використовують:
schemas;responses;parameters;securitySchemes.Але для великих API також корисні requestBodies, headers, examples і links.
components:
requestBodies:
CreateOrderBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
paths:
/v1/orders:
post:
requestBody:
$ref: '#/components/requestBodies/CreateOrderBody'
components:
headers:
RequestId:
description: Унікальний ідентифікатор запиту
schema:
type: string
responses:
'200':
description: OK
headers:
X-Request-Id:
$ref: '#/components/headers/RequestId'
components:
examples:
ReadyOrder:
summary: Готове замовлення
value:
id: 42
status: ready
Практичний принцип тут простий: якщо один і той самий фрагмент починає копіюватися в кілька місць, подумайте, чи не час винести його в components.
example vs examplesЦе маленька, але дуже часта точка плутанини.
schema:
type: string
example: pending
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
examples:
draft:
summary: Чернетка
value:
id: 1
status: pending
done:
summary: Завершене замовлення
value:
id: 2
status: ready
example добре підходить для простих полів. examples краще, коли треба показати кілька реальних сценаріїв.
schemas: не лише примітивиУ components/schemas можна описувати як прості типи, так і складні композиції.
OrderStatus:
type: string
enum: [pending, paid, canceled]
ValidationError:
allOf:
- $ref: '#/components/schemas/ApiError'
- type: object
properties:
errors:
type: array
items:
type: string
PaymentMethod:
oneOf:
- $ref: '#/components/schemas/CardPayment'
- $ref: '#/components/schemas/CashPayment'
SearchFilter:
anyOf:
- $ref: '#/components/schemas/DateFilter'
- $ref: '#/components/schemas/StatusFilter'
User:
type: object
properties:
id:
type: string
readOnly: true
password:
type: string
writeOnly: true
Практична інтерпретація:
allOf добре підходить для наслідування або розширення базової помилки;oneOf означає «рівно одна форма з кількох»;anyOf означає «може відповідати кільком формам одразу»;readOnly і writeOnly допомагають не плутати поля, що приходять лише від клієнта або лише від сервера.discriminator: коли oneOf стає складнимЯкщо oneOf описує поліморфні об'єкти, часто потрібен discriminator, щоб клієнт зрозумів, яку саме форму він отримав.
PaymentMethod:
oneOf:
- $ref: '#/components/schemas/CardPayment'
- $ref: '#/components/schemas/BankTransferPayment'
discriminator:
propertyName: type
mapping:
card: '#/components/schemas/CardPayment'
bank_transfer: '#/components/schemas/BankTransferPayment'
securitySchemes: основні варіантиsecuritySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: X-Api-Key
securitySchemes:
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.example.com/authorize
tokenUrl: https://auth.example.com/token
scopes:
orders.read: Read orders
orders.write: Modify orders
securitySchemes:
oidc:
type: openIdConnect
openIdConnectUrl: https://auth.example.com/.well-known/openid-configuration
Після визначення схеми її ще треба застосувати:
security:
- bearerAuth: []
Або глобально для всього документа:
security:
- bearerAuth: []
callbacks і webhooks: просунуті сценаріїБільшість OpenAPI-документів ніколи не використовують callbacks, але для асинхронних інтеграцій це дуже цінний інструмент.
callbacks:
paymentUpdated:
'{$request.body#/callbackUrl}':
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PaymentStatusChanged'
responses:
'200':
description: Callback accepted
Сенс тут такий: клієнт передає URL зворотного виклику, а сервер документує, який HTTP-запит прийде туди пізніше.
externalDocs: коли специфікації замалоOpenAPI добре описує контракт, але не замінює повністю гіди, туторіали та бізнесові пояснення.
externalDocs:
description: Повний гайд для інтеграції
url: https://developer.example.com/guides/orders
Цей елемент корисний, коли вам треба пов'язати формальний контракт із навчальним або бізнесовим контекстом.
Нижче наведено компактний, але реалістичний OpenAPI-документ для створення і читання замовлень.
openapi: 3.1.0
info:
title: Coffee Orders API
version: 1.0.0
summary: API для створення та перегляду замовлень кави
description: >
Контракт HTTP API для мобільного додатка кав'ярні.
API дозволяє створювати замовлення та отримувати їх за ідентифікатором.
servers:
- url: https://api.example.com
description: Production
- url: https://sandbox.api.example.com
description: Sandbox
tags:
- name: Orders
description: Операції із замовленнями
paths:
/v1/orders:
post:
tags: [Orders]
operationId: createOrder
summary: Створити нове замовлення
description: >
Приймає дані нового замовлення, виконує валідацію
і повертає створений ресурс.
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
examples:
lungo:
summary: Замовлення лунго
value:
recipeId: lungo
volumeMl: 300
sugar: 1
responses:
'201':
description: Замовлення створено
headers:
Location:
description: URL створеного ресурсу
schema:
type: string
format: uri
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/v1/orders/{orderId}:
get:
tags: [Orders]
operationId: getOrderById
summary: Отримати замовлення за ідентифікатором
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/OrderId'
responses:
'200':
description: Замовлення знайдено
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
components:
parameters:
OrderId:
name: orderId
in: path
required: true
description: UUID замовлення
schema:
type: string
format: uuid
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
responses:
BadRequest:
description: Некоректний формат запиту
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
Unauthorized:
description: Користувач не аутентифікований
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
NotFound:
description: Замовлення не знайдено
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
ValidationError:
description: Помилка валідації полів
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
schemas:
CreateOrderRequest:
type: object
additionalProperties: false
required:
- recipeId
- volumeMl
properties:
recipeId:
type: string
description: Код рецепта
example: lungo
volumeMl:
type: integer
minimum: 50
maximum: 500
description: Об'єм напою в мілілітрах
example: 300
sugar:
type: integer
minimum: 0
maximum: 5
description: Кількість ложок цукру
example: 1
Order:
type: object
required:
- id
- recipeId
- volumeMl
- status
- createdAt
properties:
id:
type: string
format: uuid
recipeId:
type: string
volumeMl:
type: integer
sugar:
type: integer
nullable: true
status:
type: string
enum: [pending, brewing, ready, canceled]
createdAt:
type: string
format: date-time
ApiError:
type: object
required:
- status
- reason
- developerMessage
properties:
status:
type: integer
example: 401
reason:
type: string
example: authentication_required
developerMessage:
type: string
example: Bearer token is missing or invalid
ValidationError:
allOf:
- $ref: '#/components/schemas/ApiError'
- type: object
properties:
errors:
type: array
items:
type: object
required: [field, message]
properties:
field:
type: string
message:
type: string
info і serversБлок info описує сам документ: назву, версію, короткий опис. Блок servers показує, проти яких середовищ клієнт може працювати. Це не просто декоративна інформація: інструменти можуть підставляти Production або Sandbox як базову адресу для тестових запитів.
pathsТут видно дві операції:
POST /v1/orders створює ресурс;GET /v1/orders/{orderId} читає ресурс.Кожна операція має власні:
summary і description для документації;operationId для генераторів SDK;security для вимог авторизації;parameters, requestBody, responses для контракту взаємодії.requestBodyУ POST /v1/orders тіло запиту оголошене явно. Специфікація не просто каже «передайте JSON», а визначає:
application/json;$ref;examples.Саме тому якісний OpenAPI-документ корисніший за звичайний Markdown-опис. Він не обмежується словами, а формалізує структуру.
responsesУ кожного статус-коду є свій опис. Це критично важливо: без цього клієнт бачить лише «може бути 400», але не знає, яке тіло повернеться і як його парсити.
Зверніть увагу на два прийоми:
201 описано локально, бо там унікальна відповідь з Location;400, 401, 404, 422 винесені в components/responses, щоб не дублювати один і той самий фрагмент у десяти ендпоінтах.components/schemasСекція schemas описує форми даних. Це фактично словник типів, який використовує весь документ.
Наприклад:
CreateOrderRequest описує payload вхідного запиту;Order описує успішну відповідь;ApiError і ValidationError описують формат помилок.Якщо ви змінюєте схему в одному місці, усі $ref, що посилаються на неї, автоматично використовують оновлений контракт.
operationIdДля людини operationId може виглядати необов'язковим. Але для генератора клієнта це майже ім'я методу.
Без operationId ви ризикуєте отримати в SDK щось на кшталт postV1Orders або getV1OrdersOrderId, що складно читати й підтримувати. Імена на кшталт createOrder, getOrderById, cancelOrder роблять сгенерований код набагато якіснішим.
examplesСхема показує форму даних, але приклад показує семантику. Поле volumeMl: integer не пояснює, чи типове значення це 50, 300 чи 5000. Приклад показує нормальний, очікуваний сценарій використання.
additionalProperties: falseЦей прапорець означає: «не приймайте довільні зайві поля». Він корисний, коли ви хочете жорсткіший контракт і не бажаєте мовчазно проковтувати сміттєві дані.
securitySchemes та securityБезпека в OpenAPI не повинна лишатися «текстом у README». Якщо API вимагає Bearer token, це має бути описано структуровано. Інакше документація виглядатиме повною, але фактично не дозволить коректно використати API.
Це одна з головних стратегічних розвилок у роботі з OpenAPI.
Спочатку команда проєктує OpenAPI-специфікацію, рев'ює її і лише потім починає реалізацію сервера та клієнтів.
Цей підхід сильний тоді, коли:
Спочатку пишеться серверний код, а OpenAPI генерується з реалізації автоматично через framework або бібліотеку.
Цей підхід сильний тоді, коли:
Універсально «кращого» підходу не існує. Потрібно дивитися на організаційний контекст.
| Сценарій | Кращий стартовий вибір |
|---|---|
| Публічне API для зовнішніх інтеграцій | contract-first |
| B2B API з тривалим життєвим циклом | contract-first |
| Внутрішній сервіс невеликої команди | code-first |
| Прототип або MVP | code-first, але з подальшим контрактним прибиранням |
code-first режимі команда повинна почати ставитися до згенерованого OpenAPI як до публічного артефакту, який перевіряють у code review так само уважно, як і код.Погана специфікація не краща за відсутність специфікації. Вона створює фальшиве відчуття надійності.
400, 401, 403, 404, 409, 422, 429, 500 або інші релевантні сценарії.camelCase, а сервер повертає snake_case; у схемі date-time, а сервер віддає локальну дату без timezone. Така документація не просто застаріла, а брехлива.components.OpenAPI не живе окремо від решти архітектурних рішень. Він лише фіксує їх у формальному вигляді.
Статус-коди
responses.Валідація
requestBody, schemas і моделі ApiError.Безпека
Процес проєктування
На практиці команда рідко працює зі специфікацією «вручну і в вакуумі». Зазвичай навколо OpenAPI існує цілий pipeline.
Візьміть наведений у статті YAML і письмово дайте відповідь на питання:
paths?POST /v1/orders?Розширте специфікацію новою операцією GET /v1/orders, яка повертає список замовлень.
Додайте:
limit;cursor;Створіть навмисно проблемну версію контракту:
required для одного обов'язкового поляformat: uuid на звичайний string422security у захищеного endpointПісля цього поясніть, які проблеми це створить для клієнта, документації та генератора SDK.
Оберіть один реальний сценарій:
Для нього аргументуйте, який підхід буде кращим: contract-first чи code-first. Аргументи мають стосуватися не технології, а процесу команди, ризику breaking changes і вимог до документації.
Оберіть одну предметну область: бібліотека, спортзал, доставка їжі або бронювання переговорних кімнат.
Створіть OpenAPI-специфікацію, що містить:
info та servers;paths;requestBody;components/schemas;securitySchemes.Після цього перевірте, чи ваш контракт узгоджується з правилами з матеріалів про статус-коди, валідацію, пагінацію та безпеку.
OpenAPI потрібен не для «гарної Swagger-сторінки», а для формалізації контракту між сервером і споживачами API. Якщо дизайн API є хорошим, OpenAPI робить його видимим, перевірюваним і придатним для автоматизації. Якщо дизайн API є поганим, OpenAPI дуже швидко це викриває.
Практичний висновок такий:
Саме в такому вигляді специфікація стає частиною інженерної дисципліни, а не просто ще одним YAML-файлом у репозиторії.
Процес проєктування API та документування
Покроковий алгоритм проєктування API: від аналізу предметної області до документації, версіонування, code style та чеклиста якості.
Основи аутентифікації та авторизації
Authentication vs Authorization, Claims-модель ідентичності .NET, ClaimsPrincipal, AuthenticationMiddleware, схеми аутентифікації та повний потік обробки запиту.