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:

json
{
  "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

ts
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()}` })
  })
});
OptionMeaning
payToWallet address that receives settled funds, you.
networkx402 network id. Default "base" (mainnet USDC); "base-sepolia" for testnet.
asset / assetExtraToken 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.
maxTimeoutSecondsSeconds an agent has to complete payment. Default 300.

Helper: usdToAtomicUsdc("0.01") "10000" (USDC has 6 decimals).

The paying agent

ts
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"] }:

ts
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

ts
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.
This is what the demo does: the paid scenario pairs x402TestPayer() with testFacilitator() so a simulated USDC payment verifies, settles, and produces a transaction reference, the full protocol round-trip minus the blockchain.