Zum Hauptinhalt springen

Integrator-Kochbuch

Die typische Sync-Schleife einer Anbindung als konkrete Aufruffolge. Alle Beispiele gehen von diesen beiden Variablen aus:

export USP_BASE="https://dev.devlodge.site/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. Die Sync-Schleife unten ist für beide 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

curl -sS -X POST "$USP_BASE/cases/$CASE_ID/attachments" \
-H "Authorization: Bearer $USP_API_KEY" \
-F "file=@schriftsatz.pdf" | jq '{attachmentId, fileName, tag}'

201 Created; der Tag bleibt NONE (Tags setzen ist extern nicht verfügbar).

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}'

Kein @ im Text — auflösbare Erwähnungen werden mit 400 abgelehnt.

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

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

ThemaEmpfehlung
Rate-LimitStandard 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.
RetryNur bei 429 und 5xx wiederholen, mit exponentiellem Backoff. 401 und 403 sind Konfigurationsfehler; 400 und 409 sind fachliche Zustände.
IdempotenzPUT ist wiederholbar (Lock beachten), PUT …/tags ist explizit idempotent. POST (Anhang, Kommentar) nicht — nach einem Timeout erst die Liste prüfen.
Cursor-PersistenzDen Cursor transaktional mit dem Verarbeitungsergebnis speichern. Sonst verlierst du bei einem Absturz entweder Änderungen oder verarbeitest doppelt.
ZeitzoneDie 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 FelderNeue Felder können innerhalb von v1 hinzukommen. Dein JSON-Parser muss sie ignorieren, statt zu scheitern.
ProtokollierungLogge publicId, Endpunkt, Status und bei 5xx die errorId — nie den Key selbst und nie ganze Response-Bodies mit Personendaten.