Audit stream & endpoints

Audit — stream=delegation

Every event is sealed in the server’s tamper-evident hash chain and pushed to matching webhook
subscriptions (server ≥ 1.23 revocation push). Event types:

Event When
iam.delegation.agent.registered Agent created (admin, DCR, or auth.md path)
iam.delegation.agent.{approved|suspended|resumed|retired|rejected} Lifecycle transitions, with the acting admin
iam.delegation.grant.created Consent verified, grant stored — cites grant_id, scopes, purpose, consent_aal, *_confirmation_id
iam.delegation.grant.revoked By the user or an admin — cites who
iam.delegation.exchange.issued Successful RFC 8693 exchange — user, agent, scopes, grant_id
iam.delegation.exchange.refused Refused exchange with the detailed reason (the client only gets the RFC error; the why lives here). v1.1 adds two budget reasons: delegation_budget_unenforceable (budgeted grant, no DelegationBudgetGuard bound) and delegation_budget_exhausted: <reason>
iam.delegation.elevation.requested JIT elevation request opened — cites grant_id, the extra scopes, the reason
iam.delegation.elevation.notified / notify_failed Out-of-band notifier outcome (best-effort: a failed delivery never cancels the request; the error is in metadata)
iam.delegation.elevation.approved / denied The human’s decision — approval cites the re-consent consent_confirmation_id + AAL
iam.delegation.freeze.applied Delegation frozen by one admin — cites scope, reason, and the required_quorum photographed at that moment
iam.delegation.freeze.lift_approved One approval toward lifting. Recorded for every approval, duplicates included: who wanted the agents running again is answered with the full list, not with whoever signed last
iam.delegation.freeze.lifted Quorum reached, delegation resumed — cites the approval count and who cast the last one
iam.delegation.receipt.issued A signed action receipt was minted — cites the action, outcome, PDP decision and the payload digest (not the JWS: the digest is what keeps the receipt probative after key rotation, and copying the JWS would double the data without adding a guarantee)
iam.delegation.receipt.refused A minting attempt was refused, with the reason the client never sees
Metadata naming rule

Audit metadata never uses keys containing the substring token — the server’s admin APIs redact by
substring. Use grant_id, consent_confirmation_id. This is why the claim is pds_dgr, not
*_token_id.

Query it like any server audit: GET /api/iam/v1/audit?stream=delegation — or the Delegation
audit
page in laravel-iam-console ≥ 1.2.

Admin API (server admin prefix, e.g. /api/iam/v1)

Same middleware stack as the server (iam.admin_auth / iam.can / iam.idempotency). Permission
slugs: iam:agents.manage, iam:delegations.manage, iam:decisions.check.

Route Does
GET/POST /agents · GET /agents/{id} List/create/inspect agents (filters: status, operator)
POST /agents/{id}/approve The human gate: pastes public JWKS, creates the OAuth client (confidential, private_key_jwt, token-exchange only), activates
POST /agents/{id}/suspend · /retire Kill-switch / terminal retirement
GET /delegation-grants Org-wide grant search — each row carries budget and its live pending_elevations (id, extra scopes, reason, expiry): the admin SEES a pending authority ask, the delegating user stays the only one who decides
POST /delegation-grants/{id}/revoke Admin revocation
GET /delegation-freezes · GET /delegation-freezes/{id} Active freezes (add include_lifted=1 for history), each with its approvals and how many are still missing
POST /delegation-freezes Freeze. One admin, no approval, immediate. reason required. Permission: iam:delegations.manage
POST /delegation-freezes/{id}/approve-lift One approval toward lifting; lifts when the quorum of distinct admins is met. Permission: iam:delegations.unfreeze — a slug of its own, granted to fewer people
POST /decisions/check-delegated The intersection decision endpoint for remote PEPs

Self-service (iam/me/delegations, host-app guard)

Route Does
GET / “My delegations”: agent, scopes, purpose, status, expiry, consent AAL, budget — plus pending_elevations (v1.1)
GET /receipts “What did my agents do”: signed action receipts, each with its own JWS to export and verify against the public JWKS
POST /consent-challenge Step 1: open the bound consent challenge (optional budget joins the binding)
POST / Step 2: verify + create the grant (one-shot confirmation)
DELETE /{grantId} One-click revoke — no step-up, ever
POST /elevations/{id}/consent-challenge JIT elevation, step 1: step-up bound to the extra scopes
POST /elevations/{id}/approve JIT elevation, step 2: verify + extend the grant (one-shot)
POST /elevations/{id}/deny One click — denying never requires step-up

Agent surface (authenticated by the delegated token itself)

Route Does
POST /iam/agent/receipts Mints a signed action receipt. The Authorization: Bearer delegated token is the credential; sub/act/pds_dgr come from it, never from the body. Refusals are always the generic receipt_not_issued

Discovery (public when the module is enabled)

Route Does
GET /.well-known/agent-auth.json The agent_auth block (auth.md interop: identity endpoint, ID-JAG support, events endpoint)
GET /AUTH.md The procedural recipe file for agent onboarding
GET /capabilities (server core) Lists agents when the module is enabled — consoles gate their UI on this, no 409-probing