---
title: Claim-Flow (service_auth & anonym)
description: Wie ein Agent aus einer Registrierung ein nur lesendes, kontogebundenes Zugriffstoken macht, indem ein angemeldeter Nutzer ihn autorisiert.
---

# Claim-Flow

Ein **Claim** bindet die Registrierung eines Agenten an die Organisation eines angemeldeten
Nutzers (oder an einen einzelnen Kunden darin) und gewährt ein **nur lesendes** (`api.read`)
Zugriffstoken. Agenten erhalten dabei nie Schreibzugriff. Es gibt zwei Wege zu einem Claim:

- **`service_auth`** startet die Claim-Zeremonie sofort und liefert den `user_code` direkt an
  den aufrufenden Agenten zurück (eine vertrauenswürdige Integration im Device-Flow-Stil).
- Eine **`anonymous`**-Registrierung (siehe [Agent-Authentifizierung](/de/api/agent-auth))
  ist sofort nutzbar und kann *optional* später per `POST /agent/identity/claim`
  hochgestuft werden: der `user_code` wird dem Agenten auf diesem Weg nie zurückgegeben; nur
  der angemeldete Nutzer sieht ihn.

## 1. Mit `service_auth` registrieren

`POST https://uptimeify.io/agent/identity`

| Feld | Typ | Erforderlich | Beschreibung |
| --- | --- | --- | --- |
| `type` | string | ja | `"service_auth"` |
| `login_hint` | string | ja | E-Mail des Nutzers, der den Agenten autorisieren wird. Muss ein `@` enthalten. |

```bash
curl -X POST https://uptimeify.io/agent/identity \
  -H 'Content-Type: application/json' \
  -d '{"type":"service_auth","login_hint":"user@deinkunde.com"}'
```

```json
{
  "registration_id": "reg_…",
  "claim": {
    "user_code": "WDJB-MJHT",
    "verification_uri": "https://uptimeify.io/claim",
    "expires_in": 600,
    "interval": 5
  },
  "claim_token": "clm_…",
  "post_claim_scopes": ["api.read"]
}
```

Zeige `verification_uri` und `user_code` deinem Nutzer an (ein Prompt im Stil des
RFC-8628-Device-Flows). Die Registrierung startet mit `status: pending` und erzeugt kein
Zugriffstoken, bis die Zeremonie abgeschlossen ist. Poll dazu mit dem `claim_token` in
Schritt 3 unten.

`user_code` und `claim_token` laufen beide nach `expires_in` Sekunden ab (600s / 10 Minuten);
rufe erneut `POST /agent/identity` auf, um eine neue Zeremonie zu starten, falls die Zeit
abläuft.

## 2. Oder: eine bestehende `anonymous`-Registrierung claimen

`POST https://uptimeify.io/agent/identity/claim`

| Feld | Typ | Erforderlich | Beschreibung |
| --- | --- | --- | --- |
| `claim_token` | string | ja | Das `clm_…` aus einem früheren Aufruf von `POST /agent/identity {"type":"anonymous"}`. |
| `email` | string | ja | E-Mail des Nutzers, der den Claim autorisieren wird. Muss ein `@` enthalten. |

```bash
curl -X POST https://uptimeify.io/agent/identity/claim \
  -H 'Content-Type: application/json' \
  -d '{"claim_token":"clm_…","email":"user@deinkunde.com"}'
```

```json
{
  "verification_uri": "https://uptimeify.io/claim?claim_attempt=cat_…",
  "claim_attempt_token": "cat_…",
  "expires_in": 600,
  "interval": 5
}
```

Beachte, was **fehlt**: `user_code`. Auf diesem Weg erfährt der Agent den Code nie, nur der
angemeldete, an die E-Mail gebundene Nutzer sieht ihn, auf der `/claim`-Seite. Die
Registrierung selbst bleibt während der ganzen Zeremonie `active` und funktioniert
durchgehend mit ihrem `tools.public`-Scope weiter: das Claimen fügt nur `api.read` hinzu, es
entzieht dem anonymen Zugriff während des laufenden Vorgangs nichts. Ein erneuter Aufruf
dieses Endpunkts vor Ablauf überschreibt Code/Attempt-Token und setzt das 10-Minuten-Fenster
zurück.

Eine Registrierung kann nur einmal geclaimt werden: Ein zweiter Aufruf von
`POST /agent/identity/claim` (oder ein `service_auth`-Claim, der immer schon vorab gebunden
ist) nach erfolgter Bestätigung liefert `409 claimed_or_in_flight`.

## 3. Menschliche Zustimmung unter `/claim`

Der gebundene Nutzer muss unter genau dem oben verwendeten `login_hint`/`email` bei Uptimeify
angemeldet sein und entweder den vom Agenten angezeigten `user_code` eintippen oder den
`verification_uri`-Link öffnen (der beim anonymen Weg `?claim_attempt=…` trägt). Die
Uptimeify-Web-App löst das über zwei sitzungscookie-authentifizierte Endpunkte zum
Bestätigungsbildschirm auf: diese ruft der Agent nicht selbst auf, sie sind hier nur der
Vollständigkeit halber dokumentiert:

- `GET /api/agent-claim/context?claim_attempt=<token>`: liefert für den angemeldeten,
  gebundenen Nutzer `{ registration_id, agent_type, scope, bound_email, user_code,
  expires_at }`, damit die UI zeigen kann, was autorisiert wird (und beim anonymen Weg den
  Code vorausfüllen kann).
- `POST /api/agent-claim/confirm { user_code }`: der angemeldete Nutzer sendet den Code zur
  Autorisierung. Bei Erfolg: `{ ok: true, registration_id }`.

Die Bestätigung bindet die Registrierung an die Organisation des Nutzers, oder, wenn dessen
Rolle ihn auf genau einen Kunden beschränkt, an diesen einen Kunden. Ein `admin` oder ein
uneingeschränkter `editor` bindet die gesamte Organisation; ein `readonly`- (oder
`editor`-)Nutzer, der auf genau einen Kunden beschränkt ist, bindet diesen Kunden; jeder
andere Fall (kein als einzelner Scope darstellbarer Kunde, z. B. eine Rolle `responder`, oder
ein `readonly`-Nutzer mit mehreren zugewiesenen Kunden) lässt die Bestätigung fehlschlagen:
der Agent sieht dann `expired_token`/`invalid_claim_token`, statt jemals ein zu breites oder
mehrdeutiges Token zu erhalten.

## 4. Auf Abschluss pollen

`POST https://uptimeify.io/oauth2/token` (`application/x-www-form-urlencoded`)

| Feld | Erforderlich | Beschreibung |
| --- | --- | --- |
| `grant_type` | ja | `urn:uptimeify:agent-auth:grant-type:claim` |
| `claim_token` | ja | Das `clm_…` aus Schritt 1 (bzw. die ursprüngliche anonyme Registrierung aus Schritt 2). |

```bash
curl -X POST https://uptimeify.io/oauth2/token \
  --data-urlencode 'grant_type=urn:uptimeify:agent-auth:grant-type:claim' \
  --data-urlencode 'claim_token=clm_…'
```

Poll nicht schneller als das `interval` aus Schritt 1/2 (5 Sekunden). Jeder Poll liefert
eines von:

| Status | HTTP | `error` | Bedeutung |
| --- | --- | --- | --- |
| Noch nicht bestätigt | 400 | `authorization_pending` | Weiter im `interval` pollen. |
| Zu schnell gepollt | 400 | `slow_down` | Drossle dich: du hast schneller als `interval` gepollt. |
| Token/Registrierung weg | 400 | `expired_token` | Der Claim-Token ist unbekannt, seine Registrierung wurde widerrufen/ist abgelaufen, das äußere 24h-Fenster ist verstrichen, oder er wurde bereits durch einen früheren Poll eingelöst. |
| Bestätigt | 200 | - | Erfolg: siehe unten. |

Bei Erfolg:

```json
{
  "access_token": "wsma_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "api.read",
  "identity_assertion": "eyJ…"
}
```

Der `claim_token` ist einmal verwendbar: Er wird beim ersten erfolgreichen Poll atomar
verbraucht, sodass niemals zwei gleichzeitige/wiederholte Poller beide ein Token erzeugen
können. Das Einlösen widerruft in derselben Alles-oder-nichts-Operation auch alle vorherigen
`tools.public`-Zugriffstokens derselben Registrierung: die Vorab-Tokens einer anonymen
Registrierung funktionieren nach dem Claim nicht mehr.

Speichere die zurückgegebene `identity_assertion` (ein EdDSA-JWT mit rund 24 Stunden
Gültigkeit, wie im Basis-Flow) und tausche sie erneut über den Grant
`urn:ietf:params:oauth:grant-type:jwt-bearer` (siehe
[Agent-Authentifizierung](/de/api/agent-auth#gegen-ein-zugriffstoken-tauschen)), um nach
Ablauf frische `api.read`-Zugriffstokens zu erzeugen. Die Claim-Zeremonie musst du dafür
nicht wiederholen.

## 5. Das Token verwenden (nur lesend)

Das `wsma_`-Zugriffstoken aus einem abgeschlossenen Claim ist nur lesend und auf die
Organisation (bzw. den einzelnen Kunden) des autorisierenden Nutzers begrenzt:
`GET /api/websites*`, `GET /api/incidents*` und `GET /api/health`. Jeder Schreibzugriff oder
jede Anfrage außerhalb dieser Allowlist liefert `403` mit `data.code = agentTokenReadOnly`.

## Noch nicht verfügbar

`identity_assertion` als Registrierungs-`type` (ID-JAG, womit eine bereits vertraute externe
OIDC-Identität ohne jede Claim-Zeremonie direkt ein Token erzeugen könnte) ist **noch nicht**
aktiviert. Verlasse dich nicht darauf; `POST /agent/identity {"type":"identity_assertion"}`
liefert derzeit `400 issuer_not_enabled`.

## Häufige Fehler

| HTTP | `error` / `data.code` | Wo | Bedeutung |
| --- | --- | --- | --- |
| 400 | `service_auth_not_enabled` | `POST /agent/identity` | `service_auth` ist auf diesem Deployment nicht aktiviert. |
| 400 | `invalid_request` | `POST /agent/identity`, `POST /agent/identity/claim`, `/api/agent-claim/*` | Fehlendes/ungültiges `login_hint`, `claim_token`, `email` oder `user_code`. |
| 400 | `invalid_claim_token` | `POST /agent/identity/claim` | Unbekannter Claim-Token, oder die Registrierung des Tokens ist nicht `anonymous`, oder sie wurde widerrufen. |
| 404 | `invalid_claim_token` | `GET /api/agent-claim/context` | Der `claim_attempt`-Token ist unbekannt/abgelaufen, oder der angemeldete Nutzer ist nicht die gebundene E-Mail, beides ununterscheidbar (fail-closed). |
| 400 | `invalid_claim_token` | `POST /api/agent-claim/confirm` | Der `user_code` ist falsch/abgelaufen, oder der Zugriff des bestätigenden Nutzers lässt sich nicht auf einen einzelnen Org-/Kunden-Scope auflösen. |
| 409 | `claimed_or_in_flight` | `POST /agent/identity/claim` | Die Registrierung ist bereits geclaimt (`organizationId` ist schon gesetzt). |
| 400 | `claim_expired` | `POST /agent/identity/claim` | Das äußere 24h-Claim-Fenster (ab Erstellung der Registrierung) ist verstrichen. |
| 400 | `authorization_pending` | `POST /oauth2/token` (Claim-Grant) | Der Nutzer hat noch nicht bestätigt: weiter pollen. |
| 400 | `slow_down` | `POST /oauth2/token` (Claim-Grant) | Schneller als `interval` (5s) gepollt, drossle dich. |
| 400 | `expired_token` | `POST /oauth2/token` (Claim-Grant) | Der Claim-Token/die Registrierung ist abgelaufen, wurde widerrufen oder bereits eingelöst. |
| 403 | `agentTokenReadOnly` | Jeder `/api/*`-Aufruf mit `wsma_`-Token | Schreibversuch, oder ein Pfad außerhalb der nur-lesenden Allowlist des Tokens. |
| 429 | - | Jeder Endpunkt oben | Rate-Limit überschritten (siehe [Agent-Authentifizierung](/de/api/agent-auth#häufige-fehler)). |

Die vollständige, kanonische Liste findest du in der
[Fehlercode-Referenz](/de/api/error-codes-and-known-pitfalls#agent-auth-fehlercodes).
