Granting an AI agent access is the easy half. The half that matters during an incident is taking it away: the agent is looping on a paid endpoint, a prompt injection has it emailing the wrong people, or you just stopped trusting the tool that runs it. In that moment you need one action that stops it at the service in question, and you need it to work now, not when a token expires next week.
This post covers why revocation is harder for agents than for human sessions, the tradeoffs between the usual designs, and the nonce-based approach Notlogin uses for its per-vendor credentials.
Why agent revocation is harder than logging someone out
When a human logs out, the service deletes a session row and the next request fails. Agent access rarely looks like that. An agent usually holds some combination of:
- Long-lived API keys it created or was handed, stored in a config file or environment variable.
- OAuth tokens from several providers, each with its own lifetime and revocation endpoint.
- Signed credentials that a service can check against a public key without calling anyone.
Every one of these is a bearer artifact: whoever holds it can use it. Agents also copy things around more than people do — into logs, tool configs, sub-agents, and sometimes the context window itself, which is the path described in credential theft via prompt injection. So you cannot assume revoking means "get the secret back". You have to make the secret stop working wherever it went.
Three ways to design revocation
Short expiry and no revocation
Issue credentials that live minutes, not months, and never revoke anything: let them expire. This is simple and needs no lookup, but the agent has to come back for a fresh credential constantly, and between issuance and expiry you have no kill switch at all. For a credential that also carries a spending budget, "it will stop in fifteen minutes" is not a good answer.
Revocation lists
Publish a list of revoked credential IDs and have services download it periodically. This keeps verification mostly offline, but revocation is only as fast as the slowest service's refresh interval, and the list only grows. It works when the set of verifiers is small and their polling is predictable.
Online status check on every use
Have the service ask the issuer "is this still valid?" at verification time. Revocation becomes effective on the next request, at the cost of a network round-trip and a dependency on the issuer being up. This is the model OAuth token introspection (RFC 7662) uses.
There is no free option. The design question is which of these you make the default, and how you let services trade freshness for latency when they need to.
The nonce-based design
Notlogin issues each credential for exactly one vendor. The credential carries the vendor slug, scope, USDC budget, expiry and the human's proof transcript, all covered by a Notlogin Ed25519 signature (see verifiable credentials for AI agents for the full shape). Alongside those fields, every credential has a nonce: a random 32-byte value generated at issuance and included in the signed payload.
The nonce does three jobs:
- It is the lookup key. The broker stores it with a uniqueness constraint, so given a credential it can find exactly one record and its current state, without trusting anything else the presenter claims.
- It cannot be swapped. Because the nonce sits inside the signed payload, an attacker cannot take a revoked credential and attach a fresh nonce to it. Changing it breaks the signature.
- It ties revocation and budget to the same row. Spending recorded against the credential and its revocation status live on the same record, so "is this still allowed?" and "how much is left?" are one question.
Revoking is a state change on that record. The human opens the credentials dashboard, picks the vendor, and revokes. Nothing has to be pushed to the agent and nothing has to be recovered from it. The credential the agent holds is still a perfectly valid signature — it just no longer refers to an active grant.
What the vendor does
On the vendor side, verifyCredential from notlogin-sdk runs in online mode by default. It checks the signature against the broker's published keys, confirms the credential is addressed to that vendor, checks expiry and the vendor's requiredProofs locally, and then posts to the broker's verify endpoint, which looks the credential up by its nonce and reports revocation and remaining budget.
import { verifyCredential } from 'notlogin-sdk'
const result = await verifyCredential(req.body.vcJson, {
vendorSlug: 'your-slug',
requiredProofs: ['email'],
// mode defaults to 'online': revocation + budget are checked on every call
})
if (!result.valid) {
return res.status(403).json({ reason: result.reason })
}
There is also an offline mode: local signature, expiry and proof checks only, sub-millisecond once keys are cached, but with no revocation check. The SDK documents it for one case only — you verified the same credential online within the last few seconds and want to skip a round-trip. That gives a vendor a clear, bounded window instead of an open-ended one.
The gap most designs miss: derived credentials
A common vendor pattern is: verify the agent's credential once, then mint your own API key for it. That key is now a second bearer artifact that the broker knows nothing about. Revoking at the broker does not reach it.
If you are a vendor, close that gap deliberately:
- Re-verify on sensitive actions. Before anything that costs money or touches user data, run the online check again. A revoked grant fails there even if your API key is still alive.
- Tie your key's lifetime to the grant. Store the credential ID next to the key you minted, and treat a failed re-verification as a reason to disable the key.
- Respect the credential's expiry. Don't issue a key that outlives the grant it came from.
This is the same idea as the confused deputy problem: any credential you derive from a delegation should be no stronger, and no longer-lived, than the delegation itself.
Why per-vendor scoping makes revocation precise
Because each credential names one vendor, revocation is surgical. Revoking the agent's access to a search API does not touch its access to an email provider. Compare that with a single master token the agent uses everywhere: revoking it is all-or-nothing, so in practice people hesitate to pull it.
Per-vendor grants also mean the human has one place to see every service an agent can reach and cut any of them off — the gap we described in OAuth for AI agents, where each vendor keeps its own tokens, consent screen and revocation endpoint.
Revocation and money
For credentials that carry a budget, revocation is also a spending stop. Notlogin's agent payment route verifies the credential's signature, expiry and revocation before it settles anything, and refuses payments beyond the remaining budget. A revoked credential cannot authorize another payment, regardless of what the agent still holds. Budgets, revocation and expiry are independent limits; you want all three. More on the payment side in how agents pay with x402.
A short checklist
- Give every credential a random, signed, unique identifier and look state up by it.
- Default verifiers to an online check; make offline verification an explicit, short-window optimization.
- Scope credentials to one service so revoking one does not mean revoking all.
- Make vendor-derived keys depend on the original grant.
- Put the revoke button where the human already manages the agent, and make it take effect on the next request.
If you run agents and want a single place to see and revoke every vendor they can reach, start with Notlogin. If you run a service and want agent credentials whose revocation you can check on every request, register as a vendor.