· aktualisiert am

REST API Design: Best Practices für URLs, Fehler, Pagination und Versionierung

REST API Design nach RFC 9110 und 9457: Methoden, Statuscodes, Fehlerformat, Pagination, Idempotenz und Versionierung, mit Beispielen von GitHub.

APIArchitekturWeb-Dev

Thema: Web-Entwicklung

Inhalt (12 Abschnitte)
REST API Design: Best Practices für URLs, Fehler, Pagination und Versionierung

Gutes REST API Design heißt: Ressourcen als Substantive in der URL, HTTP-Methoden und Statuscodes so verwenden, wie RFC 9110 sie definiert, Fehler einheitlich nach RFC 9457 melden, Listen von Anfang an seitenweise ausliefern und eine Versionierungsregel festlegen, bevor der erste Client existiert. Genau diese Entscheidungen lassen sich später kaum noch ändern.

Stand: 13. Oktober 2026. Die Angaben stammen aus den RFCs der IETF, der Dissertation von Roy Fielding, den API-Richtlinien von Google, Microsoft und Zalando, der Dokumentation von GitHub, Stripe und PostgreSQL sowie der OpenAPI-Spezifikation, abgerufen an diesem Tag. Die Quellen stehen am Ende.

Was REST bedeutet

REST steht für Representational State Transfer. Roy Fielding hat den Architekturstil in seiner Dissertation aus dem Jahr 2000 beschrieben, als Satz von Einschränkungen: Client und Server getrennt, zustandslose Kommunikation, zwischenspeicherbare Antworten, eine einheitliche Schnittstelle, ein geschichtetes System und optional Code auf Abruf. Die einheitliche Schnittstelle definiert er durch vier Bedingungen: Ressourcen sind identifizierbar, sie werden über Repräsentationen verändert, Nachrichten beschreiben sich selbst, und Hypermedia steuert den Zustand der Anwendung.

Was im Alltag „REST API“ heißt, ist meist eine HTTP-Schnittstelle mit JSON, die einen Teil dieser Bedingungen erfüllt. Für das Design zählt nach Einschätzung weniger die reine Lehre als zwei Fragen: Hält sich die API an die Bedeutung, die HTTP selbst vorgibt? Und bleibt sie für bestehende Clients stabil, wenn sie wächst?

Ressourcen und URLs benennen

URLs bezeichnen Ressourcen, die Methode sagt, was mit ihnen geschieht. Eine verbreitete Konvention, hier als Beispiel:

AufrufBedeutung
GET /kundenListe der Kunden
POST /kundenneuen Kunden anlegen
GET /kunden/42einen Kunden lesen
PATCH /kunden/42einen Kunden teilweise ändern
DELETE /kunden/42einen Kunden löschen
GET /kunden/42/rechnungenRechnungen dieses Kunden

Verben wie /kundeAnlegen oder /getKunde gehören nach dieser Konvention nicht in die URL. Für Schreibweisen gibt es keine Norm, wohl aber Richtlinien großer Anbieter. Die Zalando RESTful API Guidelines etwa schreiben kebab-case für Pfadsegmente und snake_case für Query-Parameter und Feldnamen vor. Wichtiger als die gewählte Schreibweise ist nach Einschätzung, dass sie in der ganzen API gleich bleibt.

HTTP-Methoden richtig einsetzen

RFC 9110 legt fest, welche Methoden „sicher“ und welche „idempotent“ sind. Sicher heißt: Der Client verlangt keine Zustandsänderung auf dem Server. Idempotent heißt: Mehrere gleiche Anfragen haben dieselbe beabsichtigte Wirkung wie eine einzige. Das ist mehr als Theorie: Idempotente Anfragen darf ein Client laut RFC nach einem Verbindungsfehler automatisch wiederholen.

Methodetypischer Zwecksicheridempotent
GETlesenjaja
HEADwie GET, nur Kopfzeilenjaja
POSTanlegen, Aktion auslösenneinnein
PUTRessource vollständig ersetzen oder anlegenneinja
PATCHRessource teilweise ändern (RFC 5789)neinnein
DELETElöschenneinja

Daraus folgen zwei Regeln: Ein GET darf nichts verändern, denn Crawler und Caches verlassen sich laut RFC darauf, dass sichere Methoden keinen Schaden anrichten. Und ein POST, der etwas Wichtiges auslöst, braucht einen eigenen Schutz gegen doppelte Ausführung (siehe Idempotency-Key weiter unten).

Statuscodes, die eine API wirklich braucht

Die meisten APIs kommen mit wenigen Codes aus. Die Bedeutungen stammen aus RFC 9110, der Code 429 aus RFC 6585:

CodeBedeutung laut RFCtypischer Einsatz
200 OKAnfrage erfolgreichLesen, Ändern mit Antwortinhalt
201 Createdneue Ressource angelegt, Adresse im Location-Headererfolgreicher POST
204 No Contenterfolgreich, kein InhaltDELETE
400 Bad RequestClient-Fehler, etwa fehlerhafte Syntaxkaputtes JSON
401 Unauthorizedgültige Anmeldedaten fehlen; Server muss WWW-Authenticate sendenfehlendes oder abgelaufenes Token
403 ForbiddenAnfrage verstanden, Ausführung verweigertangemeldet, aber ohne Recht
404 Not FoundRessource nicht gefundenfalsche ID
409 ConflictKonflikt mit dem aktuellen ZustandE-Mail-Adresse schon vergeben
412 Precondition FailedVorbedingung im Header nicht erfülltIf-Match mit veraltetem ETag
422 Unprocessable ContentSyntax korrekt, Inhalt nicht verarbeitbarValidierungsfehler
429 Too Many Requestszu viele Anfragen in einer Zeitspanne; Retry-After möglichRate Limit
500 Internal Server Errorunerwarteter Fehler auf dem ServerAusnahme im Code
503 Service Unavailablevorübergehend nicht verfügbarWartung, Überlast

Als Merkhilfe für 401 und 403: 401 heißt „Anmeldung fehlt oder ist ungültig“, 403 heißt „angemeldet, aber nicht berechtigt“. Wie du Anmeldung und Rechte sauber umsetzt, steht in Web Security für Entwickler.

Fehler einheitlich melden: Problem Details nach RFC 9457

Ohne festes Fehlerformat baut nach Einschätzung jeder Endpoint sein eigenes, und jeder Client braucht für jeden Endpoint eigenen Fehlercode. RFC 9457 „Problem Details for HTTP APIs“ (Juli 2023, ersetzt RFC 7807) definiert dafür ein JSON-Format mit dem Medientyp application/problem+json. Ein Beispiel nach dem Muster der RFC:

HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://api.example.com/probleme/validierung",
  "title": "Die Anfrage ist ungültig.",
  "status": 422,
  "errors": [
    { "detail": "muss eine positive ganze Zahl sein", "pointer": "#/menge" },
    { "detail": "muss 'rot', 'grün' oder 'blau' sein", "pointer": "#/farbe" }
  ]
}
FeldBedeutung laut RFC 9457
typeURI, die die Art des Problems bezeichnet; fehlt es, gilt about:blank
titlekurze, für Menschen lesbare Zusammenfassung der Problemart
statusder HTTP-Statuscode, als Hinweis für den Client
detailErklärung zu genau diesem Vorfall; soll beim Beheben helfen, nicht beim Debuggen
instanceURI-Referenz auf genau diesen Vorfall

Eigene Felder wie errors sind als Erweiterungen erlaubt. Clients sollen laut RFC detail nicht maschinell auswerten; dafür sind Erweiterungsfelder da. Aus den Sicherheitshinweisen der RFC: Keine Implementierungsdetails wie Stack-Traces über die Schnittstelle ausgeben. Zalando schreibt Problem JSON in seinen Richtlinien verpflichtend vor.

Pagination von Anfang an

Googles API-Richtlinie AIP-158 ist hier eindeutig: Methoden, die Sammlungen liefern, müssen von Beginn an seitenweise ausliefern, weil das nachträgliche Einführen eine inkompatible Änderung ist. Ein Client, der heute alle Einträge in einer Antwort erwartet, bricht, sobald plötzlich nur noch die ersten 50 kommen.

Es gibt zwei verbreitete Verfahren:

VerfahrenBeispielVorteilNachteil
Offset?limit=50&offset=100einfach, Sprung auf Seite n möglichneue Einträge verschieben die Seiten, große Offsets werden langsam
Cursor?limit=50&starting_after=obj_123stabil bei neuen Einträgen, gleichmäßig schnellkein Sprung auf Seite n

Stripe nutzt Cursor: Listen-Endpoints nehmen limit (1 bis 100, Standard 10) sowie starting_after oder ending_before mit einer Objekt-ID. Google gibt in AIP-158 ein next_page_token zurück, das am Ende der Liste leer ist. GitHub liefert die Adressen der nächsten, vorigen, ersten und letzten Seite im link-Header nach RFC 8288.

Ein Cursor sollte für den Client undurchsichtig sein, damit du die Umsetzung später ändern kannst. Ein Beispiel in Node.js, das den Cursor aus Zeitstempel und ID des letzten Eintrags bildet:

// cursor.mjs
// Der Cursor ist für den Client undurchsichtig: Base64url-kodiertes JSON
export function cursorErzeugen({ erstellt, id }) {
  return Buffer.from(JSON.stringify({ erstellt, id })).toString('base64url');
}

export function cursorLesen(cursor) {
  const { erstellt, id } = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
  if (typeof erstellt !== 'string' || !Number.isInteger(id)) {
    throw new Error('ungültiger Cursor');
  }
  return { erstellt, id };
}

Die passende Abfrage nutzt einen Zeilenvergleich, den PostgreSQL von links nach rechts auswertet. Die ID als zweites Kriterium sorgt dafür, dass Einträge mit gleichem Zeitstempel weder doppelt noch gar nicht erscheinen:

SELECT id, erstellt FROM bestellungen
WHERE (erstellt, id) < ($1, $2)
ORDER BY erstellt DESC, id DESC
LIMIT $3;

Modul und Abfrage wurden mit Node.js 22.22 gegen eine In-Memory-Datenbank in SQLite geprüft, die denselben Zeilenvergleich kennt: Sieben Einträge, davon vier mit gleichem Zeitstempel, kamen in Seiten zu je drei vollständig und ohne Doppelte. Speichert PostgreSQL Mikrosekunden, liefern Node-Treiber wie node-postgres und PGlite timestamptz als Date, das nur Millisekunden kennt; die Mikrosekunden fehlen dann im Cursor, und Einträge mit fast gleichem Zeitstempel können übersprungen werden. Gegen PostgreSQL in PGlite 0.5 fehlte so bei zwei Zeitstempeln mit Mikrosekunden ein Eintrag. Gib den Zeitstempel für den Cursor deshalb zusätzlich als Text aus (erstellt::text AS cursor_zeit) oder lege die Spalte als timestamptz(3) an; mit beiden Varianten kamen alle sieben Einträge. Für große Tabellen braucht sie einen Index auf (erstellt, id); wie weit PostgreSQL für solche Aufgaben trägt, steht in Warum Postgres fast immer reicht.

Doppelte Zahlungen verhindern: der Idempotency-Key

POST ist nicht idempotent. Bricht die Verbindung nach dem Absenden ab, weiß der Client nicht, ob die Bestellung angelegt wurde. Wiederholt er die Anfrage, entsteht womöglich eine zweite. Stripe löst das mit dem Header Idempotency-Key: Der Client erzeugt einen eindeutigen Schlüssel, Stripe empfiehlt UUIDs der Version 4, und schickt ihn bei jeder Wiederholung mit. In der API v1 speichert Stripe Statuscode und Antwort des ersten Aufrufs und gibt sie bei jeder Wiederholung mit demselben Schlüssel zurück, auch einen Fehler 500.

POST /v1/bestellungen HTTP/1.1
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-3b4d-4c8e-9a71-2f5e8d0b4c13

Stripe nennt dazu Details, die beim Nachbauen helfen: Schlüssel bis 255 Zeichen, keine sensiblen Daten im Schlüssel, in der API v1 Löschen frühestens nach 24 Stunden, und ein Fehler, wenn derselbe Schlüssel mit anderen Parametern kommt. Ein Standard ist der Header bisher nicht: Der IETF-Entwurf „The Idempotency-Key HTTP Header Field“ ist nach Revision 07 abgelaufen.

Versionierung und Abkündigung

Es gibt keine einheitliche Regel, aber jede große Richtlinie legt sich fest:

RichtlinieVerfahren
Google (AIP-185)Hauptversion als erster Teil des Pfads, etwa /v1/; keine Neben- oder Patchversionen
Microsoft AzurePflicht-Query-Parameter api-version mit Datum YYYY-MM-DD; fehlt er, Fehler 400
GitHubHeader X-GitHub-Api-Version mit Datum; ohne Header gilt 2022-11-28; alte Versionen mindestens 24 Monate nach Erscheinen der neuen unterstützt
ZalandoVersionierung möglichst vermeiden; wenn nötig über den Medientyp, nie in der URL

GitHub listet auch, was als inkompatible Änderung gilt: unter anderem einen Endpoint entfernen, einen Parameter oder ein Antwortfeld entfernen oder umbenennen, einen neuen Pflichtparameter einführen, den Typ eines Feldes ändern und Enum-Werte entfernen. Als kompatibel gelten neue Endpoints, optionale Parameter und neue Antwortfelder. Clients sollten unbekannte Felder deshalb nach Einschätzung ignorieren, statt daran zu scheitern.

Für das Abkündigen gibt es zwei Header: Deprecation nach RFC 9745 (März 2025) kündigt an, dass eine Ressource veraltet ist oder sein wird, Sunset nach RFC 8594 nennt den Zeitpunkt, ab dem sie voraussichtlich nicht mehr antwortet. GitHub antwortet nach Ablauf der Unterstützung mit 410 Gone.

Dokumentation mit OpenAPI

Die OpenAPI Specification beschreibt HTTP-APIs in einem standardisierten, sprachunabhängigen Format, das Menschen und Programme lesen können; die aktuelle Fassung ist 3.2.1 vom 10. September 2026. Laut Spezifikation nutzen Werkzeuge eine solche Beschreibung, um Dokumentation anzuzeigen, Server und Clients in verschiedenen Sprachen zu erzeugen und zu testen. Nach Einschätzung lohnt es sich, die Datei vor dem Code zu schreiben: Dann entstehen Namen, Fehlerformat und Pagination einmal für die ganze API statt je Endpoint.

Checkliste vor dem ersten Client

EntscheidungFestlegen vor dem ersten Client
Schreibweise von Pfaden und Feldernja, später nur mit neuer Version änderbar
Fehlerformat (RFC 9457)ja
Pagination für jede Listeja, nachträglich inkompatibel
Versionierungsverfahrenja
Idempotency-Key für kritische POSTsja, wenn Geld oder Bestellungen betroffen sind
Rate Limits mit 429 und Retry-Aftersinnvoll, sobald die API öffentlich ist

Ob du Anmeldung selbst baust oder einen Dienst nutzt, ist eine eigene Entscheidung; fertige Backends mit eingebauter Anmeldung vergleicht der Überblick Supabase-Alternativen.

Quellen und Stand

Alle Angaben wurden am 13. Oktober 2026 geprüft an:

Häufige Fragen

Was sind REST API Best Practices?

Ressourcen als Substantive in der URL, Methoden und Statuscodes nach RFC 9110, ein einheitliches Fehlerformat nach RFC 9457, Pagination für jede Liste von Anfang an, Schutz vor doppelten POSTs per Idempotency-Key und eine feste Versionierungsregel. Dazu eine Beschreibung in OpenAPI, damit alle Endpoints denselben Regeln folgen.

Wie benennt man Endpoints in einer REST API?

Mit Substantiven im Plural für Sammlungen und der ID für einzelne Einträge, etwa /kunden und /kunden/42; die Aktion steckt in der HTTP-Methode. Eine Norm für die Schreibweise gibt es nicht. Zalando etwa verlangt kebab-case in Pfaden und snake_case in Feldnamen. Entscheidend ist, eine Schreibweise durchzuhalten.

Wie sollte eine REST API Fehler zurückgeben?

Mit einem passenden Statuscode und einem einheitlichen Inhalt im Format application/problem+json nach RFC 9457: type, title, status, detail und instance, ergänzt um eigene Felder wie eine Liste von Validierungsfehlern. Stack-Traces und andere Interna gehören nicht in die Antwort.

Wie versioniert man eine REST API?

Üblich sind drei Wege: Version im Pfad wie /v1/ (Google), als Query-Parameter mit Datum wie api-version=2026-10-13 (Microsoft) oder im Header (GitHub). Wichtiger als der Weg ist, festzulegen, was als inkompatible Änderung gilt, und alte Versionen mit Deprecation- und Sunset-Header rechtzeitig abzukündigen.

Wie funktioniert Pagination in einer REST API?

Der Client fragt eine begrenzte Zahl von Einträgen ab und bekommt einen Verweis auf die nächste Seite, etwa einen Cursor, ein next_page_token oder einen link-Header. Cursor-Pagination bleibt stabil, wenn neue Einträge hinzukommen. Google verlangt Pagination von Beginn an, weil sie sich nicht kompatibel nachrüsten lässt.

Was ist der Unterschied zwischen PUT und PATCH?

PUT ersetzt eine Ressource vollständig und ist laut RFC 9110 idempotent: Zwei gleiche PUTs haben dieselbe Wirkung wie einer. PATCH ändert nur Teile und ist laut RFC 5789 weder sicher noch idempotent. Für Teiländerungen ist PATCH deshalb die passende Methode, bei Wiederholungen aber vorsichtig zu behandeln.

Newsletter

Eine Mail pro Woche. Alles Wichtige aus KI und Software.

Jeden Montag früh: die Videos der Woche und die wichtigsten Nachrichten aus KI und Software, kurz erklärt und mit Quelle.

  • Montags früh
  • Kostenlos
  • Abmelden mit einem Klick

Du bekommst zuerst eine Mail zum Bestätigen. Abmelden mit einem Klick. Datenschutz