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
Local validation or introspection
Section titled “Local validation or introspection”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.
Choosing which calls to introspect
Section titled “Choosing which calls to introspect”Mark a route high-risk on the tool server:
// 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.
How to revoke
Section titled “How to revoke”| 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.
POST /oauth2/revoke
Section titled “POST /oauth2/revoke”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
jtionly. - 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 versus blocking
Section titled “Revoking versus blocking”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.
Repeating a kill switch
Section titled “Repeating a kill switch”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.
What revocation does not stop
Section titled “What revocation does not stop”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.
In the ledger
Section titled “In the ledger”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.
© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.