Zum Hauptinhalt springen

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

codeHTTPBedeutungRetry sinnvoll?
API_KEY_INVALID401Key fehlt, ist unbrauchbar oder nicht mehr gültignein — alarmieren
ACCESS_DENIED403Scope fehlt, oder echter Rechtefehler der Grant-Personnein
NOT_FOUND404Ressource unbekannt / fremder Mandant / nicht sichtbarnein
VALIDATION400Ungültige Eingabenein — Request korrigieren
ALREADY_MODIFIED400Optimistic-Lock-Konfliktja, nach erneutem Lesen
CONFLICT409Zustand passt nicht zur Aktion (Status, fehlende Dokumente, Duplikat)nur nach Zustandsänderung
RATE_LIMITED429IP-Limit vor der Authentifizierung erreichtja, nach Retry-After
API_RATE_LIMITED429Key-/Benutzer-/Mandanten-Limit nach der Authentifizierung erreichtja, nach Retry-After
API_KEY_AUTH_UNAVAILABLE503Authentifizierung selbst gestörtja, mit Backoff
INTERNAL_ERROR500unerwarteter Serverfehlerja, 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:

UrsacheWoran es liegt
Kein, mehrfacher oder falsch formatierter Authorization-HeaderGenau 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 SecretKey aus der Verwaltung neu ausstellen lassen
Key abgelaufen, deaktiviert, widerrufen oder als kompromittiert markiertStatus in der Schlüsselverwaltung prüfen
Rotations-Übergangsfrist des Vorgängerschlüssels abgelaufenAuf den neuen Key umstellen
Grant der Person ausgesetzt oder widerrufenNeuen Grant anfordern
Mandanten-Policy deaktiviertOperator einbeziehen
Benutzer inaktiv, technisch oder nicht mehr Mitglied des MandantenBenutzerkonto 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öserAbhilfe
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 darflocationId unverändert lassen
PUT /cases/{caseId} ändert die Bearbeitungsart ohne die nötigen RechteprocessingType 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:

  1. Die Ressource existiert nicht.
  2. Sie gehört zu einem fremden Mandanten.
  3. 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-values antwortet 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.

EndpunktAuslöser
alle mit {caseId}caseId ist keine gültige UUID
GET /casespage 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}/statusstatus fehlt; status = HIDDEN; status = CANCELLED ohne reason
PUT /cases/{caseId}/tagstag fehlt oder ist NONE
PUT /cases/{caseId}/evaluation-valuesBody fehlt; caseId im Body ≠ Pfad
POST /cases/{caseId}/attachmentsDatei leer; Dateiname mit Pfad-Traversal
POST /cases/{caseId}/commentsText fehlt/leer; Text enthält eine auflösbare @-Erwähnung
GET /locations/{locationCode}leerer Standortcode
POST/PUT /insurancesinsuranceId, 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)

EndpunktAuslöser
PUT /cases/{caseId}Fall ist CLOSED, CANCELLED, HIDDEN oder WAITING_FOR_CUSTOMER (nicht editierbar)
POST /cases/{caseId}/attachmentswie oben
PUT /cases/{caseId}/statusRELEASEDkeine signierte Vollmacht/RKÜ (entfällt bei Eigenbearbeitung)
PUT /cases/{caseId}/statusHANDED_OVER_APPRAISERkein signierter Gutachtenauftrag bzw. keine Vollmacht
PUT /cases/{caseId}/statusCANCELLEDFall 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 /insurancesEintrag 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:

HTTPWann
400Ungültiges JSON im Body; nicht-numerisches page/size; unparsbares changedSince
404Kein Endpunkt für den Pfad (Tippfehler, falsche Version, externe API deaktiviert)
405Falsche HTTP-Methode am Endpunkt
413Upload überschreitet 20 MB
415Falscher 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

AntwortVerhalten
401, 403sofort abbrechen und alarmieren — Konfigurationsproblem, kein Retry
404Ressource als „für mich nicht existent" behandeln, nächsten Datensatz verarbeiten
400 VALIDATION / ohne codeabbrechen und loggen — dein Request ist falsch
400 ALREADY_MODIFIEDneu lesen, Änderung erneut anwenden, ein Mal wiederholen
409fachlichen Zustand protokollieren, nicht wiederholen
429Retry-After abwarten, dann mit Backoff weiter
5xxexponentieller Backoff, begrenzte Versuche, errorId mitloggen