Übersicht und Bearbeitung
Der Kernablauf jeder Integration: Fälle des Mandanten auflisten oder als Delta abholen, einen Fall
vollständig lesen, Felder zurückschreiben und die Regulierungswerte pflegen. Alles unter
/api/external/v1.
Ziel
Du hältst dein Fremdsystem mit der Plattform synchron: neue und geänderte Fälle abholen, deine eigenen Daten (Aktenzeichen, Beträge, Adressen) zurückschreiben — ohne dir dabei Änderungen anderer zu überschreiben.
Voraussetzungen
- Scopes:
cases:readzum Lesen,cases:writezum Schreiben. Sie implizieren einander nicht. - Sichtbarkeit: doppelt begrenzt — auf die Standorte der Person hinter dem Grant und auf
den Mandanten des Keys. Ein Fall gehört zum Mandanten, wenn sein Standort im Teilbaum liegt
(Werkstatt-Sicht, z. B. Key auf
musterhaus) oder sein Fallabwickler (caseHandler) dort liegt (Fallabwickler-Sicht, z. B. Key aufmusterhandler— der sieht die ihm zugewiesenen Fälle, egal in welcher Werkstatt sie liegen; Fälle in Eigenbearbeitung nie). Alles andere ist 404, nie 403. - Status: Schreiben nur auf editierbaren Fällen (
WORK_IN_PROGRESS,RELEASED,HANDED_OVER_APPRAISER); siehe Status-Lebenszyklus. - Optimistic Lock: Für
PUT /cases/{caseId}brauchst du das zuletzt gelesenelastModifiedDatedes Falls.
1. Fälle auflisten
GET /api/external/v1/cases (Scope cases:read)
Ein GET, weil es ein Lesezugriff ist — die internen Overview-Aufrufe der Oberfläche (POST mit
Filterliste) haben hier kein Gegenstück. Der Mandantenfilter wird nicht übergeben, sondern
serverseitig aus dem Key gesetzt: ein Werkstatt-Key (musterhaus) filtert auf den Fall-Standort
im Teilbaum, ein Fallabwickler-Key (musterhandler) auf die ihm zugewiesenen Fälle
(caseHandler) — Fälle in Eigenbearbeitung tauchen dort nie auf.
| Parameter | Default | Bedeutung |
|---|---|---|
page | 0 | 0-basierter Seitenindex |
size | 50 | Seitengröße, maximal 200 |
sort | LAST_MODIFICATION_DATE | LAST_MODIFICATION_DATE, CREATION_DATE oder RELEASE_DATE (Groß-/Kleinschreibung egal; unbekannte Werte fallen still auf CREATION_DATE zurück) |
direction | DESC | ASC oder DESC (unbekannte Werte fallen still auf DESC zurück) |
changedSince | — | ISO-8601-Zeitstempel ohne Zone; schaltet in den Delta-Modus (siehe unten) |
Antwort:
{
"items": [ { "id": "5f4d…", "status": "RELEASED", "opened": "2026-07-02T10:12:00", "…": "…" } ],
"totalItems": 138,
"page": 0,
"size": 50,
"totalPages": 3,
"delta": false
}
Jeder Eintrag ist eine kompakte Projektion des Falls:
| Feld | Inhalt |
|---|---|
id | Fall-UUID — der Schlüssel für alle weiteren Aufrufe |
status | aktueller CaseStatus |
opened | Anlagezeitpunkt |
updated | Änderungszeitpunkt, abgeleitet aus der Fallhistorie (Fallback: lastModifiedDate) |
released, closedDate | Freigabe- bzw. Abschlusszeitpunkt, sonst null |
lastName, company | Nachname bzw. Firma der geschädigten Partei (leerer String, wenn nicht gesetzt) |
vehicleLicensePlate | Kennzeichen des beschädigten Fahrzeugs |
damageType, repairWanted, accidentDate, accidentNational | Schadenart, Reparaturwunsch, Unfalldatum, Auslandschaden |
location | Standort des Falls (locationCode, name, partner, type, …) |
tags | nicht versteckte Fall-Tags |
processingType | HANDLER_PROCESSING oder OWN_PROCESSING |
caseHandler | Standortcode des zuständigen Fallabwicklers; null, wenn keiner zugewiesen ist |
Zwei Eigenheiten, die du kennen solltest:
sort=RELEASE_DATEverkleinert die Treffermenge: Fälle ohne Freigabedatum fallen heraus.totalItemsändert sich dadurch mit.- Zeilen, die weder über Standort noch über Fallabwickler zum Mandanten gehören, werden
verworfen (fail closed). Die Summe der gelieferten
itemsüber alle Seiten kann deshalb minimal kleiner sein alstotalItems.
2. Delta-Abholung (changedSince)
GET /api/external/v1/cases?changedSince=2026-07-20T06:00:00
Mit changedSince liefert der Endpunkt unpaginiert alle Fälle des Mandanten, deren
lastModifiedDate am oder nach dem Zeitstempel liegt — älteste Änderung zuerst.
page, size, sort und direction werden dann ignoriert; in der Antwort ist delta = true,
page/size/totalPages sind null und totalItems ist die Zeilenzahl dieses Batches.
Die Grenze ist inklusiv. Verwende deshalb als neuen Cursor den größten updated-Wert des
Batches und akzeptiere, dass der jüngste Fall im nächsten Lauf noch einmal auftaucht — dein
Verarbeitungsschritt sollte idempotent sein. Ein zu weit vorgeschobener Cursor verliert dagegen
Änderungen dauerhaft.
Weil das Ergebnis unpaginiert ist: halte den Takt eng (Minuten bis wenige Stunden), sonst wächst der
Batch unbegrenzt. Für den Erstabgleich nimm den Seitenmodus mit sort=CREATION_DATE&direction=ASC.
Die vollständige Schleife inklusive Cursor-Handling steht im Integrator-Kochbuch.
3. Einen Fall lesen
GET /api/external/v1/cases/{caseId} (Scope cases:read)
Liefert das komplette CaseDTO — dieselbe Struktur, die auch die Oberfläche bekommt: Kopfdaten
(id, status, locationId, caseHandler, fileSign, customerReference, damageType,
processingType), die Adressblöcke injuredParty, opponentOwnerAddress, opponentDriverAddress,
witnessAddress, die Fahrzeug- und Versicherungsfelder (vehicleLicensePlate, vehicleId,
vehicleInsuranceId, opponentInsuranceId, opponentClaimNumber, legalInsuranceId, …), die
Unfalldaten (accidentDate, accidentPlace, accidentCourse, policeAlerted, …), die
Gutachterfelder (expertOffice, expertOfficeCode, expertEmployee, expertReference) sowie
additionalInfos, tags, releaseDate, closedDate und die Audit-Felder createdBy,
creationDate, lastModifiedBy, lastModifiedDate.
lastModifiedDate ist deine Lock-Baseline — hebe sie zusammen mit dem Fall auf.
Unbekannte UUID, fremder Mandant und fehlende Sichtbarkeit antworten identisch mit
404 NOT_FOUND („Fall nicht gefunden"). Eine syntaktisch ungültige UUID ergibt 400 VALIDATION.
4. Fall aktualisieren
PUT /api/external/v1/cases/{caseId} (Scope cases:write)
Der Body ist das komplette CaseDTO, nicht ein Patch. Das erprobte Muster:
GET /cases/{caseId}— vollständiges Objekt holen.- Nur die Felder ändern, die du besitzt.
- Objekt unverändert im Rest zurückschicken.
Regeln, die der Server durchsetzt:
| Regel | Verstoß |
|---|---|
lastModifiedDate muss exakt dem Serverstand entsprechen | 400 ALREADY_MODIFIED (nicht 409!) |
locationId muss gesetzt sein und entweder dem persistierten Standort entsprechen oder im Mandanten des Keys liegen — eine Werkstatt (musterhaus) kann den Fall im eigenen Teilbaum umziehen, ein Fallabwickler-Key (musterhandler) schickt den Werkstatt-Standort unverändert zurück und kann den Fall nie verschieben | 400 VALIDATION bzw. 404 NOT_FOUND |
Eine id im Body darf der Pfad-caseId nicht widersprechen (der Pfad gewinnt) | 400 VALIDATION |
status darf nicht vom persistierten Status abweichen | 400 VALIDATION mit Verweis auf PUT /cases/{caseId}/status |
damageType darf nicht vom persistierten Wert abweichen — die Schadenart bestimmt das Fallabwickler-Routing und wird in der Plattform geändert, nicht über die API | 400 VALIDATION |
| Fall muss editierbar sein | 409 CONFLICT |
| Zielstandort und Bearbeitungsart müssen für die Grant-Person erlaubt sein | 403 ACCESS_DENIED |
Der Statuswechsel ist bewusst ausgesperrt: er hat mit cases:status einen eigenen Scope, und ein
still verworfenes status-Feld wäre eine Falle. Schick den Status entweder gar nicht mit oder
genau so, wie du ihn gelesen hast.
Die Antwort ist der gespeicherte Fall mit neuem lastModifiedDate — übernimm es sofort als
neue Baseline.
Seiteneffekte: Der Server schreibt den Diff in die Fallhistorie und veröffentlicht ein
CaseUpdatedEvent. Bei fachlich relevantem Unterschied geht eine CASE_UPDATED-Mail an das
Postfach des Fallabwickler-Standorts — unterdrückt bei Eigenbearbeitung, im Entwurfsstatus und je
nach Mail-Präferenz des Standorts. Ein Wechsel des Gutachterbüros löst zusätzlich eine
Gutachter-Benachrichtigung aus. Schreib also nicht in einer Schleife identische Werte zurück.
Optimistic Lock in der Praxis
lastModifiedDate bewegt sich auch ohne Fall-Update: jeder Historieneintrag — also auch ein
Anhang-Upload, ein Kommentar, ein Fall-Tag oder eine Regulierungswert-Änderung — schiebt ihn vor.
Die praktische Konsequenz für deine Integration:
GET /cases/{id} → lastModifiedDate = T0
POST /cases/{id}/attachments → Historie! Serverstand ist jetzt T1
PUT /cases/{id} (mit T0) → 400 ALREADY_MODIFIED
Richtig ist: nach jedem Anhang-, Kommentar- oder Tag-Aufruf den Fall neu lesen (oder zuerst den
Fall schreiben und danach die Nebenobjekte). Bei 400 ALREADY_MODIFIED gilt: neu lesen, Änderung
erneut anwenden, noch einmal schreiben — nie blind wiederholen.
5. Regulierungswerte
GET /api/external/v1/cases/{caseId}/evaluation-values (Scope cases:read)
PUT /api/external/v1/cases/{caseId}/evaluation-values (Scope cases:write)
Die finanziellen Werte des Falls: issueDate, repairInvoiceNo,
repairCostsIssued/repairCostsReceived, rentalInvoiceNo,
rentalCostsIssued/rentalCostsReceived, substitutionNoticeDate, advanceRequestDate,
depreciationIssued/depreciationReceived, expertFeesIssued/expertFeesReceived,
expertInvoiceNo.
GETantwortet 404, wenn für den Fall noch keine Werte erfasst sind. Das ist kein Fehler, sondern der Normalfall bei frischen Fällen — behandle es als „leer".PUTist eine Vollersetzung, kein Patch. Jedes Feld des Bodys wird geschrieben; ausgelassene Felder werden geleert. Lies also erst, ändere, schreib zurück.- Die
caseIdaus dem Pfad gewinnt; eine abweichendecaseIdim Body ergibt 400VALIDATION. - Der Aufruf stempelt
lastSyncedam Fall und schreibt die alten/neuen Werte in die Fallhistorie — und bewegt damitlastModifiedDate. - Es gibt hier kein Optimistic Locking und kein Status-Gate: auch geschlossene Fälle sind änderbar, und zwei parallele Schreiber überschreiben sich gegenseitig.
Was kann schiefgehen?
Vollständiger Katalog: Fehlercodes.
| HTTP | code | Wann | Was tun |
|---|---|---|---|
| 400 | VALIDATION | page negativ, size außerhalb 1–200, ungültige UUID, fehlende locationId, id-/caseId-Konflikt, Status- oder Schadenart-Änderung im Fall-Update | Request korrigieren; Statuswechsel über PUT /cases/{caseId}/status |
| 400 | (kein code) | nicht-numerisches page/size oder unparsbares changedSince — das fängt Spring vor dem Controller ab | Parameterformat prüfen (2026-07-20T06:00:00) |
| 400 | ALREADY_MODIFIED | lastModifiedDate fehlt oder ist veraltet | Fall neu lesen, Änderung erneut anwenden, erneut schreiben |
| 403 | ACCESS_DENIED | Scope fehlt; oder Zielstandort/Bearbeitungsart für die Grant-Person nicht erlaubt | Key-Scopes prüfen; Standort im eigenen Mandanten belassen |
| 404 | NOT_FOUND | Fall unbekannt, fremder Mandant, außerhalb der Sichtbarkeit — oder eine geänderte locationId außerhalb des Mandanten; bei GET …/evaluation-values auch: noch keine Werte erfasst | ID prüfen; bei Regulierungswerten als „leer" behandeln |
| 409 | CONFLICT | Fall ist CLOSED, CANCELLED, HIDDEN oder WAITING_FOR_CUSTOMER | Status prüfen; ggf. erst wiedereröffnen |
| 429 | RATE_LIMITED / API_RATE_LIMITED | Rate-Limit erreicht | Retry-After abwarten |
| 500 | INTERNAL_ERROR | unerwarteter Serverfehler | errorId aus der Antwort an den Support geben |