Skip to content

live/stable

The gate

Put a check in front of an endpoint with @recensus/kya — refuse by flag or tier, or price by tier, in Hono, Express or Python.

@recensus/kya puts a check in front of an endpoint. It finds out who is asking, checks them, and refuses or passes the request before your handler runs. It is built from the agent lane and has no x402 dependency, so it works in front of any x402 seller.

npm i @recensus/kya

Hono

import { createKya, kyaGate, kyaPaywall } from '@recensus/kya';

const kya = createKya({ apiKey: process.env.RECENSUS_KEY }); // apiUrl?, cacheSeconds = 60

await kya.check('erc8004:42');                                  // the check's answer

app.use('/paid/*', kyaGate(kya, { minTier: 'bronze' }));        // refuse below a tier
app.use('/paid/*', kyaGate(kya, { refuseUnbonded: true, refuseFlags: true }));
// downstream: c.get('kya') is the answer

Express

import { createKya, kyaGateExpress, kyaPrice } from '@recensus/kya';

const kya = createKya({ apiKey: process.env.RECENSUS_KEY });
app.use('/paid', kyaGateExpress(kya, { refuseFlags: true }));
// downstream: req.kya is the answer

// For another x402 seller: a price function by tier.
const price = kyaPrice(kya, { gold: '0.0005', unbonded: '0.002' });

Python (Starlette, FastAPI)

The gate is in recensus-sdk, module recensus_sdk.kya: KyaClient with check, KyaGate, ASGI middleware with the same options and codes, and price_for(answer, prices) for price by tier. It reads the payment headers and leaves the payment itself to your x402 library.

from recensus_sdk.kya import KyaClient, KyaGate, price_for

kya = KyaClient(api_key=os.environ["RECENSUS_KEY"])
app.add_middleware(KyaGate, client=kya, refuse_flags=True, min_tier="bronze")

Who is asking

  • the payer of an x402 payment in the request: PAYMENT-SIGNATURE (v2) or X-PAYMENT (v1), payload.authorization.from; a labelled nonce names the agent ID too;
  • or an agent-lane signature (the lane's headers, verified locally exactly as the lane does).

With neither, the gate passes the request on, so your x402 middleware can answer 402; requireIdentity: true refuses it instead.

The decision

In this order:

  1. check the subject (cached cacheSeconds, answers and misses alike; concurrent checks of one subject share one request);
  2. not an agent → KYA_NOT_AN_AGENT, unless allowNonAgents;
  3. a flag in refuseFlags (true means every flag) → KYA_FLAGGED;
  4. unbonded with refuseUnbonded → KYA_UNBONDED;
  5. a tier below minTier → KYA_TIER_TOO_LOW.

Each refusal is 403 with { error, detail, check }. When the KYA API cannot be reached the gate answers 503 KYA_UNAVAILABLE, or passes the request with failOpen: true. Until bonds are live on mainnet every agent there is unbonded, so minTier and refuseUnbonded refuse every mainnet agent today.

The trust rule

A payer named in a payment header is a claim until the payment's signature is verified, which your x402 middleware (or its facilitator) does before anything is served. A header naming someone else's wallet therefore buys a refusal or a failed payment, never access. That is why the gate runs before your x402 middleware, and why kyaPaywall verifies before it serves.

Price by tier

kyaPaywall is a minimal x402 v2 exact seller that charges by tier:

  • no payment → 402 quoting the price for the tier of whoever the request identifies (an agent-lane signature or a Recensus-Payer hint), else the unbonded price;
  • a payment → the gate's checks, then the amount must be at least the price for the payer's actual tier (KYA_PRICE_TOO_LOW, 402 with the right quote), then verify the payment, serve, settle.

A tier with no price takes the next lower tier's. The Recensus API's own pay per check is this paywall.

app.use('/paid/*', kyaPaywall(kya, { payTo, prices: { gold: '0.0005', unbonded: '0.002' }, refuseFlags: true }));

Codes

KYA_NOT_AN_AGENT · KYA_FLAGGED · KYA_UNBONDED · KYA_TIER_TOO_LOW · KYA_PRICE_TOO_LOW · KYA_UNAVAILABLE

Example

examples/kya-seller/ in the repository is a Hono endpoint behind kyaPaywall, with a README. Payments are x402 exact in USDG on eip155:4663, or eip155:46630 for Robinhood Chain testnet.