The central configuration object for Key0. Pass it wrapped in a Key0Config object to any framework integration to stand up payment-gated endpoints:
Express — key0Router({ config, adapter, store, seenTxStore })
Hono — key0App({ config, adapter, store, seenTxStore })
Fastify — fastify.register(key0Plugin, { config, adapter, store, seenTxStore })
Factory — createKey0({ config, adapter, store, seenTxStore })
import type { SellerConfig } from "@key0ai/key0" ;
Identity
Fields that populate the A2A agent discovery card.
Field Type Required Default Description agentNamestringYes — Display name shown in the agent card. agentDescriptionstringYes — Short description of the agent’s capabilities. agentUrlstringYes — Public base URL of your server (used for endpoint discovery). providerNamestringYes — Organization or developer name. providerUrlstringYes — Organization URL. versionstringNo "1.0.0"Semantic version exposed in the agent card.
Payment
Field Type Required Default Description walletAddress`0x${string}`Yes — Address that receives USDC payments. network"testnet" | "mainnet"Yes — "testnet" targets Base Sepolia (chain 84532). "mainnet" targets Base (chain 8453).
Product Catalog
Field Type Required Default Description plansreadonly Plan[]No — Subscription-style pricing plans. See Plan below. routesreadonly Route[]No — Pay-per-call route catalog. Use this for per-request pricing. See Route below.
Challenge
Field Type Required Default Description challengeTTLSecondsnumberNo 900How long (in seconds) a challenge remains valid before expiring.
Credential Issuance
Field Type Required Default Description fetchResourceCredentials(params: IssueTokenParams) => Promise<TokenIssuanceResult>Yes — Called after on-chain payment is verified for subscription plans. Return a token (JWT, API key, etc.) that grants the buyer access. Pay-per-call routes use fetchResource or proxyTo instead. See IssueTokenParams and TokenIssuanceResult . tokenIssueTimeoutMsnumberNo 15000Maximum time (ms) to wait for fetchResourceCredentials before timing out. tokenIssueRetriesnumberNo 2Number of retries on transient failures within fetchResourceCredentials.
Per-Request Proxy (Standalone Gateway)
Enable standalone gateway mode for top-level routes. In standalone mode, clients call the route directly, Key0 returns a 402 challenge for paid routes, and then proxies the request to your backend after payment. Subscription plans still use POST /x402/access.
Only one of fetchResource or proxyTo is needed. Use proxyTo for simple URL forwarding; use fetchResource when you need full control over the request.
Field Type Required Default Description fetchResource(params: FetchResourceParams) => Promise<FetchResourceResult>No — Called after settlement for pay-per-call routes. Fetch and return the backend response. See FetchResourceParams and FetchResourceResult . proxyToProxyToConfigNo — Shorthand: automatically builds a fetchResource that forwards to baseUrl. Supports optional headers (e.g. a shared secret so your backend can reject requests that bypass the gateway) and pathRewrite. See ProxyToConfig .
Lifecycle Hooks
Optional callbacks fired during the challenge lifecycle. Both are fire-and-forget — failures are logged but do not affect the payment flow.
Field Type Required Default Description onPaymentReceived(grant: AccessGrant) => Promise<void>No — Fired after a payment is verified and credentials are issued. onChallengeExpired(challengeId: string) => Promise<void>No — Fired when a challenge passes its TTL without payment.
MCP
Field Type Required Default Description mcpbooleanNo falseWhen true, mounts MCP discovery (/.well-known/mcp.json) and Streamable HTTP (POST /mcp) endpoints alongside A2A routes.
Customization
Field Type Required Default Description basePathstringNo "/a2a"Prefix for all A2A endpoints. resourceEndpointTemplatestringNo auto-generated URL template containing {resourceId} that appears in the access grant. Example: "https://api.example.com/photos/{resourceId}".
Settlement
Control how on-chain settlement is performed. By default, settlement is routed through the Coinbase Developer Platform facilitator.
Field Type Required Default Description gasWalletPrivateKey`0x${string}`No — Provide a private key to enable self-contained gas wallet settlement instead of the facilitator. facilitatorUrlstringNo CDP default Override the default facilitator URL from CHAIN_CONFIGS. redisIRedisLockClientNo — Redis client for distributed locking when using gas wallet settlement across multiple instances. Without this, an in-process serial queue is used (single-instance only).
Supporting Types
Plan
Defines a pricing tier that buyers can select.
type Plan = {
readonly planId : string ;
readonly unitAmount : string ; // e.g. "$0.10"
readonly description ?: string ;
};
Field Type Required Default Description planIdstringYes — Unique identifier for this plan. unitAmountstringYes — Dollar-formatted price string (e.g. "$0.10", "$5.00"). descriptionstringNo — Human-readable label for the plan.
Route
type Route = {
readonly routeId : string ;
readonly method : "GET" | "POST" | "PUT" | "DELETE" | "PATCH" ;
readonly path : string ;
readonly unitAmount ?: string ;
readonly description ?: string ;
};
Field Type Required Description routeIdstringYes Unique identifier for this route. method"GET" | "POST" | "PUT" | "DELETE" | "PATCH"Yes HTTP method for the route. pathstringYes Route path including any Express-style parameters (for example "/api/weather/:city"). unitAmountstringNo Dollar-formatted price string. Omit for free routes. descriptionstringNo Human-readable description of the route.
ProxyToConfig
type ProxyToConfig = {
readonly baseUrl : string ;
readonly headers ?: Record < string , string >;
readonly pathRewrite ?: ( path : string ) => string ;
};
Field Type Required Description baseUrlstringYes Base URL of your backend service (e.g. "https://api.internal"). headersRecord<string, string>No Extra headers injected on every proxied request. Use this to pass a shared secret so your backend can verify that requests came through the gateway. pathRewrite(path: string) => stringNo Optional function to rewrite the path before forwarding.
FetchResourceParams
Passed to your fetchResource callback after on-chain settlement.
type FetchResourceParams = {
readonly paymentInfo : PaymentInfo ;
readonly method : string ;
readonly path : string ;
readonly headers : Record < string , string >;
readonly body ?: unknown ;
};
Field Type Description paymentInfoPaymentInfoPayment metadata — txHash, payer, planId, amount, challengeId. methodstringHTTP method of the original request. pathstringPath of the original request (e.g. "/api/weather/london"). headersRecord<string, string>Forwarded request headers. bodyunknownRequest body, if any.
FetchResourceResult
Returned by your fetchResource callback.
type FetchResourceResult = {
readonly status : number ;
readonly body : unknown ;
readonly headers ?: Record < string , string >;
};
Field Type Required Description statusnumberYes HTTP status code from the backend. bodyunknownYes Response body to forward to the client. headersRecord<string, string>No Response headers to forward.
PaymentInfo
Available in req.key0Payment (embedded mode) and FetchResourceParams.paymentInfo (standalone mode).
type PaymentInfo = {
readonly txHash : `0x ${ string } ` ;
readonly payer ?: string ;
readonly planId : string ;
readonly amount : string ;
readonly method : string ;
readonly path : string ;
readonly challengeId : string ;
};
Field Type Description txHash`0x${string}`On-chain transaction hash of the USDC transfer. payerstringPaying wallet address, when available from the on-chain event. planIdstringPlan that was charged. amountstringAmount charged (e.g. "$0.01"). methodstringHTTP method of the gated request. pathstringRoute path of the gated request. challengeIdstringInternal challenge ID — use for audit trails and refund lookups.
IssueTokenParams
Passed to fetchResourceCredentials after payment verification.
type IssueTokenParams = {
readonly requestId : string ;
readonly challengeId : string ;
readonly resourceId : string ;
readonly planId : string ;
readonly txHash : string ;
};
Field Type Description requestIdstringOriginal access request ID. challengeIdstringChallenge that was fulfilled. resourceIdstringResource the buyer is purchasing access to. planIdstringSelected plan from the catalog. txHashstringOn-chain transaction hash of the USDC transfer.
TokenIssuanceResult
Returned from fetchResourceCredentials.
type TokenIssuanceResult = {
readonly token : string ;
readonly tokenType ?: string ; // Default "Bearer"
};
Field Type Required Description tokenstringYes The credential (JWT, API key, etc.) the buyer will use to access the resource. tokenTypestringNo Token scheme. Defaults to "Bearer".
Minimal Example
A working configuration with only the required fields:
import { key0Router } from "@key0ai/key0/express" ;
import { X402Adapter , RedisChallengeStore , RedisSeenTxStore } from "@key0ai/key0" ;
import Redis from "ioredis" ;
const redis = new Redis ( process . env . REDIS_URL ! );
const config = {
agentName: "Photo API" ,
agentDescription: "High-resolution stock photos via A2A." ,
agentUrl: "https://photos.example.com" ,
providerName: "Example Inc." ,
providerUrl: "https://example.com" ,
walletAddress: "0x1234567890abcdef1234567890abcdef12345678" as `0x ${ string } ` ,
network: "mainnet" as const ,
plans: [
{ planId: "single" , unitAmount: "$0.10" },
],
async fetchResourceCredentials ({ resourceId , planId }) {
const token = await generateJwt ({ resourceId , planId });
return { token };
},
};
app . use (
key0Router ({
config ,
adapter: new X402Adapter ({ network: "mainnet" }),
store: new RedisChallengeStore ({ redis }),
seenTxStore: new RedisSeenTxStore ({ redis }),
}),
);
Full Example
All optional fields included:
import { key0Router } from "@key0ai/key0/express" ;
import Redis from "ioredis" ;
const redis = new Redis ( process . env . REDIS_URL );
const config = {
// Identity
agentName: "Photo API" ,
agentDescription: "High-resolution stock photos via A2A." ,
agentUrl: "https://photos.example.com" ,
providerName: "Example Inc." ,
providerUrl: "https://example.com" ,
version: "2.1.0" ,
// Payment
walletAddress: "0x1234567890abcdef1234567890abcdef12345678" as `0x ${ string } ` ,
network: "mainnet" as const ,
// Product catalog — subscription plans and pay-per-call routes can coexist
plans: [
{ planId: "single" , unitAmount: "$0.10" , description: "One photo download" },
{ planId: "bulk-50" , unitAmount: "$3.50" , description: "50 photo downloads" },
],
routes: [
{
routeId: "weather-query" ,
method: "GET" ,
path: "/api/weather/:city" ,
unitAmount: "$0.01" ,
},
],
// Challenge
challengeTTLSeconds: 600 ,
// Credential issuance
async fetchResourceCredentials ({ resourceId , planId , txHash }) {
const token = await generateJwt ({ resourceId , planId , txHash });
return { token , tokenType: "Bearer" };
},
tokenIssueTimeoutMs: 10_000 ,
tokenIssueRetries: 3 ,
// Lifecycle hooks
async onPaymentReceived ( grant ) {
await analytics . track ( "payment" , { challengeId: grant . challengeId });
},
async onChallengeExpired ( challengeId ) {
await analytics . track ( "challenge_expired" , { challengeId });
},
// MCP
mcp: true ,
// Customization
basePath: "/v1/a2a" ,
resourceEndpointTemplate: "https://photos.example.com/api/photos/{resourceId}" ,
// Settlement
gasWalletPrivateKey: process . env . GAS_WALLET_KEY as `0x ${ string } ` ,
facilitatorUrl: "https://custom-facilitator.example.com" ,
redis ,
};
app . use (
key0Router ({
config ,
adapter: new X402Adapter ({ network: "mainnet" }),
store: new RedisChallengeStore ({ redis }),
seenTxStore: new RedisSeenTxStore ({ redis }),
}),
);
Embedded Quickstart Get started with SellerConfig in your existing Express/Hono/Fastify app.
Factory createKey0() — the factory that wires SellerConfig into the full SDK.