Zum Hauptinhalt springen

Authentifizierung und Scopes

Die externe API kennt genau ein Authentifizierungsverfahren: den Benutzer-API-Key. Diese Seite beschreibt sein Format, wie du ihn sendest, woran er gebunden ist, welche Scopes es gibt, welche Endpunkte sie freischalten und wie die Fehlerbilder 401/403/404/429/503 zu lesen sind.

Wie du überhaupt an einen Key kommst und wie du ihn rotierst oder widerrufst, steht unter Schlüsselverwaltung.

Key-Format

usp_<env>_<publicId>_<secret>
SegmentInhalt
uspfester Präfix
<env>Umgebungskennung der ausstellenden Instanz, 1–16 Zeichen aus [a-z0-9-] (z. B. dev, prod)
<publicId>26 Zeichen Crockford-Base32 — die öffentliche Kennung des Keys; sie steht in Audit-Log und Verwaltungs-UI und darf im Ticket genannt werden
<secret>43 Zeichen Base64url — das eigentliche Geheimnis

Der Server speichert nur einen gepfefferten HMAC des Secrets (secret_verifier), niemals das Secret selbst. Der Klartext-Key wird bei der Erstellung genau einmal angezeigt und ist danach nicht wiederherstellbar — verloren heißt neu ausstellen.

Ein Key trägt die Umgebung im Namen, damit ein Dev-Key in einem Prod-Ticket sofort auffällt. Er ist kein JWT: er ist undurchsichtig, hat keinen lesbaren Claim-Inhalt und lässt sich nicht lokal validieren.

Übertragung

Authorization: Bearer usp_dev_9F8A3K2M7Q4T6V1X5Z0C3BDEFG_5nJq…

Harte Regeln des Filters:

  • Genau ein Authorization-Header. Null, zwei oder ein anderes Schema als Bearer ⇒ 401.
  • Kein Key im Query-String, im Pfad, in einem Cookie oder im Body — diese Wege existieren nicht.
  • TLS ist Pflicht: der Key ist ein Bearer-Credential im Klartext.

Bei erfolgreicher Authentifizierung wird der last_used_at-Zeitstempel des Keys gedrosselt fortgeschrieben — die Verwaltungsoberfläche zeigt damit, ob ein Key noch lebt.

Woran ein Key gebunden ist

Ein Key hängt an einer Kette, die bei jedem Request neu geprüft wird:

Daraus folgen drei Dinge, die im Alltag zählen:

  1. Effektive Scopes = Key ∩ Grant ∩ Policy. Wird ein Scope am Grant oder an der Mandanten-Policy entfernt, verliert der Key ihn sofort — ohne dass jemand den Key anfasst. Was aktuell wirkt, sagt dir GET /me.
  2. Ein Key = ein Mandant. Die Person hinter dem Grant darf mehreren Mandanten angehören; der Key sieht trotzdem nur den einen Standort-Teilbaum, auf den er ausgestellt wurde. Der Vergleich ist exakt: Mandant musterhaus umfasst musterhaus und musterhaus/nord, aber nicht musterhaus2. Für Fälle bestimmt der Typ des Mandanten die Sicht: ein Werkstatt-Mandant (musterhaus) bindet über den Standort des Falls, ein Fallabwickler-Mandant (musterhandler) über den zugewiesenen Fallabwickler (caseHandler) — dessen Key sieht also genau die ihm zugewiesenen Fälle, obwohl sie in fremden Werkstatt-Standorten liegen —, ein Gutachter-Mandant (Büro) über das zugewiesene Gutachterbüro (expertOfficeCode) des Falls. Fälle in Eigenbearbeitung (OWN_PROCESSING) sind über die Fallabwickler-Sicht nie sichtbar, und ein Fallabwickler-Key kann einen Fall nie in einen anderen Standort verschieben.
  3. Die Person bleibt im Spiel. Wird das Benutzerkonto deaktiviert, in einen technischen Benutzer umgewandelt oder aus dem Mandanten entfernt, sind alle seine Keys sofort tot. Ein Key sieht außerdem nie mehr Fälle als die Person selbst: die fachlichen Services scopen weiterhin auf deren Standort-Mitgliedschaften. Genau daher kommt auch die Eigenbearbeitungs-Regel der Fallabwickler-Sicht: schon die Personen-Schranke liefert Fallabwickler-Mitgliedern keine OWN_PROCESSING-Fälle.

Selbstauskunft: GET /me

Der einzige Endpunkt ohne Scope-Anforderung — ein gültiger Key genügt. Er ist der Smoke-Test jeder Integration und der Weg, die tatsächlich wirksame Konfiguration zu sehen.

curl -sS https://usp.linkki.de/api/external/v1/me \
-H "Authorization: Bearer $USP_API_KEY"
{
"publicId": "9F8A3K2M7Q4T6V1X5Z0C3BDEFG",
"username": "m.mustermann",
"tenantRoot": "musterhaus",
"scopes": ["cases:read", "cases:write", "attachments:read"],
"serverTime": "2026-07-20T09:14:22.481+02:00"
}

Die Antwort enthält nie das Secret, nie den Verifier und keine Fachdaten. scopes sind die effektiven Scopes (siehe oben) — wundere dich also nicht, wenn dort weniger steht als bei der Key-Erstellung ausgewählt wurde.

Scope-Katalog

Jeder Endpunkt trägt genau einen Scope. Es gibt kein Wildcard, keinen „oder"-Verbund und keinen Scope, der einen anderen impliziert: cases:write beinhaltet nicht cases:read.

Die Risikostufe ist reine Anzeigeinformation für die Person, die den Key erzeugt (die UI warnt entsprechend) — sie ist nie Teil einer Autorisierungsprüfung.

ScopeRisikoSchaltet frei
cases:readnormalGET /cases, GET /cases/{caseId}, GET /cases/{caseId}/tags, GET /cases/{caseId}/evaluation-values
cases:writeerhöhtPOST /cases, PUT /cases/{caseId}, PUT /cases/{caseId}/tags, PUT /cases/{caseId}/evaluation-values
cases:statuserhöhtPUT /cases/{caseId}/status
attachments:readerhöhtGET /cases/{caseId}/attachments, GET /attachments/{attachmentId}, GET /attachments/{attachmentId}/content
attachments:writeerhöhtPOST /cases/{caseId}/attachments, PUT /attachments/{attachmentId}/tag, DELETE /attachments/{attachmentId}
comments:readnormalGET /cases/{caseId}/comments
comments:writenormalPOST /cases/{caseId}/comments, PUT /comments/{commentId}
reference-data:readnormalGET /locations, GET /locations/{locationCode}, GET /case-handlers, GET /users, GET /users/{username}, GET /insurances, GET /insurances/{id}, GET /legal-insurances, GET /legal-insurances/{id}
reference-data:writeerhöhtPOST /insurances, PUT /insurances/{id}zusätzlich Operator-Konto nötig
extraction:executeprivilegiertNoch nicht verfügbar — der Scope existiert im Katalog, aber die API hat aktuell keinen Extraktions-Endpunkt

Warum attachments:read als „erhöht" gilt, obwohl es nur liest: Anhänge enthalten Vollmachten, Gutachten und Rechnungen — also die personenbezogensten Daten eines Falls.

Warum reference-data:write teurer ist als jeder andere Schreib-Scope: der Versicherungskatalog ist plattformweite Stammdatenhaltung ohne Mandantenschranke. Ein Schreibzugriff dort wirkt sofort für alle Mandanten — anders als jeder mandantenlokale Schreibzugriff.

vorsicht
reference-data:write verlangt zwei Berechtigungen

Der Scope allein reicht nicht: die Person, an deren Freigabe der Schlüssel hängt, muss zusätzlich ein Operator-Konto sein (Mitgliedschaft in einem ADMIN-Standort) — dieselbe Schranke, die auch intern für den Versicherungskatalog gilt. Ein Schlüssel aus einem Partner-Mandanten bekommt hier 403 ACCESS_DENIED, selbst wenn der Scope im Ceiling und in der Freigabe steht. Ein Integrator, der den Katalog pflegen soll, muss deshalb an einem Operator-Konto hängen.

Scopes minimal wählen

Nimm für eine Leseanbindung cases:read + reference-data:read und ergänze nur, was du wirklich zurückschreibst. Ein Key, der nichts löschen können muss, braucht kein attachments:write.

Statuscodes und ihre Bedeutung

StatuscodeWann
401API_KEY_INVALIDKein/mehrfacher/falsch formatierter Header, unbekannte publicId, falsches Secret, Key abgelaufen/deaktiviert/widerrufen, Grant oder Mandanten-Policy nicht aktiv, Benutzer inaktiv oder nicht mehr im Mandanten
403ACCESS_DENIEDDer Key ist gültig, trägt aber den für den Endpunkt nötigen Scope nicht — oder ein echter Rechtefehler nach bestandener Sichtbarkeitsprüfung
404NOT_FOUNDRessource unbekannt oder fremder Mandant oder außerhalb der Sichtbarkeit der Person — bewusst ununterscheidbar
429RATE_LIMITED / API_RATE_LIMITEDRate-Limit erreicht (siehe unten)
503API_KEY_AUTH_UNAVAILABLEDie Authentifizierung selbst ist gestört (Datenbank oder Schlüsselkonfiguration)

401: eine Antwort für alle Ursachen

Alle oben genannten Ursachen liefern denselben Body und zusätzlich den Header WWW-Authenticate: Bearer realm="usp-api", error="invalid_token":

{ "code": "API_KEY_INVALID", "message": "API-Zugangsdaten sind ungültig" }

Das ist Absicht: Aus der Antwort lässt sich nicht ableiten, ob eine publicId existiert oder ob nur das Secret falsch war. Die interne Ursache steht ausschließlich im Server-Log (und im Audit-Trail des Mandanten). Bei einem 401 hilft dir GET /me mit einem bekannt guten Key sowie ein Blick in die Schlüsselverwaltung — nicht ein Rateversuch gegen die API.

Wichtig für dein Retry-Verhalten: 401 ist nie transient. Wiederhole nicht automatisch, sondern alarmiere — der Key ist tot oder falsch konfiguriert.

403 vs. 404: kein Existenz-Orakel

Die Trennung ist bewusst so gelegt:

  • 403 darfst du erfahren, denn es geht um deine eigene Key-Konfiguration: der Scope fehlt. Der Body ist { "code": "ACCESS_DENIED", "message": "Zugriff verweigert" }. Abhilfe: Key mit dem passenden Scope ausstellen lassen (oder Grant/Policy erweitern) — siehe Schlüsselverwaltung.
  • 404 verrät nichts. Ein Fall eines fremden Mandanten, eine unbekannte UUID und ein Fall außerhalb der Sichtbarkeit der Grant-Person liefern identisch { "code": "NOT_FOUND", "message": "Ressource nicht gefunden" } bzw. für Fälle "Fall nicht gefunden". Du kannst mit IDs also nicht herausfinden, was es anderswo gibt.

Ein 403 nach bestandener Sichtbarkeitsprüfung gibt es trotzdem in wenigen Fällen — etwa wenn ein PUT /cases/{caseId} den Fall auf einen Standort verschieben will, den die Grant-Person nicht bedienen darf. Dort wäre ein 404 irreführend, und ein Orakel entsteht nicht: den Fall durftest du ohnehin schon sehen.

429: Rate-Limits

Es gibt zwei Stufen. Beide antworten mit Retry-After (Sekunden bis zum Ende des laufenden Minutenfensters).

StufeGreiftStandardwertcode
Pro IP, vor der Authentifizierungjeder Request an /api/external/v1/*300 Anfragen/MinuteRATE_LIMITED
Pro Key, nach der Authentifizierungerfolgreich authentifizierte Requests120 Anfragen/MinuteAPI_RATE_LIMITED
Pro Benutzer, nach der Authentifizierungalle Keys derselben Person zusammen300 Anfragen/MinuteAPI_RATE_LIMITED
Pro Mandant, nach der Authentifizierungalle Keys des Mandanten zusammen1000 Anfragen/MinuteAPI_RATE_LIMITED

Die Werte sind Deployment-Konfiguration (platform.api-keys.rateLimitPerMinute, …rate-limit-key-per-minute, …rate-limit-user-per-minute, …rate-limit-tenant-per-minute) — frag im Zweifel nach den Werten der Zielumgebung, statt sie zu erraten.

Alle Zähler laufen als festes Minutenfenster (kein gleitendes): der Zähler springt zum Minutenwechsel auf null. Zusätzliche Keys für dieselbe Person erhöhen die Benutzerquote nicht.

Beide Rate-Limit-Antworten tragen neben code und message das Feld retryAfterSeconds:

{ "code": "API_RATE_LIMITED", "message": "Zu viele Anfragen. Bitte versuche es in Kürze erneut.", "retryAfterSeconds": 37 }

Richte deinen Client so ein, dass er Retry-After respektiert und danach mit Backoff weitermacht. 429 ist der einzige 4xx, den du automatisch wiederholen darfst.

503: Auth ist gestört, nicht dein Key

{ "code": "API_KEY_AUTH_UNAVAILABLE", "message": "Die API-Authentifizierung ist vorübergehend nicht verfügbar" }

Kommt bei Datenbank-Ausfall oder fehlender Schlüsselkonfiguration auf dem Server. Bewusst nicht als 401 getarnt, damit du den Unterschied zwischen „mein Key ist kaputt" und „die Plattform ist kaputt" siehst. Behandle es wie ein 5xx: Backoff und erneut versuchen.