Payments & 402
Charge decisions are settled by entitlement providers. Two ship with the SDK: publisher-issued API keys, and agentic micro-payments over the open x402 protocol, USDC settled directly to your wallet.
The 402 body
When a charge rule matches and no entitlement is satisfied, the SDK returns a 402 whose body tells the agent every acceptable way to pay. The x402 section follows the x402 v2 shape, so wallet-equipped agents can satisfy it automatically:
{
"error": "entitlement_required",
"effect": "charge",
"payment": { "amount": "0.01", "currency": "USD" },
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"asset": "0x…USDC",
"payTo": "0x2222222222222222222222222222222222222222",
"maxTimeoutSeconds": 300,
"extra": { "name": "USDC", "version": "2" }
}
]
}
x402 in one paragraph
x402 (Coinbase / x402 Foundation, Linux Foundation governed) revives HTTP 402: the server states its price; the agent retries the request with a PAYMENT-SIGNATURE header carrying a base64 x402 v2 payment payload, typically an EIP-3009 USDC transfer authorization. A facilitator verifies the payload and settles it on-chain to your payTo address. Chain interaction lives entirely in the facilitator; this SDK never implements the cryptocurrency protocol itself. The payload is validated against @x402/core schemas and bound to the exact resource, price, and recipient, never trusted from client input.
Configuring the x402 entitlement
import { x402Entitlement, httpFacilitator } from "@corri/sdk/server";
// Testnet (base-sepolia) via the free x402.org facilitator:
const x402 = x402Entitlement({
payTo: "0xYourWallet",
network: "base-sepolia",
facilitator: httpFacilitator({ url: "https://x402.org/facilitator" })
});
// Mainnet via the Coinbase CDP facilitator (bearer credentials per call):
const mainnet = x402Entitlement({
payTo: "0xYourWallet",
network: "base",
facilitator: httpFacilitator({
url: "https://api.cdp.coinbase.com/platform/v2/x402",
createAuthHeaders: async () => ({ Authorization: `Bearer ${await cdpToken()}` })
})
});
| Option | Meaning |
|---|---|
payTo | Wallet address that receives settled funds, you. |
network | x402 network id. Default "base" (mainnet USDC); "base-sepolia" for testnet. |
asset / assetExtra | Token contract override (+ EIP-712 domain info). Defaults to USDC on known networks. |
mode | "settle" (default): verify then settle before granting access. "verify-only": grant on verification, settle out of band. |
maxTimeoutSeconds | Seconds an agent has to complete payment. Default 300. |
Helper: usdToAtomicUsdc("0.01") → "10000" (USDC has 6 decimals).
The paying agent
import { createAgentClient, x402TestPayer } from "@corri/sdk/client";
const agent = createAgentClient({
identity: { agentId, keyId, privateKey },
defaultPurpose: "summarization",
entitlements: {
// Demo/test payer, fabricates an EIP-3009-shaped authorization,
// compatible only with the SDK's testFacilitator. Real agents plug a
// wallet-backed PaymentHandler (e.g. built on x402-fetch / viem).
payment: x402TestPayer()
}
});
// One call: 402 received → payment built → request retried → 200.
const res = await agent.fetch(url, { action: "read", purpose: "summarization" });
// Per-call opt-out: agent.fetch(url, { autoPay: false })
API keys
For partners you bill off-chain, accept publisher-issued keys instead of (or alongside) x402, the demo policy accepts either via entitlement: { type: "any", providers: ["api-key", "x402"] }:
import { apiKeyEntitlement, staticEntitlement } from "@corri/sdk/server";
// Production: resolve keys from your database.
const apiKeys = apiKeyEntitlement({
resolve: async (key) => db.grants.byKey(key), // ApiKeyGrant | null
header: "authorization" // default: Authorization: Bearer <key>
});
// Tests and local examples only:
const fixed = staticEntitlement({
keys: {
demo_pub_key_2026: {
resources: ["report:*"], actions: ["read"],
purposes: ["summarization"], reference: "grant:demo"
}
}
});
A grant scopes what a key may do, resources (with * wildcards), actions, optional purposes, an optional expiresAt, an optional principalId lock, and the reference recorded in receipts.
Testing without a chain
import { testFacilitator } from "@corri/sdk/server";
const facilitator = testFacilitator({ network: "base-sepolia" });
// In-process: verifies amount, recipient binding, and single-use nonces.
// After a settle: facilitator.settledReferences → ["tx:…"]
// No chain interaction. Never use in production.
x402TestPayer() with testFacilitator() so a simulated USDC payment verifies, settles, and produces a transaction reference, the full protocol round-trip minus the blockchain.