Uptimeify Docs
Agentic registration

Claim flow (service_auth & anonymous)

How an agent turns a registration into a read-only, account-scoped access token by having a signed-in user authorize it.

Claim flow

A claim binds an agent's registration to a signed-in user's organization (or a single customer within it), granting a read-only (api.read) access token. Agents never gain write access. There are two ways to reach a claim:

  • service_auth registration starts a claim ceremony immediately and returns the user_code directly to the calling agent (a trusted, device-flow-style integration).
  • An anonymous registration (see Agent authentication) is usable immediately and can optionally be upgraded later via POST /agent/identity/claim: the user_code is never returned to the agent on this path; only the signed-in user sees it.

1. Register with service_auth

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

FieldTypeRequiredDescription
typestringyes"service_auth"
login_hintstringyesEmail of the user who will authorize the agent. Must contain @.
curl -X POST https://uptimeify.io/agent/identity \
  -H 'Content-Type: application/json' \
  -d '{"type":"service_auth","login_hint":"user@deinkunde.com"}'
{
  "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"]
}

Display verification_uri and user_code to your user (an RFC 8628 device-flow-style prompt). The registration starts status: pending and mints no access token until the ceremony completes. Poll step 3 below with the claim_token.

user_code and claim_token both expire after expires_in seconds (600s / 10 minutes); call POST /agent/identity again to start a fresh ceremony if it lapses.

2. Or: claim an existing anonymous registration

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

FieldTypeRequiredDescription
claim_tokenstringyesThe clm_… returned from an earlier POST /agent/identity {"type":"anonymous"} call.
emailstringyesEmail of the user who will authorize the claim. Must contain @.
curl -X POST https://uptimeify.io/agent/identity/claim \
  -H 'Content-Type: application/json' \
  -d '{"claim_token":"clm_…","email":"user@deinkunde.com"}'
{
  "verification_uri": "https://uptimeify.io/claim?claim_attempt=cat_…",
  "claim_attempt_token": "cat_…",
  "expires_in": 600,
  "interval": 5
}

Note what is absent: user_code. On this path the agent never learns the code, only the signed-in, bound-email user sees it, on the /claim page. The registration itself stays active and keeps working with its tools.public scope throughout: claiming only adds api.read, it never revokes anonymous access while the ceremony is pending. Calling this endpoint again before it expires overwrites the prior code/attempt token and resets the 10-minute window.

A registration can only be claimed once: a second POST /agent/identity/claim (or a service_auth claim, which is always pre-bound) after it's already been confirmed returns 409 claimed_or_in_flight.

The bound user must be signed in to Uptimeify as the exact login_hint / email used above, then either type the user_code shown by the agent, or open the verification_uri link (which carries ?claim_attempt=… on the anonymous path). The Uptimeify web app resolves that into the confirmation screen via two session-cookie-authenticated endpoints: these are not called by the agent directly, they exist for completeness of the flow:

  • GET /api/agent-claim/context?claim_attempt=<token>: returns { registration_id, agent_type, scope, bound_email, user_code, expires_at } for the signed-in bound user, so the UI can show what's being authorized (and pre-fill the code on the anonymous path).
  • POST /api/agent-claim/confirm { user_code }: the signed-in user submits the code to authorize. On success: { ok: true, registration_id }.

Confirming binds the registration to the user's organization, or, if the user's role restricts them to exactly one customer, to that single customer. An admin or an unrestricted editor binds the whole organization; a readonly (or editor) user restricted to exactly one customer binds that customer; any other case (no customer representable as a single scope, e.g. a role of responder, or a readonly user assigned to several customers) fails the confirmation closed: the agent will see expired_token / invalid_claim_token rather than ever receiving an over-broad or ambiguous token.

4. Poll for completion

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

FieldRequiredDescription
grant_typeyesurn:uptimeify:agent-auth:grant-type:claim
claim_tokenyesThe clm_… from step 1 (or the original anonymous registration in step 2).
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 no faster than the interval from step 1/2 (5 seconds). Each poll returns one of:

StatusHTTPerrorMeaning
Not yet confirmed400authorization_pendingKeep polling at interval.
Polled too fast400slow_downBack off: you polled faster than interval.
Token/registration gone400expired_tokenThe claim token is unknown, its registration was revoked/expired, the outer 24h window elapsed, or it was already redeemed by a previous poll.
Confirmed200-Success: see below.

On success:

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

The claim_token is single-use: it is atomically consumed on the first successful poll, so concurrent/retried pollers can never both mint a token. Redeeming it also revokes any prior tools.public access tokens for the same registration in the same all-or-nothing operation: an anonymous registration's pre-claim tokens stop working once it's claimed.

Store the returned identity_assertion (a ~24h EdDSA JWT, same as the base flow) and re-exchange it via the urn:ietf:params:oauth:grant-type:jwt-bearer grant (see Agent authentication) to mint fresh api.read access tokens after this one expires. You do not need to repeat the claim ceremony.

5. Use the token (read-only)

The wsma_ access token from a completed claim is read-only and scoped to the authorizing user's organization or single customer: GET /api/websites*, GET /api/incidents*, and GET /api/health. Any write, or any request outside that allowlist, returns 403 with data.code = agentTokenReadOnly.

Not yet available

identity_assertion as a registration type (ID-JAG, letting an already-trusted external OIDC identity mint a token directly, without any claim ceremony) is not enabled yet. Do not rely on it; POST /agent/identity {"type":"identity_assertion"} currently returns 400 issuer_not_enabled.

Common errors

HTTPerror / data.codeWhereMeaning
400service_auth_not_enabledPOST /agent/identityservice_auth is not enabled on this deployment.
400invalid_requestPOST /agent/identity, POST /agent/identity/claim, /api/agent-claim/*Missing/invalid login_hint, claim_token, email, or user_code.
400invalid_claim_tokenPOST /agent/identity/claimUnknown claim token, or the token's registration is not anonymous, or it was revoked.
404invalid_claim_tokenGET /api/agent-claim/contextThe claim_attempt token is unknown/expired, or the signed-in user is not the bound email, indistinguishably (fails closed).
400invalid_claim_tokenPOST /api/agent-claim/confirmThe user_code is wrong/expired, or the confirming user's access can't be resolved to a single org/customer scope.
409claimed_or_in_flightPOST /agent/identity/claimThe registration is already claimed (organizationId is already set).
400claim_expiredPOST /agent/identity/claimThe outer 24h claim window (from registration creation) has elapsed.
400authorization_pendingPOST /oauth2/token (claim grant)The user has not confirmed yet: keep polling.
400slow_downPOST /oauth2/token (claim grant)Polling faster than interval (5s), back off.
400expired_tokenPOST /oauth2/token (claim grant)The claim token/registration expired, was revoked, or was already redeemed.
403agentTokenReadOnlyAny /api/* call with a wsma_ tokenWrite attempt, or a path outside the token's read-only allowlist.
429-Any endpoint aboveRate limit exceeded (see Agent authentication).

See the error codes reference for the full, canonical list.

On this page