API-Design mit Claude Code: OpenAPI, Tests und Breaking-Change-Prüfung
Entwirf robuste REST APIs mit Claude Code: OpenAPI, Mock, Tests, Versionierung, Sicherheit und typische Fehler.
API-Design bedeutet nicht, hübsche URLs zu erfinden. Es bedeutet, einen Vertrag zu formulieren: Was darf ein Client senden, was gibt der Server zurück, und wie wird ein Fehler erklärt?
Wenn dieser Vertrag unklar bleibt, interpretieren Frontend, Mobile App, Partnerintegration, Tests und Monitoring dieselbe API unterschiedlich. Je später man den Vertrag repariert, desto teurer wird die Änderung.
Claude Code hilft dabei, wenn du es als Design-Reviewer einsetzt und nicht nur als Codegenerator. Lass Claude Code einen OpenAPI-Entwurf schreiben, Endpunkte prüfen, Mock und Tests erzeugen und Breaking Changes gegen bestehende Clients finden.
Nutze offizielle Quellen als Grundlage: OpenAPI Specification, RFC 9110 HTTP Semantics, JSON Schema docs und OWASP API Security Top 10. Für die Umsetzung passen außerdem Production API Development, API-Testautomatisierung und API-Versionierung.
Was API-Design festlegt
Eine API ist eine Oberfläche für ein anderes Programm. Eine menschliche Oberfläche erklärt sich durch Labels und Layout. Eine API erklärt sich durch Pfade, HTTP-Methoden, Statuscodes, JSON-Felder, Schemas, Beispiele und Fehlerantworten.
Für den Einstieg reichen fünf Entscheidungen.
| Entscheidung | Einfache Erklärung | Beispiel |
|---|---|---|
| Ressource | Das Substantiv der API | orders, customers, invoices |
| Operation | Was mit der Ressource geschieht | GET, POST, PATCH, DELETE |
| Schema | Form und Regeln des JSON | items enthält mindestens ein Element |
| Fehler | Wie Scheitern gemeldet wird | 400, 401, 403, 404, 422 mit Details |
| Kompatibilität | Wie Clients nicht brechen | Neues Pflichtfeld ist breaking |
REST muss am Anfang nicht akademisch sein. Praktisch zählt: URLs beschreiben Substantive, HTTP-Methoden beschreiben die Aktion. POST /orders ist klarer als POST /orders/create, und GET /orders/ord_123 ist besser testbar als GET /getOrder?id=ord_123.
Workflow mit Claude Code
Bitte Claude Code nicht, Entwurf, Implementierung, Tests und Doku in einem Schritt zu erledigen. Trenne die Arbeit in überprüfbare Einheiten.
flowchart TD
A["Business-Regeln zusammenfassen"] --> B["OpenAPI-Vertrag entwerfen"]
B --> C["HTTP, Schema und Sicherheit prüfen"]
C --> D["Mock und API-Tests erzeugen"]
D --> E["Breaking Changes in CI prüfen"]
E --> F["Implementieren, dokumentieren, veröffentlichen"]
OpenAPI ist ein maschinenlesbarer Vertrag für HTTP-APIs. JSON Schema beschreibt JSON-Formen und Einschränkungen. HTTP-Statuscodes geben Erfolg und Fehlern eine gemeinsame Bedeutung. Claude Code kann diese Bausteine verbinden, aber Spezifikationen und Tests bleiben die Wahrheit.
In einem kleinen Testprojekt sah Masa, dass eine reine Endpoint-Liste zuerst brauchbar wirkte. Später mussten Authentifizierung, Pagination, Idempotency-Key und Fehlerdetails nachgerüstet werden. Ein besserer erster Prompt fragt direkt nach Fehlerfällen und Wiederherstellung für Clients.
Kopierbares Starter-Beispiel
Dieses Beispiel braucht keine externen Pakete. Du kannst OpenAPI, Mock-Server und Breaking-Change-Prüfung in einem Ablauf testen.
mkdir api-design-lab
cd api-design-lab
mkdir docs examples
node --version
Erstelle docs/openapi.yaml. Die offizielle OpenAPI-Seite zeigt die neueste veröffentlichte Fassung; dieses Beispiel nutzt 3.1, weil viele Tools es stabil unterstützen.
openapi: 3.1.0
info:
title: Orders API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/v1/orders:
post:
summary: Create an order
operationId: createOrder
security:
- bearerAuth: []
parameters:
- name: Idempotency-Key
in: header
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"422":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/Problem"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
schemas:
CreateOrderRequest:
type: object
required: [customerId, items]
properties:
customerId:
type: string
minLength: 3
items:
type: array
minItems: 1
items:
type: object
required: [sku, quantity]
properties:
sku:
type: string
minLength: 3
quantity:
type: integer
minimum: 1
Order:
type: object
required: [id, status, customerId, total]
properties:
id:
type: string
status:
type: string
enum: [accepted, cancelled]
customerId:
type: string
total:
type: integer
Problem:
type: object
required: [type, title, status, detail]
properties:
type:
type: string
title:
type: string
status:
type: integer
detail:
type: string
errors:
type: array
items:
type: object
Erstelle examples/mock-server.mjs.
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";
function readJson(req) {
return new Promise((resolve, reject) => {
let body = "";
req.on("data", (chunk) => {
body += chunk;
if (body.length > 1_000_000) req.destroy(new Error("Body too large"));
});
req.on("end", () => {
if (!body) return resolve({});
try {
resolve(JSON.parse(body));
} catch (error) {
reject(error);
}
});
req.on("error", reject);
});
}
function send(res, status, body, headers = {}) {
res.writeHead(status, {
"content-type": "application/json; charset=utf-8",
"x-content-type-options": "nosniff",
...headers,
});
res.end(JSON.stringify(body, null, 2));
}
function problem(status, title, detail, errors = []) {
return {
type: "https://example.com/problems/request",
title,
status,
detail,
errors,
};
}
function validateOrder(input) {
const errors = [];
if (typeof input.customerId !== "string" || input.customerId.length < 3) {
errors.push({
path: "customerId",
message: "customerId must be a string with 3+ characters",
});
}
if (!Array.isArray(input.items) || input.items.length === 0) {
errors.push({ path: "items", message: "items must contain at least one item" });
}
for (const [index, item] of (input.items ?? []).entries()) {
if (typeof item.sku !== "string" || item.sku.length < 3) {
errors.push({
path: `items.${index}.sku`,
message: "sku must be a string with 3+ characters",
});
}
if (!Number.isInteger(item.quantity) || item.quantity < 1) {
errors.push({
path: `items.${index}.quantity`,
message: "quantity must be a positive integer",
});
}
}
return errors;
}
const server = createServer(async (req, res) => {
const url = new URL(req.url ?? "/", "http://localhost");
if (req.method === "GET" && url.pathname === "/health") {
return send(res, 200, { ok: true });
}
const customerMatch = url.pathname.match(/^\/v1\/customers\/([a-z0-9-]+)$/);
if (req.method === "GET" && customerMatch) {
return send(res, 200, {
id: customerMatch[1],
name: "Aki Tanaka",
plan: "pro",
});
}
if (req.method === "POST" && url.pathname === "/v1/orders") {
const idempotencyKey = req.headers["idempotency-key"];
if (!idempotencyKey) {
return send(
res,
400,
problem(400, "Missing Idempotency-Key", "POST /v1/orders requires the header.")
);
}
try {
const body = await readJson(req);
const errors = validateOrder(body);
if (errors.length > 0) {
return send(res, 422, problem(422, "Invalid request body", "Fix errors.", errors));
}
return send(
res,
201,
{
id: `ord_${randomUUID()}`,
status: "accepted",
customerId: body.customerId,
total: 4200,
},
{ location: "/v1/orders/example" }
);
} catch {
return send(res, 400, problem(400, "Malformed JSON", "Request body must be JSON."));
}
}
return send(res, 404, problem(404, "Not found", `${req.method} ${url.pathname} is undefined.`));
});
server.listen(3000, () => {
console.log("Mock API running at http://localhost:3000");
});
Starte den Server und rufe ihn aus einem zweiten Terminal auf.
node examples/mock-server.mjs
curl -i http://localhost:3000/health
curl -i -X POST http://localhost:3000/v1/orders \
-H "content-type: application/json" \
-H "idempotency-key: demo-001" \
-d '{"customerId":"cus_123","items":[{"sku":"book-1","quantity":2}]}'
curl -i -X POST http://localhost:3000/v1/orders \
-H "content-type: application/json" \
-H "idempotency-key: demo-002" \
-d '{"customerId":"x","items":[]}'
Erstelle examples/contract-check.mjs. Das Skript schlägt absichtlich fehl.
import assert from "node:assert/strict";
const previous = {
paths: {
"/v1/orders": {
post: {
request: {
required: ["customerId", "items"],
properties: ["customerId", "items", "couponCode"],
},
response: {
required: ["id", "status", "customerId", "total"],
properties: ["id", "status", "customerId", "total"],
},
},
},
},
};
const next = structuredClone(previous);
next.paths["/v1/orders"].post.request.required.push("shippingAddress");
next.paths["/v1/orders"].post.response.properties =
next.paths["/v1/orders"].post.response.properties.filter((name) => name !== "total");
function diffContract(oldSpec, newSpec) {
const breaking = [];
for (const [path, methods] of Object.entries(oldSpec.paths)) {
for (const [method, oldOperation] of Object.entries(methods)) {
const newOperation = newSpec.paths[path]?.[method];
if (!newOperation) {
breaking.push(`${method.toUpperCase()} ${path} was removed`);
continue;
}
const oldRequired = new Set(oldOperation.request.required);
for (const field of newOperation.request.required) {
if (!oldRequired.has(field)) {
breaking.push(`${method.toUpperCase()} ${path} now requires "${field}"`);
}
}
const newResponseFields = new Set(newOperation.response.properties);
for (const field of oldOperation.response.properties) {
if (!newResponseFields.has(field)) {
breaking.push(`${method.toUpperCase()} ${path} removed response "${field}"`);
}
}
}
}
return breaking;
}
const breaking = diffContract(previous, next);
console.log(breaking.join("\n") || "No breaking changes found");
assert.equal(breaking.length, 0, "Breaking API changes detected");
node examples/contract-check.mjs
Hier ist das Scheitern korrekt. Das Skript erkennt ein neues Pflichtfeld in der Anfrage und ein entferntes Feld in der Antwort.
Prompts für Claude Code
Trenne Entwurf, Review, Beispielgenerierung und Kompatibilitätsprüfung.
claude -p "
Erstelle einen OpenAPI-Entwurf in docs/openapi.yaml für eine E-Commerce-Orders-API.
Ressourcen: customers, orders, invoices.
Füge summary, operationId, requestBody, responses, examples und bearerAuth hinzu.
Nutze OpenAPI 3.1 und Constraints im Stil von JSON Schema.
"
claude -p "
Lies docs/openapi.yaml als API-Design-Reviewer.
Gib zuerst Findings nach Schweregrad aus und ändere noch keine Dateien.
Prüfe RFC 9110 method/status Semantik, unklare Schemas, Pagination,
Idempotency, Authentication und typische OWASP API Security Risiken.
"
claude -p "
Erzeuge aus docs/openapi.yaml einen Node.js Mock-Server und API-Testbeispiele.
Decke Erfolg, Auth-Fehler, Validierungsfehler und fehlende Ressource ab.
Halte lange Zeilen unter 150 Zeichen und ergänze README-Kommandos.
"
claude -p "
Vergleiche das aktuelle docs/openapi.yaml mit der Version in HEAD.
Liste zuerst Breaking Changes, bevor du Änderungen vorschlägst.
Prüfe entfernte Paths, neue Pflichtfelder, entfernte Antwortfelder,
geänderte Statuscodes und geänderte Auth Scopes.
"
Reale Use Cases
Der erste Fall ist eine SaaS-Orders-API. Admin-Oberfläche, Billing Jobs, E-Mails und Buchhaltungsexporte lesen dasselbe Order-Modell. Wenn total, Währung, Steuer und Stornozustand unklar sind, geraten alle Integrationen auseinander.
Der zweite Fall ist eine mobile Profil-API. Alte App-Versionen bleiben oft monatelang aktiv. Ein entferntes Antwortfeld oder eine geänderte enum-Bedeutung kann Clients brechen, die nicht sofort aktualisiert werden.
Der dritte Fall ist eine B2B-Partner-API. Externe Entwickler kennen deine internen Regeln nicht. Sie brauchen stabile Fehlercodes, Rate Limits, Retry-Regeln, Sandbox und Beispiele.
Der vierte Fall ist eine interne Admin-API. Intern heißt nicht automatisch sicher. Objektbezogene Autorisierung bleibt wichtig: Wer eine ID kennt, darf dadurch nicht Bestellungen eines anderen Tenants lesen.
Fehler und Fallstricke
Ein häufiger Fehler sind Verben in Pfaden: /cancelOrder, /getUserOrders, /updateOrderStatus. Mit der Zeit entstehen inkonsistente Namen. Modelliere zuerst Ressourcen und nutze bei Bedarf Subressourcen.
Ein zweiter Fehler ist HTTP 200 für alle Geschäftsfehler. Das erschwert Monitoring, SDKs, Retries und Client-Logik. Nutze 400 für fehlerhafte Eingabe, 401 ohne Authentifizierung, 403 verboten, 404 nicht gefunden und 422 für semantisch ungültige Eingaben.
Retry-Sicherheit bei POST zu vergessen ist teuer. Bestellung und Zahlung können nach Timeout erneut gesendet werden. Ein Idempotency-Key gehört früh ins Design.
Schema-Unschärfe erzeugt langsame Bugs. Entscheide, ob null löschen, unbekannt oder nicht gesendet bedeutet. Definiere Pflichtfelder, Mindestlängen, Paging-Grenzen, Zeitzonen und zusätzliche Properties.
Versionierung, Fehler, Schema und Sicherheit
Versionierung ist mehr als /v1. Das Team braucht klare Regeln, was breaking ist. Ein optionales Feld ist oft sicher; ein neues Pflichtfeld, ein entferntes Antwortfeld, geänderte Statussemantik oder strengere Berechtigungen können Clients brechen.
Fehlerantworten sollten die nächste Aktion erklären. Invalid request reicht nicht. Halte type, title, status, detail und bei Bedarf feldbezogene errors stabil. Stack traces, SQL und interne IDs gehören nicht in Produktionsantworten.
Schemas sollten Einschränkungen beschreiben, nicht nur Beispiele. ID-Formate, Mindestlängen, Array-Grenzen, Pagination und Datumsregeln reduzieren Annahmen im Client.
Sicherheit trennt Authentifizierung von Autorisierung. Ein Bearer Token hilft nicht, wenn GET /orders/{id} Bestellungen anderer Tenants liefert. Vermeide API Keys in Query Strings, reduziere sensible Daten, nutze Rate Limits und Audit Logs.
Monetarisierung und Beratung
Wer nach API-Design sucht, hat oft ein echtes Projekt: öffentliche Integration, Mobile Backend, Team-Review-Regeln oder Partner-API. Der Artikel sollte deshalb zeigen, dass der Ablauf umsetzbar ist.
ClaudeCodeLab unterstützt API-Design-Reviews mit Claude Code, OpenAPI-Aufräumen, Testautomatisierung und Breaking-Change-Checks. Teams starten mit Training und Beratung; einzelne Entwickler können die kostenlosen Ressourcen nutzen.
Verifiziertes Ergebnis
Ich habe den Code mit Node v24.14.1 geprüft. GET /health lieferte 200, ein gültiger POST /v1/orders lieferte 201 und leere items lieferten 422. contract-check.mjs schlägt absichtlich fehl und zeigt das neue Pflichtfeld sowie das entfernte Antwortfeld. Damit gibt es einen konkreten Weg von Claude-Code-Prompts zu OpenAPI, Mock, Fehlerdesign und CI-Prüfung.
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 Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.