KYC verifies a person at one point in time. Agent Passport verifies the whole chain of agency behind an action — which agent, under whose mandate, backed by which KYC'd principal, carrying whose liability — so platforms can accept agent traffic instead of blocking it as fraud.
An API request arrives that wants to move real value — refill an account, place an order, subscribe to a fund. No compliance regime today can resolve who committed this action, so the safe default is to reject it as fraud. Legitimate, well-funded agent volume gets thrown away at the door.
The request looks like traffic from a person, but it isn't. Nothing on the wire distinguishes the two.
If it's an agent, who runs it? An anonymous key is not a counterparty you can transact with.
Was it actually allowed to do this — this action, this amount, on this platform, right now?
If it goes wrong, who bears it? Who do you dispute with, screen for sanctions, and hold accountable?
KYC verified a point — one identity, once. The passport verifies the chain — every action resolved to a scoped mandate and a KYC'd principal who carries the liability. That's the difference between blocking agents and banking them.
Principals onboard once and hand out narrow, revocable authority. Agents sign every request. Platforms verify the chain with a single call that fails closed.
A human or entity passes KYC once, registers an agent's public key, and grants a narrow mandate: this action, these caps, this platform, expiring — and revocable in seconds.
prn_ → mnd_ · signed grantThe agent SDK (or MCP server in Claude Code) holds the keypair and signs a per-request assertion — action, amount, platform, nonce, timestamp — attaching it as an X-Passport header.
agt_ key · JWS · nonce + tsOne middleware line calls /v1/verify. The passport checks signature, mandate, scope, live revocation, period counters, KYC and sanctions — then returns allow/deny with the resolved chain and a signed receipt.
plt_ → vrf_ · allow / deny · co-signableDrop one middleware in front of the routes agents call. It fails closed. Anonymous traffic gets a 403 KYA required with an onboarding link — so the requests you used to reject become qualified leads.
import { requireKYA } from "@passport/verify";
// Accept agent traffic — one round trip, fails closed.
app.post(
"/v1/account/refill",
requireKYA({ action: "account.refill" }), // ← the integration
async (c) => {
const { chain } = c.get("kya"); // resolved chain
await accounts.refill(c.req.valid("json"));
return c.json({ ok: true, principal: chain.principal });
}
);
POST /v1/account/refill HTTP/1.1
Host: northbank.sandbox
Content-Type: application/json
X-Passport: eyJhbGciOiJFZERTQSJ9.eyJtYW5kYXRl
X2lkIjoibW5kX3RyIiwiYWN0aW9uIjoi…
// agent key signs this assertion, per request
{ "mandate_id": "mnd_tr…", "action": "account.refill",
"amount": 50000, "currency": "USD",
"platform_id": "plt_northbank",
"nonce": "3f9a…", "ts": "2026-07-22T18:04:11Z" }
The middleware never executes the action for anonymous agents. Instead it returns a structured block with a link to onboard — the block-page that turns rejected traffic into a lead.
treasury-bot holds a mandate: account.refill ≤ $1,000/tx, ≤ $2,500/week at Northbank. Watch the passport allow what's in scope, deny what isn't, react to a live revocation, and turn away an anonymous caller.
ok · receipt vrf_9c1… signed
remaining this week $2,000.00
per_tx_cap · mandate cap $1,000 / tx
ok
remaining this week $1,100.00
ok
remaining this week $200.00
period_cap · only $200.00 left this week
mandate_revoked · live revocation check
Every ALLOW returns the full resolved chain and a JWS receipt. After executing, the platform can countersign the verification — a record signed by both sides is nearly incontestable.
A mandate is the narrowest possible authority: one principal, one agent, specific actions with hard caps, an expiry, and a live status. Platforms read the attributes they need — never the identity documents behind them.
{
"id": "mnd_tr7…",
"principal": "prn_a1…",
"agent": "agt_9f…",
"scopes": [{
"action": "account.refill",
"max_amount_per_tx": 100000,
"max_amount_per_period": 250000,
"period": "week",
"currency": "USD",
"platforms": ["plt_northbank"],
"purpose": "trading-capital"
}],
"not_before": "2026-07-22T00:00:00Z",
"expires_at": "2026-08-22T00:00:00Z",
"status": "active",
"issuer_sig": "<JWS by passport issuance key>"
}
Per-transaction and cumulative-per-period ceilings in minor units. The passport maintains the counters and enforces them server-side.
Authority is valid only inside a window. Outside it, the passport denies before any other check runs.
One click in the principal dashboard. Verification is a live check, so the next request denies within seconds — no waiting on token expiry.
Platforms see KYC level, country, and an accreditation flag — never the underlying identity documents, unless the mandate grants it or a lawful request compels it.
Records, not rails. We attest decisions; we never custody or move value.
x402 · AP2 · ACP interop. AP2 mandates map to ours 1:1 — the KYA layer any rail can call.
No custody, no money transmission. KYC through licensed vendors; attestation only.
FCRA-shaped by design. Access, correction, and dispute flows are first-class — regulation as moat.
W3C VC / JWS migration. Token format tracks the standard; a migration is a serializer change.
The sandbox is live right now: the passport service runs at passport‑production‑f136.up.railway.app and the KYA‑gated Northbank brokerage at northbank‑production.up.railway.app — try an anonymous POST /api/refill and get the 403. Or spin it all up locally, issue a mandate, and watch the chain of agency resolve — allow, deny, revoke, block — in one command.