<!-- markdown mirror of https://mintid.net/en/sdk/accept-accountable-agents — generated at build time -->

> For a business that lets its own customers’ agents in and keeps every other agent out: connecting an agent by its pairwise pseudonym, the cap as your own ledger, revocation seen as a refused presentation, the x402 middleware beside the draft OAuth bridge, and how it compares with a platform’s own agent profiles.

index — 25 documents

1.  [overviewsdk/00](/en/sdk)
2.  [getting startedsdk/01](/en/sdk/getting-started)
3.  [python sdksdk/02](/en/sdk/python)
4.  [service modesdk/03](/en/sdk/service-mode)
5.  [typescript clientsdk/04](/en/sdk/typescript-client)
6.  [mcp serversdk/04a](/en/sdk/mcp-server)
7.  [conformancesdk/05](/en/sdk/conformance)
8.  [reason codessdk/06](/en/sdk/reason-codes)
9.  [substrate readssdk/07](/en/sdk/substrate-reads)
10.  [credentials & holderssdk/08](/en/sdk/credentials-and-holders)
11.  [holder · wasmsdk/holder](/en/sdk/holder)
12.  [agent clientssdk/15](/en/sdk/agent-clients)
13.  [agent-to-agent handshakesdk/14](/en/sdk/agent-to-agent-handshake)
14.  [payment identitysdk/09](/en/sdk/payment-identity-profile)
15.  [becoming a verifiersdk/10](/en/sdk/becoming-a-verifier)
16.  [protocol statesdk/11](/en/sdk/protocol-state)
17.  [verify a releasesdk/18](/en/sdk/verify-a-release)
18.  [browser checksdk/19](/en/sdk/check-a-presentation-in-the-browser)
19.  [principal control surfacesdk/21](/en/sdk/principal-control-surface)
20.  [accept accountable agentssdk/22](/en/sdk/accept-accountable-agents)
     *   [your customers’ agents, nobody else’s](#overview)
     *   [who this is for](#who-this-is-for)
     *   [what you learn, and what you never learn](#what-you-learn)
     *   [the pattern in five steps](#the-pattern)
     *   [the sketch](#the-sketch)
     *   [two transports](#two-transports)
     *   [sending refused agents to an operator](#sending-refused-agents)
     *   [what not to keep](#what-not-to-keep)
     *   [the honest limits](#honest-limits)
     *   [beside per-platform agent profiles](#platform-agent-profiles)
     *   [where to go next](#where-to-go-next)
21.  [verifier admissions logsdk/log](/en/sdk/verifier-admissions-log)
22.  [record · assurance vocabularyrecord/01](/en/sdk/kyc-trust-model)
23.  [record · packagingrecord/02](/en/sdk/packaging)
24.  [record · multi-languagerecord/03](/en/sdk/multi-language-clients)
25.  [record · service contractrecord/04](/en/sdk/verifier-service-contract)

SDK docs · sdk/22

# Accept accountable agents

For a business that lets its own customers’ agents in and keeps every other agent out: connecting an agent by its pairwise pseudonym, the cap as your own ledger, revocation seen as a refused presentation, the x402 middleware beside the draft OAuth bridge, and how it compares with a platform’s own agent profiles.

Your customers are starting to send software agents to your service: to read their data, to place orders, to call your API. From a request alone, your service cannot tell who answers for the agent behind it. This page shows how a business lets in **its own customers' agents**, each with a limit and an approval rule the business chooses, and keeps every other agent out — without ever learning who stands behind an agent, and without building an identity system of its own.

It describes a pattern built from parts that exist: your own verifier service, the agent credential a person mints for an agent (a **bounded agent**), and the agent's **pairwise pseudonym**. Nothing on this page is a new protocol.

## Who this is for

A business — a retailer, an API or data operator, a marketplace, later a bank — that wants:

*   **in:** agents its own signed-in customers connect to their accounts, within a cap and an approval rule per agent;
*   **out:** every agent that no accountable principal stands behind, and every agent whose principal has switched it off.

You operate a verifier: a service at your own registered web origin, signing with your own registered request key, that sends agents verification requests (**challenges**) and decides whether to accept their answers (**presentations**). That is the relying-party role of the [overview](/en/sdk); registration is on [becoming a verifier](/en/sdk/becoming-a-verifier), and what is and is not self-service today on [protocol state](/en/sdk/protocol-state). An operator may run the service and its node for you, under the conditions set out for [operated deployments](/en/sdk#operated-deployments): the decision stays at your registered origin and under your request key, and you keep the operator account that controls that key. Verification is free: nothing in this pattern is charged to a relying party.

## What you learn, and what you never learn

Each accepted presentation tells you, for this request only:

*   an **accountable principal** stands behind the agent: a principal an accepted issuer verified, at the assurance grade you asked for (A1–A4) or above;
*   the agent is **delegated to deal with you**: its permissions (its scope) cover the action you ask for, your business as counterparty, and the category and region you ask for, with a per-transaction limit at least as high as the amount you name, and its time limit (its lease) has not expired;
*   the agent's credential is **not revoked** under the issuer's current status;
*   the agent's **pairwise pseudonym** at your service: a value that is the same every time this agent comes back to you, and unrelated to the value any other relying party sees, or to that of any other agent of the same principal.

You never learn who the principal is, which other agents the principal runs, or anything about the agent's dealings with any other business. Presentations never reach the chain and your decision record is your own, never published, so nothing MintID publishes tells anyone that the agent presented to you. The only party able to connect an agent to its person is the issuer, in its private records; MintID itself issues nothing, certifies nothing and verifies nobody.

## The pattern in five steps

### 1\. Connect an agent

The customer signs in to your service as usual and chooses "connect an agent". Your service asks your verifier for a challenge carrying your **policy**: the backing grade you require, your counterparty, the action, category and region you serve, the per-transaction limit you need proven, and the two entries every agent policy carries — the pairwise pseudonym naming your own verifier id, and `unexpired`. A preset builds that policy for agents of a named bundle (`agentPolicyPreset` in TypeScript, `policy_preset` in Python; see [bounded agents](/en/sdk/agent-clients#bounded-agents)).

The agent fetches the challenge itself, inside the customer's signed-in connection (a one-time link or code your application hands to the agent), because it has **10 seconds** from the challenge to answer it. Its client checks your challenge's signature against your registered request key, proves the policy or refuses it before sending anything, and your verifier decides.

### 2\. Store the enrolment

On acceptance, store one record per connected agent (beside this section):

*   `customer_id` is your own account identifier, from your own sign-in.
*   `pseudonym` is `verified.pairwise_pseudonym` from the decision (96 hex characters).
*   `name` is the label the customer gives the agent.
*   `cap` and `approval_rule` are yours: for example, a spending limit per month, and "approve automatically up to the cap, ask me above it".

The link between the agent and the customer is **yours**: it is the association you made because the agent answered your challenge inside that customer's signed-in connection. The presentation proves that some accountable principal stands behind the agent, not that the principal is the customer signed in; MintID has no principal identifier to compare, by design.

### 3\. Every request carries a fresh presentation

Each later request from the agent carries a new 10-second presentation of the same policy, or rides on a short session minted from one ([below](#two-transports)). Your service checks the decision, then looks the pseudonym up in your enrolments. A pseudonym you do not know is not connected to any account: treat it as you treat any unknown client. A refused presentation lets nothing through, whatever the pseudonym.

### 4\. The ledger and the cap are yours

The credential proves a **per-transaction** limit ("at least the amount you named"). A cumulative cap — so much per day, per month — is not something the credential can prove across relying parties: within one relying party it is that party's own accounting. So your service keeps the ledger, keyed by the pseudonym, adds each accepted operation, and applies the customer's `approval_rule`: through on its own, or held until the customer approves in your application.

### 5\. Revocation reaches you as a refused presentation

The principal can switch the agent off at the source: with the holder's kill switch (holder self-revocation), or by asking the issuer, which revokes it (issuer-side revocation, for example from the issuer's [principal control surface](/en/sdk/principal-control-surface)). The issuer's operator can also revoke it. No notice is sent to you: from the issuer's status update (its **status root**) that carries the revocation, the agent's presentations are refused by every relying party, yours included.

A revocation is not instant. It takes effect at the next status root, and every conforming verifier refuses the revoked agent within the published bound of the path it was revoked by, on the single-node production chain and on the public testnet:

Path

Every verifier refuses by

holder self-revocation

245 s after the chain records the self-revocation

issuer-side revocation

185 s after the issuer’s removal

issuer emergency root

within one block

Before the bound there is no promise either way: a presentation accepted in that interval is a conforming decision. In Round 1 of the external test round on the public testnet, the first refusals came 11.0 s after an issuer revocation and 33.8 s after a self-revocation — observations of one run, not a guarantee ([the revocation trace](/en/developers#trace)).

What you see is a refused presentation, usually with the code `status_root_stale`. One refusal does not tell you the agent was revoked: an honest agent is also refused that way when the issuer publishes a new status root while the agent is proving, and it then presents once more and is accepted. Once the bound has passed, a revoked agent is never accepted again; if the principal mints a replacement, it comes with a new pseudonym and is connected as a new agent. You need no revocation feed: mark the enrolment inactive when the customer removes it in your application, or when the agent has not been accepted for as long as you choose.

recordsdk/22 · the enrolment

```
{ customer_id, pseudonym, name, cap, approval_rule }
```

## The sketch

Your backend, in TypeScript, against your own verifier service. The ledger, the cap and the approval rule are placeholders for your code.

The agent's side is the [agent client](/en/sdk/agent-clients): `refresh()` until usable, optionally `canSatisfy(policy)` before any challenge, then `present`.

typescriptsdk/22 · verbatim

```
import { VerifierClient, agentPolicyPreset } from "@mintid-net/verifier-client";

const verifier = new VerifierClient(MY_VERIFIER_SERVICE_URL);   // yours, never a third party's
const claim_policy = agentPolicyPreset("data-read", {
  verifier_id: MY_VERIFIER_ID,          // 64 lowercase hex: the pseudonym entry names it
  counterparty: MY_COUNTERPARTY,        // your canonical counterparty form, one the bundle grants
  geo: "iso3166-1:ES",                  // a region of the bundle
  min_limit: "5000000 USDC",            // the per-transaction limit you need proven (5 USDC)
});

// Step 1 and every request: issue a challenge, hand it to the agent unmodified,
// validate its answer.
const challenge = await verifier.createChallenge({ profile: "agent_v1", claim_policy });
// … the agent answers with agent.present(challenge) and returns the envelope …
const decision = await verifier.validatePresentation({ request: challenge.request, envelope });

if (!decision.accepted) refuse(decision.reason_code);           // a closed vocabulary
const pseudonym = decision.verified!.pairwise_pseudonym!;
// Step 2 (connect):  enrolments.insert({ customer_id, pseudonym, name, cap, approval_rule })
// Steps 3–4 (later): const e = enrolments.byPseudonym(pseudonym); ledger.apply(e, amount)
```

## Two transports

The pattern above talks to your verifier service directly through its frozen contract. For two common shapes of service there is a front that does part of it for you.

x402 middleware — paid routes

OAuth token-exchange bridge — ordinary APIs

Status

**built**, Python and Node (x402 vendor middleware)

**draft**, adapter contract 0.1.0; the subject-token type and the scope names are provisional (OAuth token exchange bridge)

What it does

asks for identity in the same `402` that asks for payment, and lets a paid request through once your service has accepted an answer for your policy

turns an accepted presentation into a short-lived, scoped access token at your own authorisation server; your APIs go on checking tokens

The pseudonym

reaches your handler with every accepted request, with the recognition record while recognition is on

**not carried**: the token's `sub` is random for each exchange, and the pseudonym enters no token in 0.1.0

Between presentations

a session that ends as soon as the status root it was decided under is replaced (every 30 seconds on the single-node production chain), so a revocation stops a session as fast as it stops a fresh presentation

the token lives until it expires (300 seconds by default) and is checked by signature, audience, scope, expiry and the client's DPoP key, not against the status root

Payment

not settled by it: your x402 payment middleware runs after it

none

So, today:

*   **For paid routes**, the middleware gives you the whole pattern: the pseudonym for your enrolments and ledger, and revocation at the speed of a fresh answer.
*   **For ordinary APIs**, the draft bridge in 0.1.0 can gate an API on the proven predicates, but it cannot key your ledger: the token carries no pseudonym, so your APIs cannot tell one connected agent from another; making the pseudonym the token's subject is left for a later version. Until then, the connect step and every operation counted against a cap call your verifier service directly, as in the sketch. If you use the draft bridge (0.1.0), keep its token lifetime short, because a token issued before a revocation remains valid until it expires.

## Sending refused agents to an operator

Being added in this release: an optional **onboarding object** your side attaches to a refusal — beside the identity requirement in the x402 refusal, in the error body of the draft OAuth bridge (0.1.0), and in the MCP server's refusal result. You configure it on your verifier: the operator's name, the URL where a person can verify and obtain a bounded agent for your service, the profiles you accept and a closed reason. The agent clients surface it to the person behind the agent, so a door that refuses also says where to go. Until that release ships, a refusal carries its reason code only.

## What not to keep

*   **Keep:** the enrolment record above, your ledger, and the decision record your verifier service keeps for you (a closed, minimal schema).
*   **Never keep:** presentations, envelopes, proof bytes or claim values. Do not log the challenge's request body next to the pseudonym.
*   **Never join** the pseudonym with the decision log (session id, context digest): the service writes them apart so that its log alone cannot link two sessions of one agent; keep them apart in your systems too.
*   **Keep the pseudonym inside your systems.** It is useful to nobody else — no other party sees the same value — and it may be personal data where you operate.

## The honest limits

*   **Cumulative caps are your accounting.** The credential proves a per-transaction limit; a limit over a period exists only in your ledger, and only at your service. An agent with several relying parties is capped by each of them separately.
*   **The pseudonym lasts one credential.** A renewal is a replacement credential with a new pseudonym, so after a renewal the customer connects the agent again. One principal can run several agents: a pseudonym supports a reputation, not a claim that a person is unique.
*   **The recognition store.** Your verifier service keeps, by default, a record per pseudonym — first seen, last seen and a count, nothing else — for 90 days after the last sighting, apart from the decision log. A stored pseudonym may be personal data of the agent's principal where you operate: record why you keep it, keep it as short as your use allows, or turn recognition off; the pseudonym still arrives in every decision ([service mode](/en/sdk/service-mode)). Your enrolment table is a second store of the same identifier, under the same obligations.
*   **Ten seconds.** A presentation must reach your service within 10 seconds of the challenge; the window is a constant. Measured on the public testnet, an agent took 4.3 to 5.8 seconds on average from witness refresh to decision. An honest agent whose root rolls over during that interval is refused — roughly one presentation in six for a five-second interval with roots every 30 seconds — and succeeds on its one retry ([reason codes](/en/sdk/reason-codes)). Keep your verifier service beside its node ([latency](/en/sdk/service-mode)).
*   **In the browser.** Where a person's own holder proves in a browser, an identity presentation takes about 0.6 seconds on a high-end laptop and about 2.5 seconds with the CPU slowed four times (MintID's proxy for a mid-range laptop; no physical mid-range device has been measured yet); the package measures the device before asking for an approval ([credentials and holders](/en/sdk/credentials-and-holders)).
*   **Revocation has a window.** Up to the published bound of its path, a revoked agent may still be accepted. If you act later on something an agent asked earlier, ask for a new presentation before you act.
*   **No principal comparison.** You cannot check that an agent's principal is your signed-in customer; the link is the one your connect step makes.

## Beside per-platform agent profiles

Some large platforms are building agent access of their own: one profile per agent inside the customer's account, approved per purchase or under a spending cap, and every other agent blocked. The pattern on this page has the same shape at your service, and differs in what stands behind the agent:

A platform's own agent profile

A bounded agent connected at your service

One record per agent in the customer’s account

yes

yes — your enrolment, keyed by the pseudonym

Cap and approval rule

the platform’s

yours, from your ledger

Who stands behind the agent

the platform’s own customer, as that platform checked them

an accountable principal an accepted issuer verified, at a grade you require — never named to you

What the agent may do

the platform’s settings

proven in each presentation: action, you as counterparty, category, region, per-transaction limit, an unexpired lease

Across services

one platform only

the same agent at every relying party its scope names, under a different, unlinkable pseudonym at each

Switching it off

in that platform’s settings

by the principal at the source (the kill switch) or by the issuer, refused at every relying party within the published bound; and by deleting your enrolment

Agents nobody stands behind

blocked by the platform

never pass: they hold no credential an accepted issuer minted for a verified principal

What configuration runs, what the agent did

—

not proven: a presentation proves who answers for the agent, never provenance (which configuration runs) or runtime evidence (which calls it made)

Payment

the platform’s checkout

not MintID’s: your payment scheme, after the identity step

## Where to go next

1.  [Agent clients](/en/sdk/agent-clients) — the agent's side: refresh, `can_satisfy`, presenting, bounded agents and their presets.
2.  x402 vendor middleware — the pattern on paid routes.
3.  OAuth token exchange bridge — the draft (0.1.0) front for ordinary APIs, and what it does not yet carry.
4.  [Service mode](/en/sdk/service-mode) — your verifier service, its recognition store and its retention.
5.  [Reason codes](/en/sdk/reason-codes) — what a refusal says.
6.  [The public testnet and its KYC sandbox](/en/developers) — where to try the pattern end to end, and the revocation bounds as measured. The single-node production chain is never a place to test.

[previous← principal control surface](/en/sdk/principal-control-surface)[nextverifier admissions log →](/en/sdk/verifier-admissions-log)

---
Source: https://mintid.net/en/sdk/accept-accountable-agents · Accept accountable agents · MintID SDK docs
