OpenAPI to format opisu interfejsu HTTP w zwykłym pliku YAML lub JSON. W pliku wypisuje się, jakie ścieżki udostępnia API, jakie metody każda z nich przyjmuje, jakie pola jadą w żądaniu i co zwraca odpowiedź. Plik czyta zarówno człowiek, jak i program, dzięki czemu dokumentację, biblioteki klienckie i atrapy serwera do testów da się wygenerować z jednego źródła.
Oficjalna nazwa to OpenAPI Specification, w skrócie OAS. Opiekuje się nią OpenAPI Initiative, projekt o otwartym modelu zarządzania działający przy Linux Foundation.
Dalej: jak zbudowany jest dokument, czym OpenAPI różni się od Swaggera, co zmieniło się w wersjach 3.1 i 3.2 oraz dlaczego główna obietnica tego formatu — dokumentacja, która nigdy się nie starzeje — sama z siebie się nie spełnia.
Jak wygląda dokument OpenAPI
Działający plik jest krótszy, niż większość zakłada:
openapi: 3.1.0
info:
title: Linki
version: "1.0"
paths:
/links:
post:
summary: Utwórz krótki link
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [url]
properties:
url:
type: string
format: uri
responses:
'201':
description: Link utworzony
Na najwyższym poziomie obowiązkowe są tylko dwa pola, openapi i info; poza nimi dokument musi zawierać co najmniej jedno z paths, components lub webhooks. Tak określa to specyfikacja OAS 3.1.1.
Już w tych pierwszych wierszach czai się pułapka: openapi i info.version to dwie różne rzeczy. Pierwsze to wersja samego formatu, po której narzędzia poznają, jak interpretować dokument. Drugie to wersja waszego interfejsu. Specyfikacja mówi o tym wprost, ale intuicja podpowiada odwrotnie — i stąd pliki deklarujące openapi: 1.0.
Większe specyfikacje zwykle rozbija się na kilka plików przez $ref: dokument główny wskazuje na ./paths/links.yaml, a ten na ./schemas/link.yaml. Wygodne i zarazem źródło problemu, do którego wrócę na końcu.
OpenAPI i Swagger to nie to samo
Najczęstsze nieporozumienie w tym temacie.
| Czym jest | Kto to prowadzi | |
|---|---|---|
| OpenAPI | standard, czyli sam format | OpenAPI Initiative, Linux Foundation |
| Swagger | rodzina narzędzi czytających ten format | SmartBear |
Zamieszanie tłumaczy historia. Na początku Swagger oznaczał jednocześnie specyfikację i narzędzia. Potem specyfikację przekazano do OpenAPI Initiative i przemianowano na OpenAPI Specification, a nazwa Swagger została przy narzędziach — Swagger UI, Swagger Editor i pozostałych.
W praktyce „korzystamy ze Swaggera" prawie zawsze znaczy „mamy plik OpenAPI i wyświetlamy go przez Swagger UI". Nic tych dwóch rzeczy ze sobą nie wiąże: ten sam plik równie dobrze otworzy się w Redoc, w Scalarze albo w generatorze klientów.
Wersje: 3.0, 3.1 i 3.2
Różnice między gałęziami nie są kosmetyczne.
3.0 wciąż jest najszerzej wdrożona. Jej słabym punktem są schematy danych: przypominają JSON Schema, nie będąc z nim w pełni zgodne, co raz po raz psuje współpracę między narzędziami.
3.1 tę lukę zamknęła. Obiekt Schema stał się nadzbiorem JSON Schema Draft 2020-12, więc schematy utrzymywane już w JSON Schema można wykorzystać wprost, zamiast przepisywać. Obowiązująca rewizja 3.1.1 ukazała się 24 października 2024 roku.
3.2 pojawiła się we wrześniu 2025 roku. Z ogłoszenia OpenAPI Initiative na uwagę zasługują przede wszystkim:
- metoda HTTP
query, do idempotentnych odczytów, których parametry nie mieszczą się w adresie; additionalOperationsdla metod spoza standardu;- strumieniowe typy mediów: Server-Sent Events, JSON Lines, sekwencje JSON;
- hierarchiczne znaczniki dzięki
summary,parentikind; - przepływ urządzenia w OAuth 2.0, dla sprzętu z niewygodnym wprowadzaniem danych.
Wersję warto dobierać do tego, co wasze narzędzia naprawdę obsługują, a nie do najwyższego numeru. Wsparcie dla 3.1 w ekosystemie jest solidne; 3.2 jest nowsza i część łańcucha narzędzi wciąż ją nadrabia. Nasza własna specyfikacja deklaruje 3.1.0.
Co to daje w praktyce
Jeden plik załatwia kilka spraw naraz:
- interaktywna dokumentacja — Swagger UI albo Redoc budują stronę prosto z pliku, razem z formularzem do wypróbowania żądania;
- biblioteki klienckie — generatory wypuszczają SDK w wybranym języku, zamiast pisanych ręcznie nakładek;
- atrapy serwera — mock można postawić na podstawie samego opisu, zanim powstanie backend, więc frontend nie czeka;
- testy kontraktu — testy zestawiają prawdziwe odpowiedzi ze schematami i wychwytują moment, w którym odpowiedź przestaje zgadzać się z opisem.
Z tej listy bierze się główny argument za OpenAPI: dokumentacja przestaje być osobnym tekstem, o którego aktualizacji ktoś zapomina.
Argument jest słuszny. Tyle że nie realizuje się sam.
Specyfikacji nic nie łączy z kodem
To ta część, której teksty „czym jest OpenAPI" prawie nigdy nie poruszają, a która gryzie na produkcji.
Plik specyfikacji to zwyczajnie plik. Router nie wie o jego istnieniu. Kontroler również nie. Można usunąć punkt końcowy, dodać nowy, zamienić pole obowiązkowe na opcjonalne — i nie wywali się ani jeden test. Dokumentacja nie synchronizuje się sama; pozostaje dokładna dokładnie tak długo, jak długo ktoś pamięta, żeby ją poprawić.
Rozjazd następuje w obie strony i każda szkodzi inaczej:
- opisane, ale nie działa. Ktoś czyta dokumentację, wysyła żądanie i dostaje 404. Zaufanie do reszty dokumentacji znika w tej samej chwili: skoro tutaj było nieprawdziwe, to gdzie jeszcze?
- działa, ale nie jest opisane. Dla kogoś, kto tego szuka, taka funkcja po prostu nie istnieje. Nikt jej nie znajdzie, nikt za nią nie zapłaci.
Groźniejszy jest drugi przypadek, bo nikt nie narzeka. Nie ma zresztą komu: użytkownik nie wie, że ten punkt końcowy w ogóle jest.
Dokładnie to nam się przydarzyło. W API Lix.li działały trzy punkty końcowe od konwersji — lista zdarzeń oraz przyjmowanie postbacków, pojedynczo i partiami — i żaden nie figurował w specyfikacji. Kod działał, testy przechodziły, a tymczasem strona prezentująca API pokazywała dwie zupełnie inne ścieżki, których w kodzie nie było wcale. Wyszło to przy pełnym przeglądzie, a nie ze zgłoszenia błędu.
Jak wyłapywać rozjazd automatycznie
Tego nie rozwiązuje dyscyplina, tylko test. Pomysł jest prosty: wziąć listę ścieżek ze specyfikacji, wziąć listę tras z routera i porównać oba zbiory w obie strony.
ścieżki z openapi.yaml → czy taka trasa istnieje? → jeśli nie, dokumentacja kłamie
trasy z routera → czy ścieżka jest opisana? → jeśli nie, funkcja jest niewidoczna
Połowę „to, co opisane, działa" najwygodniej sprawdzić prawdziwym żądaniem: wywołać punkt końcowy bez poświadczeń i upewnić się, że odpowiedź to 401, a nie 404. To rozróżnienie ma znaczenie. 401 oznacza, że trasa się znalazła i domaga się klucza; 404 — że takiej ścieżki nie ma. Nic przy tym nie zostaje zapisane, bo uwierzytelnianie wykonuje się przed kontrolerem.
Sprawdzenie w drugą stronę czyta konfigurację tras. Przy okazji wymusza jawne wypisanie wyjątków — wewnętrznych punktów końcowych, którym w publicznym kontrakcie nic do rzeczy. Ta lista wyjątków sama w sobie jest cenna: zmusza, żeby raz i świadomie ustalić, co uznajecie za swoje publiczne API.
Po historii z konwersjami napisaliśmy właśnie taki test. Od razu się obronił: jeśli znowu usunąć konwersje ze specyfikacji, test przestaje przechodzić i wymienia obie brakujące ścieżki z nazwy.
Dwie pułapki, które odkrywa się późno
$ref i ścieżki względne. Gdy specyfikacja jest rozbita na pliki, odwołania wewnątrz niej rozwiązują się względem adresu, spod którego plik został podany. Dokument główny dostępny pod /openapi poszuka ./paths/links.yaml pod adresem /paths/links.yaml i nic nie znajdzie. W przeglądarce wszystko wygląda poprawnie, bo Swagger UI ładuje plik z jego prawdziwego miejsca — za to generator klientów wycelowany w ten sam dokument się potknie. Albo podajecie specyfikację wyłącznie z jej własnego katalogu, albo pakujecie ją w jeden plik bez zewnętrznych $ref.
Swagger UI jest pusty dla robota indeksującego. Strona Swagger UI to kilka wierszy znaczników plus skrypt, który dorysowuje treść w przeglądarce. W podawanym kodzie HTML nie ma ani jednej ścieżki punktu końcowego. Dla czytającego strona jest w porządku, dla indeksowania — pusta. Do tego dochodzi częsty błąd w robots.txt: reguła Disallow: /api/, napisana po to, żeby ukryć punkty końcowe JSON, ukrywa przy okazji dokumentację dla ludzi mieszkającą pod tym samym przedrostkiem. Blokować należy dokładną ścieżkę z wersją — na przykład /api/1.0/ — a nie całą sekcję.
Od czego zacząć
Jeśli API już jest, a opisu nie ma, kolejność wygląda mniej więcej tak:
- Opiszcie jeden punkt końcowy ręcznie. To wystarczy, żeby zrozumieć strukturę.
- Wybierzcie wersję, którą wasze narzędzia obsługują bez zająknięcia; domyślnie 3.1.
- Rozbijcie plik przez
$ref, kiedy przestanie mieścić się na ekranie, i od razu ustalcie, spod jakiego adresu jest podawany. - Postawcie test porównujący specyfikację z routerem w obie strony. Do tego kroku każde zapewnienie o aktualnej dokumentacji jest obietnicą, nie właściwością.
Jak to wygląda na prawdziwym interfejsie, widać w dokumentacji API Lix.li: są tam linki, grupy, testy A/B i konwersje. Usługę zbudowaną wokół tego interfejsu opisuje strona API do skracania linków. Dwa sąsiednie tematy warte osobnego zgłębienia to webhooki, jeśli wolicie zdarzenia odbierać, a nie odpytywać o nie, oraz różnica między przekierowaniami 301, 302, 307 i 308 — bo 301 zamienia POST na GET, co dotyczy wprost wspomnianych wyżej punktów końcowych od postbacków.