Integrate as a platform
Add one middleware in front of the routes agents call. It verifies the full chain of agency in one round trip and fails closed — so you can accept agent-originated volume you'd otherwise reject as fraud.
Install & get keys #
Create a platform (plt_…) in the platform dashboard and mint an API key. The key authenticates your server-to-server calls to /v1/verify. Then install the middleware:
npm i @passport/verify
The package ships adapters for Express, Hono, and Next.js route handlers. Set PASSPORT_API_KEY (and, for local dev, PASSPORT_BASE_URL=http://localhost:4000) in your environment.
The requireKYA middleware #
Guard a route by naming the action it performs. The middleware reads the X-Passport header, calls verify, and either attaches the resolved chain to the request or short-circuits with a deny / 403. Roughly ten lines to integrate:
import { requireKYA } from "@passport/verify";
// Accept agent traffic — one round trip, fails closed.
app.post(
"/v1/account/refill",
requireKYA({ action: "account.refill" }),
async (c) => {
const { chain, verification_id } = c.get("kya");
await accounts.refill(c.req.valid("json"));
return c.json({ ok: true, principal: chain.principal });
}
);
By default the middleware reads the transaction amount from the request body (amount, minor units) and binds it into the verify call, so the passport enforces per-tx and period caps against the real amount. Override with the amount resolver option below.
Options #
| Option | Type | Description |
|---|---|---|
| action | string | Required. The scope action this route performs, e.g. account.refill. |
| amount | (req) => number | Resolver for the amount in minor units. Defaults to body.amount. |
| currency | string | Expected currency. Defaults to body.currency or "USD". |
| onDeny | (res, decision) | Customize the deny response. Default returns 403 with the reason and onboarding link. |
| failOpen | boolean | Default false. The middleware fails closed; a passport outage denies rather than admits. |
The 403 "KYA required" block page #
When a request arrives with no X-Passport header — an anonymous agent — the middleware never runs your handler. It returns a structured 403 with an onboarding link, so rejected traffic becomes a lead instead of a dead end.
{
"error": "kya_required",
"message": "This action requires a verified agent passport.",
"action": "account.refill",
"onboarding_url": "https://northbank.sandbox/kya/start"
}
Point onboarding_url at your co-branded onboarding flow. The funnel: anonymous agent → 403 → onboard → mandate → verified buyer.
Verify API #
The middleware calls this for you, but here is the raw contract for custom integrations. One round trip.
Request — the raw agent assertion plus your platform context:
{
"assertion": "<JWS signed by the agent key>",
"platform_id": "plt_northbank",
"action": "account.refill",
"amount": 50000,
"currency": "USD"
}
Response — a decision, the resolved chain (attributes only), a verification id, and a signed receipt:
{
"decision": "allow",
"reason": "ok",
"chain": {
"principal": { "type": "individual", "country": "US",
"kyc": "verified", "accredited": true },
"agent": { "id": "agt_9f…", "name": "treasury-bot", "runtime": "claude-code" },
"mandate": { "id": "mnd_tr7…", "remaining_this_period": 150000 }
},
"verification_id": "vrf_9c1…",
"receipt": "<JWS signed by passport>"
}
You receive attributes — KYC level, country, an accreditation flag — never the principal's identity documents, unless the mandate grants disclosure or a lawful request compels it. Subject access, correction, and dispute flows are first-class and FCRA-shaped.
The check pipeline #
On every verify the passport runs, in order — first failure decides:
- Agent signature — the assertion verifies against the registered agent public key.
- Replay — timestamp within a ±120s window and the nonce is single-use.
- Mandate liveness — active, not expired, not revoked (a live check — revocation is instant).
- Scope — the mandate covers this action, amount, and platform.
- Period counters — cumulative spend this period stays under the cap (we maintain the counters).
- KYC status — the principal's KYC has not lapsed.
- Sanctions — screen passes (stub interface in the alpha).
Decision reasons #
allow always carries ok. Any other reason accompanies a deny. The list is open — treat unknown reasons as a deny.
Countersigning #
After you execute the action, countersign the verification. A record signed by both sides — the passport's receipt plus your platform key — is nearly incontestable. This is the evidence-layer law, on from day zero.
{
"outcome": "executed",
"platform_ref": "txn_88f2…",
"platform_sig": "<JWS signed by your platform key>"
}
// → { "id": "vrf_9c1…", "cosigned": true }
Audit export #
Every decision is an immutable, signed vrf_… record. Export the log for reconciliation, dispute handling, or compliance review:
The platform dashboard renders the same decision log with API-key management alongside it.