API-Versionierung mit Claude Code: Praxisleitfaden für sichere Verträge
API-Versionierung mit Claude Code sicher planen: OpenAPI, Kompatibilitätstests, Deprecation-Header und Rollout.
API-Versionierung bedeutet nicht nur, einer Route/v2 hinzuzufügen. Sie ist ein Kompatibilitätsversprechen an mobile Apps, Partner-Integrationen, interne Services, Webhook-Consumer und Batch-Jobs, die bereits von der API abhängen. Ein einzelnes umbenanntes Feld kann die neue Antwort sauberer machen und gleichzeitig einen alten Client brechen.
Claude Code kann dabei helfen, weil es Codebasen liest, Dateien bearbeitet und Befehle ausführt, wie die offizielle Claude-Code-Dokumentation beschreibt. Das Risiko liegt im vagen Prompt. “Modernisiere diese API” führt schnell dazu, dass der Assistent die neue Form optimiert und bestehende Consumer vergisst. Deshalb braucht Claude Code vor der Implementierung Vertrag, Kompatibilitätsregeln, Rollout-Plan und Prüfkommandos.
Dieser Leitfaden behandelt URL-, Header- und Media-Type-Versionierung, OpenAPI-Verträge, Rückwärtskompatibilität,Deprecation- undSunset-Header, Changelog-Politik, Consumer Tests, Rollout mit Fallback und Prompts, die Breaking Changes verhindern. Offizielle Referenzen sind die OpenAPI Specification, RFC 9745 für den Deprecation-Header und RFC 8594 für den Sunset-Header.
Für angrenzende Themen siehe API-Entwicklung mit Claude Code, Code Review mit Claude Code und Versionsverwaltung mit Changesets.
Mit Dem Kompatibilitätsvertrag Starten
Versionierung soll alten Code nicht endlos konservieren. Sie soll Consumern eine planbare Migration ermöglichen. Masa testete das an einer kleinen Orders API: Als der Prompt nur “füge v2 hinzu und benenne Kundendaten um” sagte, bestand der neue Admin-Screen, aber ein alter CSV-Export brach. Es fehlten konkrete Regeln: v1-Antwortform behalten, Deprecation-Datum veröffentlichen, Consumer Test ergänzen und Migrationshinweise schreiben.
Drei Use Cases sind typisch:
| Use Case | Wichtige Einschränkung | Häufig passende Variante |
|---|---|---|
| Öffentliche REST API für mobile Apps | Alte App-Versionen bleiben monatelang aktiv | URL-Pfad-Versionierung |
| B2B-SaaS-Partner-API | Kunden migrieren nach eigenem Kalender | URL-Pfad oder expliziter Header |
| Interne Microservices | Clients können oft gemeinsam aktualisiert werden | Header oder Media Type |
Bevor Claude Code implementiert, dokumentierst du aktuelle Consumer, minimale Supportdauer, Definition von Breaking Change und die Metriken. Breaking Changes sind nicht nur gelöschte Routen. Auch umbenannte Antwortfelder, neue Pflichtfelder, andere Fehlerformate, geänderte Standardsortierung oder inkompatible Pagination können alte Clients beschädigen.
URL, Header Oder Media Type Auswählen
Wo die Version steht, beeinflusst Routing, Caching, Dokumentation, SDK-Generierung und Support. Für öffentliche APIs ist URL-Pfad-Versionierung oft der pragmatische Startpunkt: sichtbar in Logs, einfach im API Gateway, leicht mitcurl zu testen. Der Nachteil ist, dass die Ressourcen-URI eine Produktversion enthält. /api/v1/orders/123 und/api/v2/orders/123 wirken wie unterschiedliche Ressourcen.
| Stil | Beispiel | Stärke | Typischer Fehler |
|---|---|---|---|
| URL-Pfad | /api/v1/orders | Klare Routen, klare Dokumentation, einfache Fehlersuche | Alte Pfade bleiben, Router-Duplikation wächst |
| Custom Header | API-Version: 2 | Stabile URL, gut für kontrollierte Clients | Header wird vergessen; Cache brauchtVary: API-Version |
| Media Type | Accept: application/vnd.acme.orders.v2+json | Passt zur HTTP Content Negotiation | OpenAPI, SDKs und Support werden komplexer |
Bei Media-Type-Versionierung sendeVary: Accept, damit Zwischen-Caches v1 und v2 nicht mischen. Bei Header-Versionierung sendeVary: API-Version. Auch bei URL-Versionierung sollten v1 und v2 in OpenAPI getrennte Verträge sein, sobald sich die Antwortkompatibilität ändert.
OpenAPI Als Quelle Der Wahrheit Nutzen
OpenAPI beschreibt HTTP APIs maschinenlesbar: Pfade, Methoden, Parameter, Request Bodies, Responses und Security. Einfach gesagt ist es das API-Versprechen vor der Implementierung. Das Feldopenapi bezeichnet die OpenAPI-Spezifikationsversion, währendinfo.version die Version deiner API-Dokumentation bezeichnet. Claude Code sollte diese Unterscheidung explizit kennen.
Das folgende Beispiel dokumentiert v1 weiter, markiert es als deprecated und fügt v2 hinzu. Es verwendetopenapi: 3.1.0, weil viele Validatoren und Generatoren es gut unterstützen. Prüfe die offizielle OpenAPI-Seite, bevor du eine neuere Spezifikationsversion im Team festlegst.
openapi: 3.1.0
info:
title: Acme Orders API
version: 2.0.0
servers:
- url: https://api.example.com
paths:
/api/v1/orders/{orderId}:
get:
operationId: getOrderV1
summary: Get an order in the legacy v1 shape
deprecated: true
x-deprecated-at: "2026-03-31T00:00:00Z"
x-sunset-at: "2026-12-31T23:59:59Z"
parameters:
- name: orderId
in: path
required: true
schema:
type: string
responses:
"200":
description: Legacy order response
headers:
Deprecation:
schema:
type: string
description: RFC 9745 structured date, for example @1774915200
Sunset:
schema:
type: string
description: RFC 8594 HTTP-date when v1 may stop responding
content:
application/json:
schema:
$ref: "#/components/schemas/OrderV1Envelope"
/api/v2/orders/{orderId}:
get:
operationId: getOrderV2
summary: Get an order in the current v2 shape
parameters:
- name: orderId
in: path
required: true
schema:
type: string
responses:
"200":
description: Current order response
content:
application/json:
schema:
$ref: "#/components/schemas/OrderV2Envelope"
components:
schemas:
OrderV1Envelope:
type: object
required: [data]
properties:
data:
type: object
required: [id, customerName, totalCents, currency]
properties:
id:
type: string
customerName:
type: string
totalCents:
type: integer
currency:
type: string
OrderV2Envelope:
type: object
required: [data]
properties:
data:
type: object
required: [id, customer, amount, status]
properties:
id:
type: string
customer:
type: object
required: [displayName]
properties:
displayName:
type: string
amount:
type: object
required: [value, currency]
properties:
value:
type: integer
currency:
type: string
status:
type: string
enum: [paid, shipped]
Gib Claude Code zuerst diese YAML-Datei und fordere danach Implementierung an. Die Anweisung muss klar sein: keine v1-Felder entfernen, keine v1-Statuscodes ändern, Tests und CHANGELOG bei Vertragsänderungen mitpflegen.
Rückwärtskompatibilität In Node Implementieren
Der folgende TypeScript-Server nutzt nur Node-Bordmittel. Speichere ihn alsapi-versioning-demo.ts, um Versionierung per URL,API-Version-Header undAccept-Media-Type ohne Datenbank oder Framework zu testen. v1 behält die alte Form, v2 liefert die aktuelle Form, und v1 sendet die Deprecation-Signale.
import { createServer } from "node:http";
import { parse } from "node:url";
type ApiVersion = "v1" | "v2";
type OrderRow = {
id: string;
customerName: string;
totalCents: number;
currency: "JPY" | "USD";
status: "paid" | "shipped";
createdAt: string;
};
const orders = new Map<string, OrderRow>([
[
"o_100",
{
id: "o_100",
customerName: "Masa Tanaka",
totalCents: 129800,
currency: "JPY",
status: "paid",
createdAt: "2026-06-02T09:00:00.000Z",
},
],
]);
function detectVersion(req: { headers: Record<string, string | string[] | undefined> }, pathname: string) {
const pathVersion = pathname.match(/^\/api\/(v[12])\//)?.[1] as ApiVersion | undefined;
if (pathVersion) return { version: pathVersion, source: "path" };
const header = req.headers["api-version"];
if (typeof header === "string") {
const normalized = header.startsWith("v") ? header : `v${header}`;
if (normalized === "v1" || normalized === "v2") {
return { version: normalized, source: "header" };
}
throw new Error(`Unsupported API-Version: ${header}`);
}
const accept = req.headers.accept;
if (typeof accept === "string") {
const mediaMatch = accept.match(/application\/vnd\.acme\.orders\.v([12])\+json/);
if (mediaMatch) {
return { version: `v${mediaMatch[1]}` as ApiVersion, source: "media-type" };
}
}
return { version: "v1" as ApiVersion, source: "default" };
}
function orderIdFrom(pathname: string) {
return pathname.match(/^\/api\/(?:v[12]\/)?orders\/([^/]+)$/)?.[1];
}
function toV1(row: OrderRow) {
return {
data: {
id: row.id,
customerName: row.customerName,
totalCents: row.totalCents,
currency: row.currency,
},
};
}
function toV2(row: OrderRow) {
return {
data: {
id: row.id,
customer: { displayName: row.customerName },
amount: { value: row.totalCents, currency: row.currency },
status: row.status,
createdAt: row.createdAt,
},
};
}
function addDeprecationHeaders(res: import("node:http").ServerResponse) {
const deprecatedAt = Math.floor(Date.parse("2026-03-31T00:00:00Z") / 1000);
res.setHeader("Deprecation", `@${deprecatedAt}`);
res.setHeader("Sunset", new Date("2026-12-31T23:59:59Z").toUTCString());
res.setHeader(
"Link",
[
'<https://docs.example.com/api/deprecations/v1-to-v2>; rel="deprecation"; type="text/html"',
'<https://docs.example.com/api/sunset-policy>; rel="sunset"; type="text/html"',
].join(", "),
);
}
function sendJson(res: import("node:http").ServerResponse, status: number, body: unknown) {
res.writeHead(status, { "content-type": "application/json; charset=utf-8" });
res.end(JSON.stringify(body, null, 2));
}
const server = createServer((req, res) => {
const pathname = parse(req.url ?? "/").pathname ?? "/";
const orderId = orderIdFrom(pathname);
if (!orderId) {
return sendJson(res, 404, { error: "not_found", message: "Route not found" });
}
let detected: ReturnType<typeof detectVersion>;
try {
detected = detectVersion(req, pathname);
} catch (error) {
return sendJson(res, 400, {
error: "unsupported_version",
message: error instanceof Error ? error.message : "Unsupported API version",
supportedVersions: ["v1", "v2"],
});
}
const row = orders.get(orderId);
if (!row) {
return sendJson(res, 404, { error: "order_not_found", orderId });
}
res.setHeader("Vary", "Accept, API-Version");
res.setHeader("X-API-Version", detected.version);
res.setHeader("X-API-Version-Source", detected.source);
if (detected.version === "v1") {
addDeprecationHeaders(res);
return sendJson(res, 200, toV1(row));
}
return sendJson(res, 200, toV2(row));
});
const port = Number(process.env.PORT ?? 18080);
server.listen(port, () => {
console.log(`API versioning demo: http://localhost:${port}`);
});
npm init -y
npm install -D tsx typescript @types/node
PORT=18080 npx tsx api-versioning-demo.ts &
SERVER_PID=$!
sleep 1
curl -i http://localhost:18080/api/v1/orders/o_100
curl -i -H "API-Version: 2" http://localhost:18080/api/orders/o_100
curl -i -H "Accept: application/vnd.acme.orders.v2+json" http://localhost:18080/api/orders/o_100
kill "$SERVER_PID"
Wichtig ist die Transformationsschicht. v1 sollte nicht einfach die v2-Antwort wiederverwenden und hoffen, dass alte Clients damit umgehen. Jede Version mappt vom internen Modell auf die öffentliche Form, die im Vertrag steht.
Deprecation-Header Und Versionspolitik Veröffentlichen
Viele ältere Beispiele zeigenDeprecation: true. Nach RFC 9745 istDeprecation jedoch ein strukturierter Date-Wert, zum Beispiel@1774915200. Sunset nach RFC 8594 ist ein HTTP-Datum, das angibt, wann eine Ressource nicht mehr antworten kann. Diese Header sind Laufzeitsignale, aber kein vollständiger Migrationsplan.
Lege die Politik im Repository ab:
currentApiVersion: v2
minimumSupportWindowMonths: 12
breakingChangeRequires:
- new-major-version
- migration-guide
- consumer-test
- owner-approval
deprecatedVersions:
- version: v1
deprecatedAt: "2026-03-31T00:00:00Z"
sunsetAt: "2026-12-31T23:59:59Z"
replacement: "/api/v2/orders/{orderId}"
migrationGuide: "https://docs.example.com/api/deprecations/v1-to-v2"
Der CHANGELOG sollte Added, Changed, Deprecated und Removal getrennt aufführen. Ein nützlicher Eintrag sagt, wer betroffen ist, was zu ändern ist, welcher Ersatzendpunkt gilt und wann die alte Version nicht mehr antworten kann.
Consumer Tests Vor Dem Refactoring Ergänzen
Consumer Tests beschreiben, was Clients weiterhin erwarten. Sie sind besonders hilfreich, wenn Claude Code scheinbar doppelte Transformationslogik bereinigen will. Der folgende Test prüft, dass v1customerName behält und nicht versehentlich das v2-Objektcustomer zurückgibt.
import assert from "node:assert/strict";
import test from "node:test";
const baseUrl = process.env.API_BASE_URL ?? "http://localhost:18080";
test("v1 keeps the legacy response shape", async () => {
const res = await fetch(`${baseUrl}/api/v1/orders/o_100`);
assert.equal(res.status, 200);
assert.match(res.headers.get("deprecation") ?? "", /^@\d+$/);
assert.match(res.headers.get("sunset") ?? "", /GMT$/);
const body = await res.json();
assert.equal(body.data.customerName, "Masa Tanaka");
assert.equal(body.data.customer, undefined);
});
test("v2 returns the current response shape", async () => {
const res = await fetch(`${baseUrl}/api/orders/o_100`, {
headers: { "API-Version": "2" },
});
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.data.customer.displayName, "Masa Tanaka");
assert.equal(body.data.amount.currency, "JPY");
assert.equal(body.data.customerName, undefined);
});
PORT=18080 npx tsx api-versioning-demo.ts &
SERVER_PID=$!
sleep 1
API_BASE_URL=http://localhost:18080 node --test version-contract.test.mjs
kill "$SERVER_PID"
Wenn dein Projekt OpenAPI-Linting nutzt, gehört es in denselben Prüfpfad:
npx @redocly/cli lint openapi.yaml
Mit diesen Kommandos im Prompt hat Claude Code ein klares Ziel. “Bitte rückwärtskompatibel” ist allein zu unpräzise.
Stufenweise Ausrollen Und Fallback Vorbereiten
API-Versionierungsprobleme sind oft vorhersehbar. Schema und Antwortform ändern sich im selben Deploy, also ist Rollback schwer. Ein Sunset-Datum wird gesetzt, bevor echter v1-Traffic gemessen wurde. Das SDK wird aktualisiert, direkte HTTP-Nutzer werden vergessen. Oder die Dokumentation sagt deprecated, während Metriken und Alerts keine verbleibenden Consumer zeigen.
Der Rollout sollte getrennt werden: v2 hinzufügen, v1-Deprecation-Header senden, Nutzung pro Version messen, Migrationsguide veröffentlichen, SDKs aktualisieren, Partner informieren, Sunset-Regel anwenden und erst dann v1 entfernen. Fallback bedeutet: v1 läuft weiter, wenn v2 deaktiviert wird; alte Clients ignorieren neue Felder; Datenbankmigrationen bleiben mindestens lesekompatibel.
mkdir -p tmp/version-snapshots
BASE_URL=${BASE_URL:-http://localhost:18080}
for order_id in o_100 missing; do
curl -sS -D "tmp/version-snapshots/${order_id}.v1.headers" \
"$BASE_URL/api/v1/orders/$order_id" \
> "tmp/version-snapshots/${order_id}.v1.json" || true
curl -sS -D "tmp/version-snapshots/${order_id}.v2.headers" \
-H "API-Version: 2" \
"$BASE_URL/api/orders/$order_id" \
> "tmp/version-snapshots/${order_id}.v2.json" || true
done
Hänge diese Snapshots an den Pull Request oder gib sie Claude Code für eine Kompatibilitätszusammenfassung. Sie ersetzen keine Tests, machen aber Verhalten sichtbar.
Prompts Gegen Breaking Changes
Claude Code arbeitet besser, wenn Vertrag, verbotene Änderungen und Checks im Prompt stehen.
Füge v2 zur bestehenden API hinzu. Behandle OpenAPI-Dateien als Quelle der Wahrheit. Ändere die v1-Antwortform, Statuscodes und Deprecation-Header nicht.
Liste vor dem Editieren:
- mögliche Breaking Changes
- Felder, die in v1 erhalten bleiben müssen
- Felder, die in v2 ergänzt oder geändert werden
- Consumer Tests, die du hinzufügst
Führe nach dem Editieren aus:
- npm test
- npx @redocly/cli lint openapi.yaml
- curl-Vergleiche für v1 und v2
Nenne am Ende Kompatibilitätsrisiko, Notizen für den Migrationsguide und Rollback-Schritte.
Vor dem Merge hilft ein Review-Prompt:
Prüfe diesen Diff als API-Kompatibilitätsreview.
Kontrolliere:
- v1-Pflichtfelder wurden nicht entfernt, umbenannt oder im Typ geändert
- Fehlerformate, HTTP-Status, Pagination und Sortierung änderten sich nicht unerwartet
- Deprecation, Sunset, Link und Vary passen zur Politik
- OpenAPI, Implementierung, Tests und CHANGELOG stimmen überein
- Rollback bricht keine v1-Consumer
Gib Findings mit Dateinamen und konkreten Fixes zurück.
Diese Prompts verschieben das Ziel von “Code sauberer machen” zu “öffentlichen Vertrag schützen”. Für APIs ist das entscheidend.
Fazit
Sichere API-Versionierung beginnt mit einem Vertrag. Wähle URL, Header oder Media Type nach Consumern und Infrastruktur. Dokumentiere v1 und v2 in OpenAPI, halte Transformationslogik explizit, veröffentlicheDeprecation undSunset, schreibe einen nutzbaren CHANGELOG und führe Consumer Tests vor Refactorings aus.
Wenn dein Team Claude Code in API-Entwicklung einführen möchte, kann Claude Code consultation and training helfen, Verträge, CI-Gates, Review-Prompts und Rollout-Checklisten in einen wiederholbaren Prozess zu bringen. Für einen kleineren Start nutze die kostenlose Cheatsheet und passe die Prompts aus diesem Artikel an.
Ich habe das Muster mit dem Node-Server oben geprüft: v1 und v2 können dieselbe interne Datenzeile teilen und dennoch unterschiedliche öffentliche Formen behalten. Der Consumer Test erkennt eine Feldumbenennung sofort. Besonders leicht zu übersehen waren das RFC-9745-Date-Format fürDeprecation, derVary-Header bei Header- oder Media-Type-Versionierung und die gemeinsame Prüfung von OpenAPI, Code, Tests und CHANGELOG.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude-Code-Permission-Receipt: Scope, Beweis und Rollback festhalten
Permission-Receipt für Claude Code: erlaubte Aktionen, Freigabegrenzen, Prüfbefehle, Rollback und Umsatz-CTA-Prüfung.
Sicheres Agent Harness fur Claude Code und Codex: Rechte, Prufung und Rollback
Ein praktisches Agent Harness fur Claude Code und Codex mit Policy, Plan, Verifikation und Recovery.
Claude Code Subagents: Praxisleitfaden für sichere Agent-Delegation
Claude Code Subagents praktisch nutzen: Artikel- und Codearbeit sicher aufteilen, Prompts einsetzen, Fehler vermeiden.