Skip to content

Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.

Revocation

A tool server checks a token in one of two ways:

Local validation Introspection
How Checks signature, iss, aud and exp against cached keys Calls POST /oauth2/introspect on every request
Cost No call to Subact ID per request One round trip per request
Revocation takes effect When the token expires, at most max_token_ttl later At once

A short max_token_ttl (five minutes by default) keeps the window small for everything you validate locally.

Mark a route high-risk on the tool server:

examples/tool-server/express-app.ts, lines 27–35
// High-risk: the control plane is asked on every call, so a revoked task cannot comment once more.
app.post(
'/issues/:id/comments',
subactIdExpress(subactid, { scope: 'jira:comment', highRisk: true, requireActor: true }),
(request, response) => {
const claims = claimsOf(request as SubactIdRequest);
response.status(201).json({ issue: request.params.id, by: claims?.act?.sub, for: claims?.sub });
},
);

Or mark an audience high-risk on the agent’s registration:

{
"allowed_audiences": ["https://jira.internal", "https://db.internal"],
"high_risk_audiences": ["https://db.internal"]
}

Every token for that audience then carries:

{
"aud": "https://db.internal",
"introspect_required": true
}

The claim is present only when it is true. @subactid/server and @subactid/mcp introspect whenever they see it, with no tool server configuration. A tool server without an SDK must read introspect_required itself.

Setting Effect
high_risk_audiences on the registration Every token for that audience is introspected by any tool server using an SDK
highRisk on a route That route is introspected, even when the audience is not high-risk

Rule of thumb: if the action must not happen five minutes after you revoke, make it high-risk. Reads usually are not. Writes, deletions, payments and anything that leaves your systems usually are.

What How Effect
One token POST /oauth2/revoke with the token and the agent’s assertion That jti is revoked
One task DELETE /admin/tasks/{task_id} The task and its grant
Every task of an agent DELETE /admin/agents/{agent_id}/tasks All of that agent’s live tasks
An agent PATCH /admin/agents/{agent_id} with {"enabled": false} Refused on its next request; its live tokens introspect as inactive
Every task of a human DELETE /admin/sponsors/{sponsor_key}/tasks Their live tasks; they can start a new one at once
A human, until unblocked PUT /admin/sponsors/{sponsor_key}/block Their live tasks, and no new task until the block is lifted
A session ended at the identity provider The provider posts a logout token to POST /backchannel-logout The tasks that session started; the person can sign in again
A person deactivated or deleted The provider’s provisioning client writes to /scim/v2 Their live tasks, and no new task until they are reactivated
A person disabled at the provider The provider pushes a security event to POST /events Their live tasks, and no new task until the provider re-enables them

The admin endpoints need the admin API key. The last three need the receiver to be configured; see endpoints.

RFC 7009. Only the agent a token or grant was issued to can revoke it.

  • A task grant revokes its task.
  • A task token revokes that jti only.
  • Another agent’s token, or one that does not exist, gets the same empty 200, so the endpoint cannot be used to test which tokens are real. Subact ID records it as a denial.
  • Revoking twice has the same effect as revoking once.

Revoking ends what is running. Blocking also refuses new tasks until the block is lifted. Lifting a block never restores a revoked task.

Only the source that placed a block can lift it. DELETE /admin/sponsors/{sponsor_key}/block answers 409 for a block placed by SCIM or Shared Signals; clear those at the identity provider.

The sponsor key is the value of the claim named by SubactId:UpstreamIdp:SponsorKeyClaim, sub by default. How Subact ID learns about a person disabled at the identity provider without a signal depends on the sponsor check mode.

The admin kill switches are idempotent. A repeat answers 200 with revoked_tasks: 0 and writes no audit record.

A task past its expiry is not revoked by a kill switch. It is left to the expiry sweeper, which records task.expired within SubactId:Tasks:SweepInterval (a minute by default). So revoked_tasks can be lower than the number of tasks you expected, and DELETE /admin/tasks/{task_id} on such a task answers 200 with revoked_tasks: 0. No token can be issued for an expired task either way.

A token already in an agent’s hand and validated locally keeps working until it expires. Only introspection closes that gap.

Refresh stops at once: a revoked task gets access_denied. The longest anything survives a revocation is the life of the token already issued.

Every revocation writes audit records. task.revoked carries one of these reasons:

Reason Cause
operator_kill_switch DELETE /admin/tasks/…, /admin/agents/…/tasks or /admin/sponsors/…/tasks
client_revoked The agent revoked its own grant or token through POST /oauth2/revoke
sponsor_blocked PUT /admin/sponsors/{sponsor_key}/block
sponsor_logged_out A back-channel logout
scim_deactivated, scim_deleted A SCIM deactivation or delete
ssf_sessions_revoked, ssf_account_disabled, ssf_account_purged A Shared Signals event

Blocking and unblocking write sponsor.blocked and sponsor.unblocked. An accepted signal from the identity provider writes sponsor.signal. A single token revoked by jti writes token.revoked. A task that reaches its end writes task.expired, never a revocation.

Subact ID Pre-release. v0.1 is not out yet.

© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.

LegalTermsPrivacy