Die erste Frage bei API-Versionierung ist nicht “Wie?”, sondern “Muss ich ueberhaupt?”. In den meisten Projekten wird Versionierung eingeführt, bevor es einen einzigen Consumer gibt, der von einem Breaking Change betroffen wäre. Das ist premature abstraction — und sie kostet mehr, als sie bringt.
Warum die meisten Teams zu früh versionieren
Versionierung erzeugt Komplexität. Jede Version ist ein separater Codepfad, der getestet, dokumentiert und gewartet werden muss. Zwei Versionen bedeuten nicht doppelten Aufwand, aber mindestens 40-60% mehr. Drei Versionen und das Team verbringt mehr Zeit mit Kompatibilität als mit Features.
Das YAGNI-Prinzip gilt hier besonders: Solange eine API nur interne Consumers hat — das eigene Frontend, die eigene Mobile-App — braucht man keine formale Versionierung. Man deployed Client und Server zusammen, und fertig. Versionierung wird erst relevant, wenn externe Consumers existieren, die man nicht kontrolliert.
Die drei Strategien im Überblick
1. URL-Pfad-Versionierung
Die einfachste und verbreitetste Variante. Die Version steht direkt in der URL.
GET /v1/users/42
GET /v2/users/42
# Routing in einem typischen Webframework
# /v1/* -> UsersControllerV1
# /v2/* -> UsersControllerV2 Vorteile:
- Sofort sichtbar und verstaendlich
- Einfaches Routing — jede Version ist ein eigener Pfad
- Caching funktioniert out of the box (unterschiedliche URLs = unterschiedliche Cache-Keys)
- API-Gateways können trivial nach Version routen
Nachteile:
- Suggeriert, dass sich die gesamte API geändert hat, auch wenn nur ein Endpoint betroffen ist
- URLs sind keine Ressourcen-Identifier mehr —
/v1/users/42und/v2/users/42sind der gleiche User - Foerdert “Big Bang”-Versionierung statt granularer Änderungen
- Clients müssen URLs aktualisieren, nicht nur Header
2. Header-basierte Versionierung
Die Version wird über einen Custom Header mitgeschickt. Der URL-Raum bleibt stabil.
GET /users/42
Accept-Version: v2
# Oder mit einem API-spezifischen Header
GET /users/42
X-API-Version: 2024-01-15
# Server-Antwort enthaelt die verwendete Version
HTTP/1.1 200 OK
X-API-Version: 2024-01-15
Content-Type: application/json Vorteile:
- Saubere URL-Struktur — Ressourcen behalten ihre kanonische Adresse
- Erlaubt datumsbasierte Versionen (wie Stripe es macht)
- Version kann pro Request variieren, ohne URLs zu ändern
Nachteile:
- Nicht sichtbar beim Lesen einer URL — Debugging wird schwieriger
- Custom Headers werden von manchen Proxies und CDNs gefiltert
- Erfordert explizite Dokumentation, da nicht selbsterklärend
- Caching erfordert
Vary-Header-Konfiguration
3. Content Negotiation
Die Version wird über den Accept-Header als Media Type ausgehandelt. Das ist technisch die “korrekteste” Lösung nach REST-Prinzipien.
# Version über Media Type
GET /users/42
Accept: application/vnd.myapi.v2+json
# Oder mit einem Parameter
GET /users/42
Accept: application/vnd.myapi+json;version=2
# GitHub-Stil
GET /repos/octocat/hello-world
Accept: application/vnd.github.v3+json Vorteile:
- Nutzt HTTP so, wie es gedacht war
- Erlaubt gleichzeitig verschiedene Repraesentationen (JSON, XML) und Versionen
- Granulare Kontrolle pro Ressource
Nachteile:
- Komplex in der Implementierung
- Schwer zu testen (curl-Befehle werden unübersichtlich)
- Die meisten Entwickler sind damit nicht vertraut
- API-Dokumentationstools unterstützen es schlecht
Breaking vs. Non-Breaking Changes
Bevor man über Versionierung nachdenkt, muss man verstehen, was tatsächlich einen Versionssprung erfordert. Die Antwort: deutlich weniger, als die meisten denken.
Non-Breaking Changes (keine neue Version nötig):
- Neue Felder in Responses hinzufügen
- Neue optionale Query-Parameter
- Neue Endpoints
- Änderung interner Implementierungsdetails
- Neue Enum-Werte (wenn der Client unbekannte Werte ignoriert)
Breaking Changes (neue Version nötig):
- Felder aus Responses entfernen oder umbenennen
- Pflichtfelder zu Requests hinzufügen
- Datentyp eines bestehenden Feldes ändern
- Semantik eines Feldes ändern (gleicher Name, anderes Verhalten)
- Entfernen oder Umbenennen von Endpoints
- Änderung von Fehlerformaten
Additive-Only API Design
Die beste Versionierungsstrategie ist oft: keine Versionierung. Stattdessen designt man die API so, dass Breaking Changes gar nicht erst entstehen.
Die Regeln sind einfach:
- Nie Felder entfernen. Deprecated Felder liefern weiter Daten (oder Defaults).
- Nie Typen ändern.
"count": 5wird nicht zu"count": "5". - Neue Felder sind immer optional. Bestehende Clients senden sie nicht — der Server muss damit umgehen.
- Neue Endpoints statt geänderter.
/users/42/preferencesstatt ein geändertes/users/42. - Feature Flags statt Versionen.
?include=extended_profilestatt/v2/users/42.
// Phase 1: Urspruengliche Response
interface UserResponseV1 {
id: number;
name: string;
email: string;
}
// Phase 2: Erweitert, nicht ersetzt
interface UserResponseV2 extends UserResponseV1 {
// Neue Felder -- bestehende Clients ignorieren sie
avatar_url: string | null;
preferences: UserPreferences;
// Deprecated, aber noch da
/** @deprecated Use preferences.display_name instead */
name: string;
}
// Der Client entscheidet, was er braucht
// GET /users/42?fields=id,email,preferences Deprecation Workflows und Sunset Headers
Wenn eine Version oder ein Endpoint abgelöst wird, braucht es einen klaren Prozess. Der Sunset-Header (RFC 8594) ist dafür das Standardwerkzeug.
# Server signalisiert: Dieser Endpoint wird am 2026-09-01 abgeschaltet
HTTP/1.1 200 OK
Sunset: Sat, 01 Sep 2026 00:00:00 GMT
Deprecation: true
Link: <https://api.example.com/docs/migration-v3>; rel="successor-version"
# Optionaler Warning-Header für zusätzliche Sichtbarkeit
Warning: 299 - "This endpoint is deprecated. Migrate to /v3/users by 2026-09-01" Ein solider Deprecation-Workflow sieht so aus:
- Ankuendigung (6+ Monate vorher): Deprecation-Header setzen, Dokumentation aktualisieren, Changelog schreiben.
- Monitoring: Loggen, welche Clients noch die alte Version nutzen. Aktiv auf sie zugehen.
- Warnphase (3 Monate vorher): Warning-Header hinzufügen, häufigere Kommunikation.
- Sunset: Endpoint liefert
410 Gonemit Link zur neuen Version.
Real-World-Patterns
Stripe: Datumsbasierte Versionen
Stripe nutzt Header-basierte Versionierung mit Datumsversionen (2024-01-15 statt v2). Jede Version ist ein Snapshot des API-Verhaltens an diesem Datum. Neue Accounts bekommen automatisch die neueste Version. Bestehende Accounts bleiben auf ihrer Version, bis sie explizit upgraden.
Das Besondere: Stripe versioniert nicht die gesamte API, sondern einzelne Verhaltensänderungen. Intern werden Änderungen als Feature Flags implementiert, die an Versionen gebunden sind. So kann ein Endpoint in 20 Versionen existieren, ohne dass 20 Codepfade entstehen.
GitHub: Content Negotiation mit pragmatischem Fallback
GitHub nutzt Content Negotiation über den Accept-Header (application/vnd.github.v3+json), aber mit einem wichtigen Pragmatismus: Ohne expliziten Header bekommt man die aktuelle stabile Version. Das senkt die Einstiegshuerde und bestraft nicht Clients, die sich nicht um Versionierung kuemmern.
GitHub ergänzt das durch “API Previews” — experimentelle Features, die über einen speziellen Media-Type aktiviert werden (application/vnd.github.mercy-preview+json). Das entkoppelt Versionierung von Feature-Releases.
API Gateways und Version Routing
In größeren Architekturen übernimmt ein API Gateway das Routing zwischen Versionen. Das hat den Vorteil, dass die Backend-Services nichts von der Versionierung wissen müssen.
Typische Patterns:
- Path-based Routing:
/v1/*geht an Service A,/v2/*an Service B. Einfach, aber erfordert separate Deployments. - Header-based Routing: Ein Gateway liest den Version-Header und leitet an den richtigen Service weiter. Flexibler, aber komplexere Gateway-Konfiguration.
- Transformation Layer: Das Gateway transformiert Requests und Responses zwischen Versionen. Der Service kennt nur die aktuelle Version. Das ist der Stripe-Ansatz — und der mit Abstand wartbarste.
Der Transformation Layer verdient besondere Aufmerksamkeit: Statt zwei Versionen eines Services zu betreiben, schreibt man Transformationsfunktionen, die alte Formate in neue uebersetzen und umgekehrt. Die Logik bleibt zentral, und der Service-Code bleibt sauber.
Praktische Empfehlung
Die richtige Strategie hängt von genau zwei Faktoren ab: wie viele externe Consumers die API hat und wie häufig sich die API ändert.
Interne API (eigenes Frontend/App): Keine Versionierung. Deploy Client und Server zusammen. Investiere in Feature Flags statt in Versionen.
API mit wenigen externen Partnern (unter 10): URL-Pfad-Versionierung. Der Overhead ist minimal, die Vorteile (Sichtbarkeit, einfaches Routing) ueberwiegen. Maximal zwei Versionen gleichzeitig.
API als Produkt (viele externe Consumer): Header-basierte Versionierung mit Datumsversionen, nach dem Stripe-Modell. Investiere in einen Transformation Layer im API Gateway. Das skaliert.
Öffentliche API mit Standards-Anspruch: Content Negotiation. Nur wenn das Team die Komplexität tragen kann und die Consumers sophistiziert genug sind.
Fazit
API-Versionierung ist kein technisches Problem, sondern ein organisatorisches. Die Strategie muss zur Teamgroesse, zur Anzahl der Consumers und zum Änderungsrhythmus passen. Die meisten Teams fahren am besten, wenn sie sich auf Additive-Only Design konzentrieren und Versionierung als Werkzeug der letzten Instanz behandeln. Denn die guenstigste Version ist die, die nie gebaut werden musste.