Die Fehlerantworten Ihrer API sind Teil ihres Vertrags. Clients parsen sie, Retry-Logiken verzweigen sich anhand ihrer, und Support-Ingenieure durchsuchen sie um 2 Uhr morgens. Doch die meisten Teams entwerfen den Erfolgsfall detailliert und lassen Fehler einfach so entstehen, wie es das Framework standardmäßig vorgibt. So kommt es, dass Sie in einer einzigen API drei verschiedene Fehlerstrukturen haben, eine 200er-Antwort, die "success": false umschließt, und einen Stack-Trace, der Ihr Datenbankschema ins öffentliche Internet preisgibt.
Dieser Leitfaden behandelt Best Practices für die API-Fehlerbehandlung bei REST-Diensten von A bis Z: die Wahl des richtigen Statuscodes, die Standardisierung auf einen Fehlerkörper mit RFC 9457 Problem Details, die Trennung von maschinenlesbaren Codes und menschlichen Nachrichten, die Kennzeichnung von Fehlern als wiederholbar und das Fernhalten von Geheimnissen aus Antworten. Er baut auf unserer Analyse von welche HTTP-Statuscodes REST-APIs verwenden sollten auf und ergänzt die vertraglichen Entscheidungen, die dieser Leitfaden offenlässt. Sie erfahren auch, wie Sie jeden Fehlerpfad in Apidog testen, denn ein Fehlervertrag, den Sie nie testen, ist kein Vertrag, den Sie haben.
Beginnen Sie mit dem Statuscode, nicht mit dem Body
HTTP bietet Ihnen bereits kostenlos eine erste Ebene der Fehlersemantik. RFC 9110 definiert die Statuscode-Familien: 4xx bedeutet, dass der Client etwas falsch gemacht hat und die Wiederholung derselben Anfrage erneut fehlschlägt; 5xx bedeutet, dass der Server einen Fehler hatte und die Anfrage des Clients in Ordnung gewesen sein könnte. Machen Sie diese Unterscheidung richtig, bevor Sie eine einzige Zeile Fehlerbody schreiben, denn generische Clients, Proxys, Caches und Wiederholungsbibliotheken verzweigen alle anhand dessen, ohne jemals Ihr JSON zu lesen.
Die häufigsten Fehler konzentrieren sich auf eine Handvoll ähnlich aussehender Paare. Halten Sie MDNs Referenz der HTTP-Statuscodes beim Design offen und verwenden Sie diese Entscheidungstabelle für die Codes, die Teams Schwierigkeiten bereiten.
| Situation | Verwenden Sie | Nicht | Warum |
|---|---|---|---|
| Fehlformierte Anfrage: defektes JSON, falscher Content-Type, fehlendes Pflichtfeld | 400 Bad Request | 422 | Der Server kann die Anfrage überhaupt nicht parsen oder verstehen |
| Wohlgeformte Anfrage, die semantische Regeln verletzt: Betrag ist negativ, Währung nicht unterstützt | 422 Unprocessable Content | 400 | Syntax ist in Ordnung; die Werte sind es nicht |
| Keine Anmeldeinformationen oder abgelaufenes/ungültiges Token | 401 Unauthorized | 403 | Der Client hat seine Identität nicht nachgewiesen. Senden Sie WWW-Authenticate |
| Gültige Anmeldeinformationen, unzureichende Berechtigungen | 403 Forbidden | 401 | Identität ist bekannt; Zugriff verweigert. Eine erneute Authentifizierung hilft nicht |
| Ressource existierte nie, oder Sie möchten ihre Existenz nicht bestätigen | 404 Not Found | 410 | Sicherer Standard; verbirgt auch Ressourcen vor unbefugtem Sondieren |
| Ressource existierte und wurde absichtlich, dauerhaft entfernt | 410 Gone | 404 | Fordert Clients und Crawler auf, ihre Referenzen zu löschen |
| Statuskonflikt: doppelter Schlüssel, veraltete Version, Bearbeitungskonflikt | 409 Conflict | 400 | Die Anfrage ist gültig, kollidiert aber mit dem aktuellen Ressourcenzustand |
| Client hat ein Ratenlimit überschritten | 429 Too Many Requests | 503 | Fügen Sie immer Retry-After hinzu, damit Clients korrekt zurückweichen |
| Unbehandelte Ausnahme in Ihrem Code | 500 Internal Server Error | 502 | Ihr Server ist abgestürzt |
| Upstream-Dienst hat Müll an Ihr Gateway zurückgegeben | 502 Bad Gateway | 500 | Der Fehler liegt stromabwärts des Edge, nicht darin |
| Server ist überlastet oder in Wartung | 503 Service Unavailable | 500 | Per Definition temporär; fügen Sie Retry-After hinzu, wenn Sie können |
| Upstream-Dienst hat ein Timeout | 504 Gateway Timeout | 500 | Unterscheidet „langsame Abhängigkeit“ von „defektem Code“ |
Zwei davon verdienen besondere Betonung. Erstens, 401 vs. 403 ist eine Sicherheitsgrenze, keine Stilwahl: Das Zurücksenden von 403 an einen nicht authentifizierten Anrufer verrät die Existenz der Ressource. Zweitens, 429 ohne Retry-After trainiert Clients dazu, Sie in engen Schleifen zu bombardieren. Wenn Sie Ratenbegrenzungen verwenden, und das sollten Sie, koppeln Sie den Status mit einem konkreten Rückzugssignal; unser Leitfaden zu API-Ratenbegrenzungen behandelt die Header-Mathematik und die dahinter stehenden Algorithmen.
Eine Fehlerbody-Form: RFC 9457 Problem Details
Sobald der Statuscode stimmt, sollte jeder Fehler, den Ihre API zurückgibt, einen Medientyp und ein Schema gemeinsam haben. Die Standardantwort sind RFC 9457 Problem Details, die als application/problem+json bereitgestellt werden. Sie definiert fünf Kernmitglieder: type (eine URI zur Identifizierung der Fehlerkategorie), title (eine kurze, menschliche Zusammenfassung), status (der HTTP-Code, der zur Vereinfachung wiederholt wird), detail (was bei diesem Auftreten schief gelaufen ist) und instance (eine URI für diesen spezifischen Fehler). Alles andere wird in von Ihnen selbst definierten Erweiterungsmitgliedern abgelegt.
Wir werden die Spezifikation hier nicht erneut herleiten; unser RFC 9457 Erklärer geht jedes Mitglied, die Registrierungsregeln und wie es RFC 7807 ersetzt, detailliert durch. Was für Ihren Vertrag zählt, ist das Muster: Standard-Umschlag, benutzerdefinierte Erweiterungen. Hier ist ein Validierungsfehler an einem Zahlungs-Endpunkt.
POST /v1/payments HTTP/1.1
Content-Type: application/json
{ "amount": -1400, "currency": "USD", "source": "card_8xKt2" }
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-error",
"title": "Request validation failed",
"status": 422,
"detail": "One or more fields failed validation.",
"instance": "/v1/payments/requests/req_9f3c1a7b",
"code": "PAYMENT_VALIDATION_FAILED",
"errors": [
{
"field": "amount",
"code": "AMOUNT_NOT_POSITIVE",
"message": "amount must be a positive integer in minor units"
}
],
"request_id": "req_9f3c1a7b"
}
Das errors[]-Array ist ein Erweiterungselement, und es ist das, was Clients am meisten lieben: Es ermöglicht einem Frontend, jeden Fehler dem genauen Formularfeld zuzuordnen, anstatt einen vagen Banner anzuzeigen. Halten Sie Feldpfade in einem stabilen Format (JSON Pointer oder Punktpfade, wählen Sie eines), damit Client-Code sie programmatisch binden kann.
Eine Regel erspart Ihnen den größten Ärger: Geben Sie diese Form für jeden Fehler zurück, einschließlich derer, die Ihr Framework oder Gateway generiert. Ein Client, der Problem Details von Ihren Handlern, aber HTML von der 502-Seite Ihres Load Balancers erhält, muss immer noch zwei Parser schreiben.
Maschinenlesbare Codes vs. menschliche Nachrichten
Beachten Sie, dass das Beispiel sowohl code- als auch message-Felder enthält. Das ist Absicht. Sie dienen unterschiedlichen Zielgruppen und sollten niemals zu einer einzigen Zeichenfolge zusammengeführt werden.
Maschinenlesbare Codes (AMOUNT_NOT_POSITIVE, CURRENCY_UNSUPPORTED, IDEMPOTENCY_KEY_REUSED) sind Vertrag. Clients verzweigen sich anhand ihrer, daher müssen sie stabil, dokumentiert und aufzählbar sein. Lassen Sie Clients niemals Prosa parsen; in dem Moment, in dem jemand if (message.includes("positive")) schreibt, wird Ihre Textänderung zu einer Breaking Change.
Menschliche Nachrichten sind das Gegenteil: jederzeit frei verbesserbar, für einen Entwickler geschrieben, der Logs liest, und niemals tragend. Geben Sie an, was fehlgeschlagen ist und wie eine Behebung aussieht: „Betrag muss eine positive Ganzzahl in kleineren Einheiten sein“ ist besser als „ungültiger Betrag“. Wenn Sie lokalisieren, lokalisieren Sie die Nachricht und lassen Sie den Code unverändert.
Diese Aufteilung ist jetzt noch wichtiger, da API-Konsumenten auch autonome Agenten umfassen. LLM-basierte Clients erholen sich wesentlich besser von strukturierten, selbstbeschreibenden Fehlern; diesen Aspekt behandeln wir in API-Fehlerdesign für KI-Agenten.
Was niemals in eine Fehlerantwort gehört
Fehlerantworten sind ein bevorzugter Aufklärungskanal für Angreifer, da unbehandelte Fehler tendenziell sehr ausführlich sind. Ihre Fehler-Middleware sollte gewährleisten, dass nichts von Folgendem jemals einen Client erreicht:
- Stack-Traces, Klassennamen oder Dateipfade
- Rohes SQL, Abfragefragmente oder ORM-Fehler
- Interne Hostnamen, IPs, Ports oder Dienstnamen
- Bibliotheksversionen und Framework-Bannertexte
- Geheimnisse, Tokens oder Verbindungszeichenfolgen, die in Ausnahmetexten eingebettet sind
- Ob ein Benutzerkonto existiert (bei Login- und Passwort-Reset-Vorgängen, halten Sie Fehler symmetrisch)
Das Muster ist einfach: Fangen Sie alles an der Grenze ab, protokollieren Sie die vollständige Ausnahme serverseitig mit einer Anfrage-ID und geben Sie einen generischen Problem Details Body mit derselben ID zurück. Der Client erhält "detail": "An internal error occurred", "request_id": "req_51ad0", Ihre Logs erhalten die Wahrheit, und der Support kann beides miteinander verknüpfen.
Fehler als wiederholbar oder terminierend kennzeichnen
Jeder Fehler, den Sie zurückgeben, beantwortet eine Frage, die der Client stellen wird: Soll ich dies noch einmal versuchen? Backen Sie die Antwort in den Vertrag ein, anstatt jedes Client-Team raten zu lassen.
Statuscodes tragen die Standardsemantik. 429, 502, 503 und 504 sind mit exponentiellem Backoff und Jitter wiederholbar. 500 ist mehrdeutig, aber meist einen vorsichtigen Wiederholungsversuch wert. Fast alle anderen 4xx-Codes sind terminierend: Das Wiederholen einer 401, 403, 404 oder 422 mit derselben Anfrage verschwendet Kontingente und verschmutzt Logs. Timeouts verdienen besondere Aufmerksamkeit, da die Anfrage möglicherweise erfolgreich war, nachdem der Client aufgegeben hat; das ist das klassische Problem des 408 Request Timeout, und deshalb sollten mutierende Endpunkte Idempotenzschlüssel akzeptieren, damit eine wiederholte Zahlung nicht zweimal belastet werden kann.
Sie können die Wiederholbarkeit auch explizit mit einem Erweiterungselement festlegen:
{
"type": "https://api.example.com/problems/rate-limited",
"title": "Too many requests",
"status": 429,
"code": "RATE_LIMITED",
"retryable": true,
"retry_after_seconds": 30
}
Ein explizites retryable-Flag ermöglicht es Ihnen, die Standardwerte bei Bedarf zu überschreiben, z. B. einen bestimmten 500er-Untercode als terminierend zu kennzeichnen, weil ein erneuter Versuch den Zustand beschädigen würde. Dokumentieren Sie das Flag einmal, und jedes von Ihnen ausgelieferte Client-SDK erhält ein einheitliches Rückzugsverhalten.
Korrelations-IDs und Versionierung von Fehlerverträgen
Zwei kleinere Entscheidungen runden den Vertrag ab, und beide sind jetzt günstig, später aber teuer.
Geben Sie jeder Anfrage eine ID. Akzeptieren Sie einen eingehenden X-Request-Id-Header (oder generieren Sie einen), stempeln Sie ihn auf jede Protokollzeile und wiederholen Sie ihn in jedem Fehlerbody als request_id. Wenn ein Kunde einen Fehler in ein Support-Ticket einfügt, verwandelt dieses eine Feld eine Stunde Protokollsuche in eine einzige Abfrage. In verteilten Setups verbreiten Sie einen W3C traceparent daneben, damit die ID der Anfrage über Dienste hinweg folgt.
Versionieren Sie Ihren Fehlervertrag wie die API selbst. Das Hinzufügen eines neuen Erweiterungselements oder eines neuen Fehlercodes ist sicher. Das Umbenennen von errors[].field, das Ändern der Bedeutung eines Codes oder das Wechseln von einer Ad-hoc-Form zu Problem Details ist ein Breaking Change und betrifft die Codepfade, die Teams am wenigsten testen. Die type-URI bietet Ihnen einen sauberen Mechanismus: Halten Sie alte Typ-URIs dauerhaft stabil, führen Sie neue für neue Semantiken ein und geben Sie in Ihrer Dokumentation an, dass unbekannte Erweiterungselemente und unbekannte Codes ignoriert werden müssen und nicht als Fehler behandelt werden dürfen. Diese Forward-Kompatibilitätsklausel ermöglicht es Ihnen, sich ohne eine v2 zu entwickeln.
Testen Sie jeden Fehlerpfad in Apidog
Hier ist die unbequeme Wahrheit: Fehlerverträge verrotten, weil nichts sie ausführt. Der Erfolgsfall läuft in jeder Demo; der 422-Zweig läuft, wenn ein Kunde ihn trifft. Die Lösung besteht darin, Fehlerfälle zu erstklassigen Bürgern in Ihrer Testsuite zu machen, und hier verdient Apidog seinen Platz im Workflow.
Zwei Funktionen passen direkt zu diesem Problem.
Testszenarien für die Serverseite. Erstellen Sie für jeden Endpunkt ein Szenario pro Fehlerfall: fehlende Authentifizierung erwartet 401, unzureichende Rolle erwartet 403, negativer Betrag erwartet 422 mit errors[0].code gleich AMOUNT_NOT_POSITIVE, Burst-Traffic erwartet 429 mit einem Retry-After-Header. Die visuellen Assertions von Apidog überprüfen Status, Header und Body-Felder ohne Skripting, und Sie können die gesamte Payload anhand Ihres Problem Details JSON Schemas validieren, sodass jede Abweichung in der Fehlerform im CI, nicht in der Produktion, fehlschlägt. Unser Leitfaden zu API-Assertierungen zeigt die Assertionsmuster im Detail.
Mock-Server für die Client-Seite. Ihre Frontend- und SDK-Teams müssen gegen 4xx- und 5xx-Antworten entwickeln, bevor das Backend diese bei Bedarf produzieren kann. Apidog Mock-Server geben die genauen Problem Details Bodies Ihrer API-Spezifikation zurück, sodass Sie eine 503 mit Retry-After: 120, einen 409 bei doppelter Übermittlung oder eine vollständige errors[]-Validierungs-Payload simulieren und dann beobachten können, wie der Client rendert und wiederholt. Keine manuell erstellten Express-Stubs, kein Auskommentieren von Backend-Code, um einen Fehler zu erzwingen.
Entwerfen Sie den Fehlervertrag, kodieren Sie ihn als Szenarien und Mocks und integrieren Sie beides in Ihr CI. Laden Sie Apidog herunter und testen Sie es kostenlos; der Import einer bestehenden OpenAPI-Spezifikation liefert Ihnen innerhalb weniger Minuten mockbare Fehlerantworten.
FAQ
Sollte ich 400 oder 422 für Validierungsfehler verwenden?
Verwenden Sie 400, wenn die Anfrage fehlformatiert ist und der Server sie nicht verstehen kann: ungültiges JSON, falscher Content-Type, ein fehlendes Pflichtfeld. Verwenden Sie 422, wenn die Anfrage sauber geparst wird, aber die Werte Ihre Domänenregeln verletzen, wie ein negativer Zahlungsbetrag oder eine nicht unterstützte Währung. Der praktische Nutzen ist diagnostisch: Eine 422 sagt dem Client „korrigiere deine Daten“, während eine 400 sagt „korrigiere dein Anfrageformat“. Welche Aufteilung Sie auch wählen, wenden Sie sie konsistent über jeden Endpunkt an.
Was ist application/problem+json?
Es ist der Medientyp, der durch RFC 9457 für Problem Details definiert wird, dem Standard-JSON-Fehlerformat für HTTP-APIs. Eine Antwort mit diesem Inhaltstyp enthält die Mitglieder type, title, status, detail und instance, sowie alle von Ihnen definierten Erweiterungen, wie ein errors[]-Array für Validierungsfehler auf Feldebene. Die Verwendung des registrierten Medientyps ermöglicht es generischen Clients und Middleware, Ihre Fehler ohne benutzerdefinierte Konfiguration zu erkennen. Unser RFC 9457 Erklärer behandelt die vollständige Spezifikation.
Welche HTTP-Fehler sollten Clients automatisch wiederholen?
Wiederholen Sie 429, 502, 503 und 504 mit exponentiellem Backoff plus Jitter, wobei Retry-After, falls vorhanden, berücksichtigt wird. Behandeln Sie 500 als einen vorsichtigen Wiederholungsversuch wert. Wiederholen Sie keine anderen 4xx-Antworten; die Anfrage wird jedes Mal auf dieselbe Weise fehlschlagen. Bei mutierenden Endpunkten koppeln Sie Wiederholungsversuche mit Idempotenzschlüsseln, damit eine wiederholte Anfrage nicht doppelt belastet oder doppelt erstellt werden kann.
Wie teste ich API-Fehlerantworten, ohne mein Backend zu beschädigen?
Simulieren Sie sie. Richten Sie Ihren Client auf einen Apidog Mock-Server, der die exakten 4xx- und 5xx-Bodies aus Ihrer Spezifikation zurückgibt, und überprüfen Sie dann das Rendering- und Wiederholungsverhalten für jeden einzelnen. Auf der Serverseite schreiben Sie Testszenarien, die ungültige Payloads, fehlende Authentifizierung und Burst-Traffic senden, und prüfen dann Statuscodes, Header und das Fehlerbody-Schema. Beide Hälften laufen im CI, so dass der Fehlervertrag ehrlich bleibt, ohne dass jemand manuell Fehler erzwingen muss.
