Integrator-Kochbuch
Die typische Sync-Schleife einer Anbindung als konkrete Aufruffolge. Alle Beispiele gehen von diesen beiden Variablen aus:
export USP_BASE="https://usp.linkki.de/api/external/v1"
export USP_API_KEY="usp_dev_9F8A3K2M7Q4T6V1X5Z0C3BDEFG_…"
Ein wiederkehrendes Muster in allen Beispielen: -H "Authorization: Bearer $USP_API_KEY".
Antworten sind JSON; jq dient hier nur der Lesbarkeit.
0. Beim Start: Key verifizieren
curl -sS "$USP_BASE/me" -H "Authorization: Bearer $USP_API_KEY" | jq
{
"publicId": "9F8A3K2M7Q4T6V1X5Z0C3BDEFG",
"username": "m.mustermann",
"tenantRoot": "musterhaus",
"scopes": ["cases:read", "cases:write", "cases:status", "attachments:read", "attachments:write", "comments:read", "comments:write", "reference-data:read"],
"serverTime": "2026-07-20T09:14:22.481+02:00"
}
Prüfe hier einmalig, ob alle Scopes vorhanden sind, die deine Integration braucht — und brich mit einer klaren Meldung ab, wenn nicht. Das erspart dir später ein 403 mitten im Lauf.
tenantRoot sagt dir zugleich, welche Fälle du siehst: Ist dein Key auf einen
Werkstatt-Mandanten ausgestellt (z. B. musterhaus), liefert GET /cases die Fälle, deren
Standort im Teilbaum liegt. Ist er auf einen Fallabwickler-Mandanten ausgestellt (z. B.
musterhandler), bekommst du genau die Fälle, deren caseHandler im Teilbaum liegt — egal in
welcher Werkstatt sie liegen; Fälle in Eigenbearbeitung (OWN_PROCESSING) tauchen dort nie auf.
Ist er auf ein Gutachterbüro ausgestellt, bekommst du die Fälle, denen dein Büro als
Gutachter (expertOfficeCode) zugewiesen ist. Die Sync-Schleife unten ist für alle drei Sichten
identisch.
1. Stammdaten in den Cache
Einmal pro Lauf, nicht pro Fall:
curl -sS "$USP_BASE/locations" -H "Authorization: Bearer $USP_API_KEY" > locations.json
curl -sS "$USP_BASE/case-handlers" -H "Authorization: Bearer $USP_API_KEY" > case-handlers.json
curl -sS "$USP_BASE/insurances" -H "Authorization: Bearer $USP_API_KEY" > insurances.json
curl -sS "$USP_BASE/legal-insurances" -H "Authorization: Bearer $USP_API_KEY" > legal-insurances.json
curl -sS "$USP_BASE/users" -H "Authorization: Bearer $USP_API_KEY" > users.json
Damit löst du später locationId, caseHandler, vehicleInsuranceId, opponentInsuranceId,
legalInsuranceId und createdBy lokal auf. Siehe Stammdaten.
2. Erstabgleich: alle Fälle einlesen
Beim ersten Lauf gibt es keinen Cursor. Blättere chronologisch durch:
PAGE=0
while : ; do
RESP=$(curl -sS "$USP_BASE/cases?sort=CREATION_DATE&direction=ASC&page=$PAGE&size=200" \
-H "Authorization: Bearer $USP_API_KEY")
echo "$RESP" | jq -r '.items[].id' >> alle-faelle.txt
TOTAL=$(echo "$RESP" | jq -r '.totalPages')
PAGE=$((PAGE + 1))
[ "$PAGE" -ge "$TOTAL" ] && break
done
size ist auf 200 gedeckelt. Merke dir am Ende des Erstabgleichs den größten updated-Wert als
ersten Cursor.
3. Delta-Abholung
Der eigentliche Sync-Takt. changedSince ist inklusiv und liefert unpaginiert, älteste
Änderung zuerst.
CURSOR="2026-07-20T06:00:00" # aus dem letzten Lauf persistiert
curl -sS --get "$USP_BASE/cases" \
--data-urlencode "changedSince=$CURSOR" \
-H "Authorization: Bearer $USP_API_KEY" | jq '{delta, totalItems, ids: [.items[].id]}'
{ "delta": true, "totalItems": 3, "ids": ["5f4d…", "8a1c…", "b0e7…"] }
Cursor-Regel: neuer Cursor = größter updated-Wert des Batches. Weil die Grenze inklusiv ist,
kommt der jüngste Fall im nächsten Lauf noch einmal — mach deine Verarbeitung idempotent, statt den
Cursor um eine Sekunde vorzuschieben (sonst verlierst du Änderungen, die in derselben Sekunde
passiert sind).
Ist der Batch leer, bleibt der Cursor unverändert. Halte den Takt eng (Minuten bis wenige Stunden): das Ergebnis ist unpaginiert und wächst sonst unbegrenzt.
4. Fall vollständig lesen
CASE_ID="5f4d1c2e-9b1a-4f3e-8c2d-1a2b3c4d5e6f"
curl -sS "$USP_BASE/cases/$CASE_ID" -H "Authorization: Bearer $USP_API_KEY" > case.json
jq '{id, status, locationId, caseHandler, lastModifiedDate}' case.json
Heb lastModifiedDate auf — es ist deine Lock-Baseline.
5. Anhänge abholen
curl -sS "$USP_BASE/cases/$CASE_ID/attachments" \
-H "Authorization: Bearer $USP_API_KEY" | jq '.[] | {attachmentId, fileName, tag, created}'
Nur die fachlich interessanten herunterladen — jeder Download zählt gegen dein Rate-Limit:
ATT_ID=$(curl -sS "$USP_BASE/cases/$CASE_ID/attachments" \
-H "Authorization: Bearer $USP_API_KEY" | jq -r '.[] | select(.tag=="ADVISORY") | .attachmentId' | head -1)
curl -sS -o gutachten.pdf "$USP_BASE/attachments/$ATT_ID/content" \
-H "Authorization: Bearer $USP_API_KEY"
Merke dir verarbeitete attachmentIds, damit du beim nächsten Lauf nicht alles erneut lädst.
6. Kommentare lesen
curl -sS "$USP_BASE/cases/$CASE_ID/comments" \
-H "Authorization: Bearer $USP_API_KEY" | jq '.[] | {creationDate, fullName, comment}'
7. Zurückschreiben
Jetzt die Gegenrichtung. Reihenfolge beachten — jeder Anhang, Kommentar und Tag bewegt
lastModifiedDate des Falls.
7a. Falldaten aktualisieren
Komplettes Objekt zurückschicken, nur die eigenen Felder ändern:
jq '.fileSignLawyer = "12 C 345/26" | .customerReference = "AZ-2026-0815"' case.json > case-update.json
curl -sS -X PUT "$USP_BASE/cases/$CASE_ID" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
--data @case-update.json | jq '{id, lastModifiedDate}'
Bei 400 ALREADY_MODIFIED: GET /cases/{id} erneut, Änderung auf dem frischen Objekt anwenden,
noch einmal senden — nie blind wiederholen.
Lass locationId unverändert, so wie du sie gelesen hast: ein Fallabwickler-Key kann den Fall
ohnehin nie in einen anderen Standort verschieben (der Werkstatt-Standort liegt nie in seinem
Mandanten), eine Werkstatt nur innerhalb des eigenen Teilbaums.
7b. Dokument hochladen
ATT_ID=$(curl -sS -X POST "$USP_BASE/cases/$CASE_ID/attachments" \
-H "Authorization: Bearer $USP_API_KEY" \
-F "file=@schriftsatz.pdf" | jq -r '.attachmentId')
201 Created; der Upload startet mit Tag NONE. Danach klassifizieren:
curl -sS -X PUT "$USP_BASE/attachments/$ATT_ID/tag" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tag":"INVOICE_REPAIR"}' | jq '{attachmentId, tag}'
Auch die *_SIGNED-Tags sind setzbar (papier-signierte Scans) — sie schalten die Workflow-Gates
(Freigabe, Gutachter-Übergabe). Details:
Anhänge verwalten.
7c. Sachstand kommentieren
curl -sS -X POST "$USP_BASE/cases/$CASE_ID/comments" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"comment":"Klage beim Amtsgericht eingereicht, Az. 12 C 345/26."}' | jq '{commentId, creationDate}'
@benutzername im Text erwähnt Beteiligte des Falls und benachrichtigt sie — wie in der
Plattform (Benutzernamen liefert GET /users). Eigene Kommentare korrigierst du mit
PUT /comments/{commentId} (gleicher Body); fremde sind tabu (403). Details:
Kommentare.
7d. Fortschritt melden
curl -sS -X PUT "$USP_BASE/cases/$CASE_ID/tags" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tag":"LIABILITY_CONFIRMATION_ISSUED"}' | jq
Idempotent — ein doppelter Aufruf ist ein No-Op.
7e. Regulierungswerte pflegen
Vollersetzung: erst lesen, dann ändern, dann schreiben.
curl -sS "$USP_BASE/cases/$CASE_ID/evaluation-values" \
-H "Authorization: Bearer $USP_API_KEY" > eval.json || echo '{}' > eval.json
jq '.repairCostsReceived = 4210.55 | .repairInvoiceNo = "RE-2026-4711"' eval.json > eval-update.json
curl -sS -X PUT "$USP_BASE/cases/$CASE_ID/evaluation-values" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
--data @eval-update.json | jq
Ein 404 beim Lesen heißt „noch keine Werte erfasst" — kein Fehler.
7f. Statuswechsel
curl -sS -X PUT "$USP_BASE/cases/$CASE_ID/status" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"CLOSED"}' | jq
Storno braucht eine Begründung:
curl -sS -X PUT "$USP_BASE/cases/$CASE_ID/status" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"status":"CANCELLED","reason":"Kunde hat den Auftrag zurueckgezogen."}' | jq
7g. Fall anlegen
Der neue Fall muss zu deinem Mandanten gehören: als Werkstatt-Key legst du im eigenen Teilbaum
an, als Fallabwickler-Key genau dort, wo Standort + Schadenart an dich routen, als Gutachter-Key
dort, wo dein Büro als Gutachter zugewiesen ist (dein Büro ist dann automatisch am Fall
vorausgewählt); ein Admin-Mandant hat keine Teilbaum-Schranke — sonst 404. Startstatus ist immer
WORK_IN_PROGRESS, die id vergibt der Server, caseHandler wird aus Standort + Schadenart
abgeleitet. Alle Regeln: Fallanlage.
NEW_CASE=$(curl -sS -X POST "$USP_BASE/cases" \
-H "Authorization: Bearer $USP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"locationId":"musterhaus","damageType":"THIRD_PARTY_LIABILITY","vehicleLicensePlate":"B-XY 123"}')
CASE_ID=$(echo "$NEW_CASE" | jq -r '.id')
Die Antwort ist der vollständige Fall; sein lastModifiedDate ist die Baseline für dein erstes
PUT /cases/$CASE_ID.
8. Nach dem Schreiben: Baseline auffrischen
Wenn du im selben Lauf noch einmal PUT /cases/{id} senden willst, lies den Fall vorher neu:
curl -sS "$USP_BASE/cases/$CASE_ID" -H "Authorization: Bearer $USP_API_KEY" \
| jq -r '.lastModifiedDate'
Die Schleife im Überblick
Betriebshinweise
| Thema | Empfehlung |
|---|---|
| Rate-Limit | Standard sind 120 Requests/Minute je Key. Ein Fall kostet dich schnell 4–6 Requests — plane Batchgröße und Takt danach, und respektiere Retry-After bei 429. |
| Retry | Nur bei 429 und 5xx wiederholen, mit exponentiellem Backoff. 401 und 403 sind Konfigurationsfehler; 400 und 409 sind fachliche Zustände. |
| Idempotenz | PUT ist wiederholbar (Lock beachten), PUT …/tags ist explizit idempotent. POST (Anhang, Kommentar) nicht — nach einem Timeout erst die Liste prüfen. |
| Cursor-Persistenz | Den Cursor transaktional mit dem Verarbeitungsergebnis speichern. Sonst verlierst du bei einem Absturz entweder Änderungen oder verarbeitest doppelt. |
| Zeitzone | Die Zeitstempel der Fachdaten tragen keine Zone; sie sind lokale Zeit der Plattform. Rechne sie nicht um und schick sie so zurück, wie du sie bekommen hast. |
| Unbekannte Felder | Neue Felder können innerhalb von v1 hinzukommen. Dein JSON-Parser muss sie ignorieren, statt zu scheitern. |
| Protokollierung | Logge publicId, Endpunkt, Status und bei 5xx die errorId — nie den Key selbst und nie ganze Response-Bodies mit Personendaten. |