Fehlercodes
Der vollständige Katalog der Fehler, die die externe API v1 zurückgibt: welcher code bei welchem
HTTP-Status kommt, was ihn auslöst und was du dagegen tun kannst. Codes, die es nur auf der internen
SPA-Oberfläche gibt (Passwortwechsel, Fahrzeugschein-Scan, Kundenportal, Signaturprozesse), stehen
hier bewusst nicht — sie können auf /api/external/v1/** nicht auftreten.
Fehlerformat
Client-behebbare Fehler (4xx) tragen Code und Meldung:
{ "code": "VALIDATION", "message": "status muss gesetzt sein." }
Serverseitige Fehler (5xx) tragen bewusst keine Meldung — nur den Code und eine pro Vorfall erzeugte Referenz:
{ "code": "INTERNAL_ERROR", "errorId": "3f2a…" }
Die Ursache (voller Stacktrace) steht ausschließlich im Server-Log unter derselben errorId; zum
Client leakt kein internes Detail. Gib die errorId bei einer Support-Meldung immer mit.
Der code ist stabil und der Schlüssel für dein Fehler-Handling — werte ihn aus, nicht den
Meldungstext. Die Meldungen sind für Menschen gedacht und dürfen sich ändern.
Übersicht
code | HTTP | Bedeutung | Retry sinnvoll? |
|---|---|---|---|
API_KEY_INVALID | 401 | Key fehlt, ist unbrauchbar oder nicht mehr gültig | nein — alarmieren |
ACCESS_DENIED | 403 | Scope fehlt, oder echter Rechtefehler der Grant-Person | nein |
NOT_FOUND | 404 | Ressource unbekannt / fremder Mandant / nicht sichtbar | nein |
VALIDATION | 400 | Ungültige Eingabe | nein — Request korrigieren |
ALREADY_MODIFIED | 400 | Optimistic-Lock-Konflikt | ja, nach erneutem Lesen |
CONFLICT | 409 | Zustand passt nicht zur Aktion (Status, fehlende Dokumente, Duplikat) | nur nach Zustandsänderung |
RATE_LIMITED | 429 | IP-Limit vor der Authentifizierung erreicht | ja, nach Retry-After |
API_RATE_LIMITED | 429 | Key-/Benutzer-/Mandanten-Limit nach der Authentifizierung erreicht | ja, nach Retry-After |
API_KEY_AUTH_UNAVAILABLE | 503 | Authentifizierung selbst gestört | ja, mit Backoff |
INTERNAL_ERROR | 500 | unerwarteter Serverfehler | ja, mit Backoff |
API_KEY_INVALID (401)
{ "code": "API_KEY_INVALID", "message": "API-Zugangsdaten sind ungültig" }
Zusätzlich kommt der Header WWW-Authenticate: Bearer realm="usp-api", error="invalid_token".
Eine Antwort für alle Ursachen — kein Existenz- oder Statusorakel:
| Ursache | Woran es liegt |
|---|---|
Kein, mehrfacher oder falsch formatierter Authorization-Header | Genau ein Header mit Präfix Bearer senden |
Key entspricht nicht dem Format usp_<env>_<publicId>_<secret> | Key vollständig und ohne Zeilenumbruch übernehmen |
Unbekannte publicId oder falsches Secret | Key aus der Verwaltung neu ausstellen lassen |
| Key abgelaufen, deaktiviert, widerrufen oder als kompromittiert markiert | Status in der Schlüsselverwaltung prüfen |
| Rotations-Übergangsfrist des Vorgängerschlüssels abgelaufen | Auf den neuen Key umstellen |
| Grant der Person ausgesetzt oder widerrufen | Neuen Grant anfordern |
| Mandanten-Policy deaktiviert | Operator einbeziehen |
| Benutzer inaktiv, technisch oder nicht mehr Mitglied des Mandanten | Benutzerkonto prüfen lassen |
Nicht automatisch wiederholen. Ein 401 ist nie transient. Prüfe mit GET /me, ob der Key
überhaupt noch lebt, und sieh in der Schlüsselverwaltung nach.
ACCESS_DENIED (403)
{ "code": "ACCESS_DENIED", "message": "Zugriff verweigert" }
| Auslöser | Abhilfe |
|---|---|
| Dem Key fehlt der Scope des Endpunkts (häufigster Fall) | Effektive Scopes mit GET /me prüfen; Key/Grant/Policy erweitern lassen |
PUT /cases/{caseId} verschiebt den Fall auf einen Standort, den die Grant-Person nicht bedienen darf | locationId unverändert lassen |
PUT /cases/{caseId} ändert die Bearbeitungsart ohne die nötigen Rechte | processingType nicht verändern |
PUT /cases/{caseId}/status verlangt Fallabwickler- oder Admin-Rechte (Abschließen, Wiedereröffnen aus CLOSED) | Aktion einer berechtigten Person überlassen |
403 gibt es nur dort, wo du die Ressource ohnehin sehen darfst. Alles andere ist 404 — siehe unten.
NOT_FOUND (404)
{ "code": "NOT_FOUND", "message": "Ressource nicht gefunden" }
Bei Fall-Endpunkten lautet die Meldung Fall nicht gefunden, bei Stammdaten entsprechend
Standort nicht gefunden, Benutzer nicht gefunden, Versicherung nicht gefunden.
Drei Ursachen sind bewusst ununterscheidbar:
- Die Ressource existiert nicht.
- Sie gehört zu einem fremden Mandanten.
- Sie liegt außerhalb der Sichtbarkeit der Person hinter dem Grant.
Damit kann eine Integration nicht durch Ausprobieren von IDs herausfinden, was es anderswo gibt.
Zwei Sonderfälle, die kein Fehler sind:
GET /cases/{caseId}/evaluation-valuesantwortet 404, wenn für den Fall noch keine Regulierungswerte erfasst sind → als „leer" behandeln.- Ein 404 ohne
code-Feld bedeutet, dass es für den Pfad gar keinen Endpunkt gibt (Tippfehler, falsche Version, deaktivierte externe API).
VALIDATION (400)
Die message benennt das Problem konkret.
| Endpunkt | Auslöser |
|---|---|
alle mit {caseId} | caseId ist keine gültige UUID |
GET /cases | page negativ; size außerhalb 1–200 |
PUT /cases/{caseId} | Body fehlt; id im Body ≠ Pfad-caseId; locationId fehlt oder leer; status weicht vom persistierten Status ab |
PUT /cases/{caseId}/status | status fehlt; status = HIDDEN; status = CANCELLED ohne reason |
PUT /cases/{caseId}/tags | tag fehlt oder ist NONE |
PUT /cases/{caseId}/evaluation-values | Body fehlt; caseId im Body ≠ Pfad |
POST /cases/{caseId}/attachments | Datei leer; Dateiname mit Pfad-Traversal |
POST /cases/{caseId}/comments | Text fehlt/leer; Text enthält eine auflösbare @-Erwähnung |
GET /locations/{locationCode} | leerer Standortcode |
POST/PUT /insurances | insuranceId, displayName oder type fehlt |
ALREADY_MODIFIED (400)
{ "code": "ALREADY_MODIFIED", "message": "Case was already modified" }
Optimistic-Lock-Konflikt bei PUT /cases/{caseId}: das mitgeschickte lastModifiedDate fehlt oder
ist älter als der Serverstand. Achtung: HTTP 400, nicht 409.
Erwarte diesen Fehler nicht nur bei echter Parallelarbeit: auch deine eigenen Aufrufe bewegen den Zeitstempel — Anhang-Upload, Anhang-Löschen, Kommentar, Fall-Tag und Regulierungswerte schreiben jeweils einen Historieneintrag.
Richtiges Verhalten: GET /cases/{caseId}, Änderung auf dem frischen Objekt erneut anwenden, noch
einmal schreiben. Nie blind wiederholen — du würdest fremde Änderungen überschreiben.
CONFLICT (409)
| Endpunkt | Auslöser |
|---|---|
PUT /cases/{caseId} | Fall ist CLOSED, CANCELLED, HIDDEN oder WAITING_FOR_CUSTOMER (nicht editierbar) |
POST /cases/{caseId}/attachments | wie oben |
PUT /cases/{caseId}/status → RELEASED | keine signierte Vollmacht/RKÜ (entfällt bei Eigenbearbeitung) |
PUT /cases/{caseId}/status → HANDED_OVER_APPRAISER | kein signierter Gutachtenauftrag bzw. keine Vollmacht |
PUT /cases/{caseId}/status → CANCELLED | Fall ist beim Schreiben der Storno-Begründung nicht editierbar |
PUT /cases/{caseId}/status (allgemein) | Wechsel nach WAITING_FOR_CUSTOMER oder aus WAITING_FOR_CUSTOMER nach RELEASED/HANDED_OVER_APPRAISER |
POST /insurances | Eintrag mit dieser insuranceId und diesem type existiert bereits |
RATE_LIMITED und API_RATE_LIMITED (429)
Beide tragen den Header Retry-After (Sekunden bis zum Ende des Minutenfensters).
RATE_LIMITED— Drossel pro IP, greift vor der Authentifizierung und damit auch für fehlgeschlagene Versuche.API_RATE_LIMITED— hierarchische Drossel pro Key, Benutzer und Mandant nach erfolgreicher Authentifizierung.
Beide tragen den Header Retry-After und das Feld retryAfterSeconds im Body — du kannst also
in beiden Fällen dieselbe Wartelogik verwenden und musst die Codes nur zum Verstehen der Ursache
unterscheiden.
Der einzige 4xx, den du automatisch wiederholen darfst. Grenzwerte und Reihenfolge: Authentifizierung und Scopes.
API_KEY_AUTH_UNAVAILABLE (503)
{ "code": "API_KEY_AUTH_UNAVAILABLE", "message": "Die API-Authentifizierung ist vorübergehend nicht verfügbar" }
Die Authentifizierung selbst ist gestört (Datenbank oder Schlüsselkonfiguration des Servers). Bewusst nicht als 401 getarnt: dein Key ist wahrscheinlich in Ordnung. Backoff und erneut versuchen; hält es an, ist es ein Betriebsvorfall der Plattform.
INTERNAL_ERROR (500)
{ "code": "INTERNAL_ERROR", "errorId": "3f2a…" }
Catch-all für unerwartete Serverfehler (Dateisystem, Datenbank, Identity-Provider …). Der Body
enthält keine message. Die errorId ist eine pro Vorfall erzeugte UUID, unter der der Server
den vollen Stacktrace loggt — sie ist die Referenz für den Support. Wiederholen mit Backoff ist
sinnvoll; bleibt es dabei, melde errorId, Uhrzeit und Endpunkt.
Framework-Fehler ohne code
Springs eigene Request-Fehler behalten ihren Status und werden nicht in den
{code, message}-Vertrag übersetzt:
| HTTP | Wann |
|---|---|
| 400 | Ungültiges JSON im Body; nicht-numerisches page/size; unparsbares changedSince |
| 404 | Kein Endpunkt für den Pfad (Tippfehler, falsche Version, externe API deaktiviert) |
| 405 | Falsche HTTP-Methode am Endpunkt |
| 413 | Upload überschreitet 20 MB |
| 415 | Falscher oder fehlender Content-Type (z. B. Upload ohne multipart/form-data) |
Behandle „4xx ohne code" in deinem Client als Programmierfehler auf deiner Seite, nicht als
fachlichen Zustand.
Empfohlenes Fehler-Handling
| Antwort | Verhalten |
|---|---|
| 401, 403 | sofort abbrechen und alarmieren — Konfigurationsproblem, kein Retry |
| 404 | Ressource als „für mich nicht existent" behandeln, nächsten Datensatz verarbeiten |
400 VALIDATION / ohne code | abbrechen und loggen — dein Request ist falsch |
400 ALREADY_MODIFIED | neu lesen, Änderung erneut anwenden, ein Mal wiederholen |
| 409 | fachlichen Zustand protokollieren, nicht wiederholen |
| 429 | Retry-After abwarten, dann mit Backoff weiter |
| 5xx | exponentieller Backoff, begrenzte Versuche, errorId mitloggen |