OpenAPI ist ein Format, um eine HTTP-Schnittstelle in einer gewöhnlichen YAML- oder JSON-Datei zu beschreiben. Darin steht, welche Pfade die Schnittstelle anbietet, welche Methoden sie annehmen, welche Felder in der Anfrage mitgehen und was die Antwort zurückgibt. Die Datei lesen Menschen und Programme gleichermaßen, weshalb sich Dokumentation, Client-Bibliotheken und Mock-Server aus derselben Quelle erzeugen lassen.

Der formale Name lautet OpenAPI Specification, kurz OAS. Gepflegt wird sie von der OpenAPI Initiative, einem Projekt mit offener Governance unter dem Dach der Linux Foundation.

Im Folgenden: wie das Dokument aufgebaut ist, worin sich OpenAPI und Swagger unterscheiden, was sich in 3.1 und 3.2 geändert hat — und warum das zentrale Versprechen des Formats, eine Dokumentation die nie veraltet, von allein nicht eingelöst wird.

Wie ein OpenAPI-Dokument aussieht

Eine funktionierende Datei ist kürzer, als die meisten erwarten:

openapi: 3.1.0

info:
  title: Links
  version: "1.0"

paths:
  /links:
    post:
      summary: Kurzlink erstellen
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
      responses:
        '201':
          description: Link erstellt

Pflicht auf oberster Ebene sind nur zwei Felder, openapi und info; zusätzlich muss das Dokument mindestens eines von paths, components oder webhooks enthalten. So legt es die Spezifikation OAS 3.1.1 fest.

Schon in diesen ersten Zeilen steckt eine Falle: openapi und info.version sind zwei verschiedene Dinge. Das erste ist die Version des Formats selbst, an der Werkzeuge ablesen, wie das Dokument zu interpretieren ist. Das zweite ist die Version Ihrer Schnittstelle. Die Spezifikation sagt das ausdrücklich, die Intuition legt das Gegenteil nahe — und so entstehen Dateien, die openapi: 1.0 deklarieren.

Größere Spezifikationen werden meist über $ref auf mehrere Dateien verteilt: Das Wurzeldokument verweist auf ./paths/links.yaml, dieses auf ./schemas/link.yaml. Praktisch — und die Ursache eines Problems, auf das ich am Schluss zurückkomme.

OpenAPI und Swagger sind nicht dasselbe

Die häufigste Verwechslung im ganzen Thema.

Was es ist Wer es pflegt
OpenAPI der Standard, das Format selbst OpenAPI Initiative, Linux Foundation
Swagger eine Werkzeugfamilie, die dieses Format liest SmartBear

Die Geschichte erklärt das Durcheinander. Ursprünglich bezeichnete Swagger beides, Spezifikation und Werkzeuge. Später ging die Spezifikation an die OpenAPI Initiative über und heißt seither OpenAPI Specification, während der Name Swagger bei den Werkzeugen blieb — Swagger UI, Swagger Editor und den übrigen.

In der Praxis heißt „wir haben Swagger" fast immer „wir haben eine OpenAPI-Datei und stellen sie mit Swagger UI dar". Zwingend verbunden ist das nicht: Dieselbe Datei öffnet sich in Redoc, Scalar oder einem Client-Generator genauso gut.

Versionen: 3.0, 3.1 und 3.2

Die Unterschiede zwischen den Zweigen sind nicht kosmetisch.

3.0 ist nach wie vor am weitesten verbreitet. Schwachstelle sind die Datenschemata: Sie ähneln JSON Schema, ohne vollständig dazu kompatibel zu sein, was die Zusammenarbeit zwischen Werkzeugen immer wieder stört.

3.1 hat diese Lücke geschlossen. Das Schema-Objekt wurde zu einer Obermenge von JSON Schema Draft 2020-12, sodass bereits vorhandene JSON-Schema-Modelle weiterverwendet statt neu geschrieben werden können. Die aktuelle Fassung 3.1.1 erschien am 24. Oktober 2024.

3.2 kam im September 2025. Aus der Ankündigung der OpenAPI Initiative ragen heraus:

  • die HTTP-Methode query für idempotente Abfragen, deren Parameter nicht in die URL passen;
  • additionalOperations für nicht standardisierte Methoden;
  • Streaming-Medientypen: Server-Sent Events, JSON Lines, JSON-Sequenzen;
  • hierarchische Tags über summary, parent und kind;
  • OAuth-2.0-Device-Flow für Geräte mit umständlicher Eingabe.

Die Version sollte sich danach richten, was Ihre Werkzeuge tatsächlich beherrschen, nicht nach der höchsten Nummer. Die Unterstützung für 3.1 im Ökosystem ist solide; 3.2 ist neuer, und Teile der Werkzeugkette ziehen noch nach. Unsere eigene Spezifikation deklariert 3.1.0.

Was die Spezifikation praktisch einbringt

Eine Datei erledigt mehrere Aufgaben zugleich:

  • interaktive Dokumentation — Swagger UI oder Redoc bauen die Seite direkt aus der Datei, samt Formular zum Ausprobieren;
  • Client-Bibliotheken — Generatoren erzeugen ein SDK für die gewünschte Sprache, statt handgeschriebener Wrapper;
  • Mocks — ein Mock-Server läuft anhand der Beschreibung, bevor das Backend existiert, und die Frontend-Arbeit steht nicht still;
  • Vertragstests — Tests vergleichen echte Antworten mit den Schemata und bemerken den Moment, in dem eine Antwort nicht mehr zur Beschreibung passt.

Aus dieser Liste stammt das Hauptargument für OpenAPI: Dokumentation ist kein separater Text mehr, den jemand zu aktualisieren vergisst.

Das Argument stimmt. Es erfüllt sich nur nicht von selbst.

Nichts verbindet die Spezifikation mit dem Code

Das ist der Teil, den Einführungstexte zum Thema kaum erwähnen — und der im Betrieb zubeißt.

Die Spezifikationsdatei ist bloß eine Datei. Der Router weiß nichts von ihr, der Controller ebenso wenig. Sie können einen Endpunkt löschen, einen hinzufügen oder ein Pflichtfeld optional machen: Kein einziger Test schlägt fehl. Dokumentation synchronisiert sich nicht selbst. Sie bleibt genau so lange richtig, wie jemand daran denkt, sie zu pflegen.

Auseinanderlaufen kann es in beide Richtungen, und jede schadet anders:

  • dokumentiert, aber funktioniert nicht. Jemand liest die Dokumentation, schickt die Anfrage und bekommt einen 404. Das Vertrauen in den Rest der Dokumentation bricht sofort weg — wenn das hier falsch war, was noch?
  • funktioniert, aber ist nicht dokumentiert. Für alle, die danach suchen, existiert die Funktion schlicht nicht. Niemand findet sie, niemand zahlt dafür.

Der zweite Fall ist der gefährlichere, weil sich niemand beschwert. Es gibt auch niemanden, der es könnte: Die Nutzer wissen nicht, dass der Endpunkt da ist.

Genau das ist uns passiert. Die Lix.li-Schnittstelle hatte drei funktionierende Endpunkte für Conversions — eine Ereignisliste sowie die Annahme einzelner und gebündelter Postbacks — und keiner davon stand in der Spezifikation. Der Code lief, die Tests waren grün, und auf der API-Landingpage prangten derweil zwei ganz andere Pfade, die es im Code überhaupt nicht gab. Aufgefallen ist das bei einer vollständigen Prüfung, nicht durch eine Fehlermeldung.

Abweichungen automatisch aufspüren

Das lässt sich nicht durch Disziplin lösen, sondern durch einen Test. Der Gedanke ist schlicht: die Pfade aus der Spezifikation nehmen, die Regeln aus dem Router nehmen und beide Mengen in beide Richtungen vergleichen.

Pfade aus openapi.yaml   →   gibt es die Route?        →   sonst lügt die Dokumentation
Routen aus dem Router    →   ist der Pfad beschrieben? →   sonst ist die Funktion unsichtbar

Die Hälfte „Dokumentiertes funktioniert" prüft man am bequemsten mit einer echten Anfrage: den Endpunkt ohne Zugangsdaten aufrufen und sicherstellen, dass die Antwort 401 lautet und nicht 404. Der Unterschied zählt. 401 heißt, die Route wurde gefunden und verlangt einen Schlüssel; 404 heißt, es gibt diesen Pfad nicht. Geschrieben wird dabei nichts, denn die Authentifizierung läuft vor dem Controller.

Die Gegenrichtung liest die Routenkonfiguration. Sie zwingt außerdem dazu, die Ausnahmen ausdrücklich aufzulisten — interne Endpunkte, die in einem öffentlichen Vertrag nichts verloren haben. Diese Liste ist für sich genommen wertvoll: Sie erzwingt eine einmalige, bewusste Entscheidung darüber, was als öffentliche Schnittstelle gilt.

Nach der Sache mit den Conversions haben wir diesen Test geschrieben. Er hat sich sofort bezahlt gemacht: Entfernt man die Conversions wieder aus der Spezifikation, schlägt er fehl und benennt beide fehlenden Pfade.

Zwei Fallen, die man spät bemerkt

$ref und relative Pfade. Ist eine Spezifikation auf mehrere Dateien verteilt, lösen sich die Verweise darin relativ zu der URL auf, unter der die Datei ausgeliefert wurde. Ein Wurzeldokument unter /openapi sucht ./paths/links.yaml dann unter /paths/links.yaml und findet nichts. Im Browser wirkt alles korrekt, weil Swagger UI die Datei von ihrem tatsächlichen Ort lädt — ein Client-Generator, auf dasselbe Dokument gerichtet, scheitert dagegen. Entweder Sie liefern die Spezifikation ausschließlich aus ihrem eigenen Verzeichnis aus, oder Sie bündeln sie zu einer einzigen Datei ohne externe $ref.

Swagger UI ist für einen Crawler leer. Eine Swagger-UI-Seite besteht aus wenigen Zeilen Markup und einem Skript, das den Inhalt erst im Browser zeichnet. Im ausgelieferten HTML steht kein einziger Endpunkt-Pfad. Für Lesende ist die Seite in Ordnung, für die Indexierung ist sie leer. Dazu kommt ein verbreiteter Fehler in der robots.txt: Eine Regel Disallow: /api/, gedacht zum Ausblenden der JSON-Endpunkte, blendet die menschenlesbare Dokumentation unter demselben Präfix gleich mit aus. Sperren sollte man den genauen versionierten Pfad, etwa /api/1.0/, nicht den ganzen Bereich.

Womit anfangen

Wenn die Schnittstelle schon existiert und die Beschreibung noch nicht, ist die Reihenfolge ungefähr diese:

  1. Beschreiben Sie einen Endpunkt von Hand. Das genügt, um die Struktur zu verstehen.
  2. Wählen Sie die Version, die Ihre Werkzeuge sicher beherrschen; im Zweifel 3.1.
  3. Teilen Sie die Datei per $ref auf, sobald sie nicht mehr auf einen Bildschirm passt, und legen Sie sofort fest, von welcher URL sie ausgeliefert wird.
  4. Richten Sie einen Test ein, der Spezifikation und Router in beide Richtungen vergleicht. Bis zu diesem Schritt ist jede Aussage über aktuelle Dokumentation ein Versprechen, keine Eigenschaft.

Wie das an einer echten Schnittstelle aussieht, zeigt die API-Dokumentation von Lix.li mit Links, Gruppen, A/B-Tests und Conversions. Den Dienst rund um diese Schnittstelle beschreibt die Seite zur URL-Shortener-API. Aus verwandten Themen passt die Aufschlüsselung der Weiterleitungen 301, 302, 307 und 308 — sie erklärt unter anderem, warum ein 301 aus einem POST ein GET macht, was bei Postback-Endpunkten wie den oben erwähnten Conversions direkt relevant wird. Wer Ereignisse lieber zugestellt bekommt, statt sie abzufragen, kommt außerdem an Webhooks nicht vorbei.