Qeet Docs
Platform

Agent Governance

First-class identities for AI agents / MCP clients — ephemeral tokens, a named human sponsor, Shadow-AI discovery, an AuthZEN PDP facade, and a tenant-wide kill-switch.

An agent is a first-class, non-human principal — the identity an AI agent or MCP client authenticates as, distinct from the users and service principals that already exist. Every capability on this page is designed around one rule: an agent is never unaccountable. It's short-lived, scoped, revocable, and always owned by a named human.

Also in the console

The Qeet ID admin console has a dedicated Agent Governance section (Developer → Agent Governance) covering everything below except Token Vault and CIBA, which are API-only today.

Create an agent

POST/v1/tenants/{tenantID}/agentsCreate an agent
request
{
  "name": "support-copilot",
  "scopes": ["tickets:read", "kb:read"],
  "token_ttl_seconds": 600,
  "sponsor_user_id": "…"
}

The response includes a secret, shown exactly once — there's no re-read endpoint. sponsor_user_id is required and must be an actual member of the tenant (see Sponsor model below).

GET/v1/tenants/{tenantID}/agentsList agents
PATCH/v1/tenants/{tenantID}/agents/{id}Suspend / resume an agent
DELETE/v1/tenants/{tenantID}/agents/{id}Decommission an agent

An agent moves through a small lifecycle — activesuspended (reversible) → decommissioned (terminal, excluded from listings).

Mint a token

POST/v1/agents/tokenMint an agent token

The agent authenticates with its id + secret and gets a short-lived, scoped access token marked actor_type="agent". Tokens are ephemeral by design — there's no refresh token; the agent re-mints when it needs a new one. Lifetime is clamped to 60s–3600s.

Tenant-wide kill-switch

If an agent (or a whole fleet) misbehaves, one call suspends every active agent in the tenant — their tokens stop working immediately on next verification.

POST/v1/tenants/{tenantID}/agents/kill-allSuspend all agents

Agent-as-Principal

An agent token self-describes as a non-human principal via claims rather than a separate sub namespace — actor_type="agent" plus agent_id on every issued token and on /oauth/introspect. (A sub-prefix convention was considered and rejected: it would break Token Exchange's subject-token parsing for a purely cosmetic gain.) Discovery advertises the principal types a client can expect to see:

GET /.well-known/openid-configuration
JSON
{
  "actor_types_supported": ["user", "service", "agent"]
}

Every agent has a named human sponsor — the accountable owner, validated as an actual tenant member at creation time. When a sponsor leaves the organization, their agents don't go orphaned:

GET/v1/tenants/{tenantID}/agents/sponsored-by/{userID}List a user's sponsored agents
POST/v1/tenants/{tenantID}/agents/sponsored-by/{userID}/transferTransfer sponsorship
request
JSON
{ "to_user_id": "…" }

Transfer reassigns every agent the departing sponsor owned in one call and returns the count moved. The console's sponsor-transfer tool previews that count before you confirm.

Shadow-AI discovery

Not every piece of automation goes through this registry on purpose — an OAuth client can pick up client_credentials or token-exchange without anyone registering it as a managed agent or service principal. Qeet ID flags those as Shadow-AI candidates, ranked by how many live (unexpired, unrevoked) refresh tokens they're actually holding:

GET/v1/tenants/{tenantID}/oidc/clients/shadow-aiList Shadow-AI candidates
POST/v1/tenants/{tenantID}/oidc/clients/{id}/reviewAcknowledge a candidate

Acknowledging a candidate doesn't change its permissions — it's an attestation that a human has looked at it, recorded with who and when.

AuthZEN — an external PDP facade

For a policy-enforcement point that would rather speak a standard protocol than Qeet ID's own /check shape — an MCP tool-call gateway, for instance — the OpenID AuthZEN core evaluation API fronts the same RBAC/ReBAC engines documented in Authorization:

POST/v1/tenants/{tenantID}/access/v1/evaluationAuthZEN evaluation
request — RBAC (resource.type = permission)
JSON
{
  "subject": { "type": "user", "id": "…" },
  "resource": { "type": "permission" },
  "action": { "name": "billing:write" }
}
request — ReBAC (any other resource.type)
JSON
{
  "subject": { "type": "user", "id": "…" },
  "resource": { "type": "document", "id": "readme" },
  "action": { "name": "editor" },
  "context": { "explain": true }
}

resource.type="permission" routes to RBAC (action.name is the permission key, resource.id is ignored); anything else routes to ReBAC (resource becomes "type:id", action.name becomes the relation). context.explain: true returns the same grant-path trace as each engine's own ?explain=true.

Token Vault & CIBA

Two more pieces of the governance surface, API-only today (no console UI yet):

  • Token Vault — a per-tenant encrypted store for third-party OAuth tokens (Slack/GitHub/Google/custom), so an agent holding a delegated token can reach the delegating user's own connected account without ever seeing the raw refresh token. POST /v1/tenants/{tenantID}/vault/providers, connect via GET .../vault/{provider}/connect, then GET .../vault/tokens/{provider} for an auto-refreshed access token.
  • CIBA (backchannel authentication) — the async, no-browser sibling of the Device Authorization Grant: a client resolves the user via login_hint and polls while an in-app notification collects consent. POST /v1/oauth/bc-authorize to start, POST /v1/oauth/token with grant_type=urn:openid:params:grant-type:ciba to poll.

Both are governed by the same primitives as everything else on this page — scoped, revocable, and attributable to a human — they just don't have a dedicated admin screen distinct from their API contract yet.

On this page