x402
Pay-per-call API access via x402
USDC on Base (CAIP-2 eip155:8453), EIP-3009 transferWithAuthorization, and HTTP payment headers per x402 v2.
How x402 works
x402 is an open payment protocol over HTTP that uses status code 402 Payment Required to challenge unpaid requests with machine-readable payment requirements. Clients construct an authorization for the exact asset amount, submit it in the PAYMENT-SIGNATURE header (v2), and the resource server verifies and settles through a facilitator before returning the resource. USDC on Base uses EIP-3009 transferWithAuthorization; typed signing follows EIP-712.
Stratalize verifies and settles through the Coinbase Developer Platform (CDP) x402 facilitator. Server wiring uses @coinbase/x402 createFacilitatorConfig with @x402/core/server (lib/x402/facilitator.ts). Protocol mechanics and facilitator error codes are defined in CDP x402 documentation.
Quickstart
Pricing and accepted schemes are returned in the 402 challenge and in GET https://www.stratalize.com/api/x402/<tool> metadata. Call without payment to read current requirements. Do not hard-code amounts; select the requirement that matches your wallet network and asset.
Stratalize x402 tool routes use x402 v2 wire headers: PAYMENT-REQUIRED on the challenge response, PAYMENT-SIGNATURE on the retried POST, and PAYMENT-RESPONSE (or legacy X-PAYMENT-RESPONSE) on the paid 200. Legacy v1 X-PAYMENT is not accepted on these routes (app/api/x402/[...tool]/route.ts).
TypeScript (Node 20+)
Dependencies already used by this repo: @x402/core, @x402/evm, viem. Set BASE_RPC_URL (Base mainnet JSON-RPC) and PRIVATE_KEY (funded payer on Base). Uses atomic-tier example tool get_inflation_benchmark; amount is taken from the challenge, not hard-coded.
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { toClientEvmSigner } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const RESOURCE =
"https://www.stratalize.com/api/x402/get_inflation_benchmark";
async function readPaymentRequired(res: Response): Promise<unknown> {
const header =
res.headers.get("payment-required") ?? res.headers.get("PAYMENT-REQUIRED");
if (header) {
const http = new x402HTTPClient(new x402Client());
return http.getPaymentRequiredResponse((name) => res.headers.get(name), undefined);
}
const copy = res.clone();
const body = await copy.json().catch(() => null);
if (
body &&
typeof body === "object" &&
(body as { x402Version?: unknown }).x402Version === 2 &&
Array.isArray((body as { accepts?: unknown }).accepts)
) {
return body;
}
throw new Error("402 without parseable PAYMENT-REQUIRED / x402 v2 body");
}
async function main() {
const rpcUrl = process.env.BASE_RPC_URL;
const pk = process.env.PRIVATE_KEY;
if (!rpcUrl || !pk) {
throw new Error("Set BASE_RPC_URL and PRIVATE_KEY");
}
const account = privateKeyToAccount(pk as `0x${string}`);
const core = new x402Client();
registerExactEvmScheme(core, {
signer: toClientEvmSigner(account),
networks: ["eip155:8453"],
schemeOptions: { 8453: { rpcUrl } },
});
const http = new x402HTTPClient(core);
const first = await fetch(RESOURCE, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
if (first.status !== 402) {
const t = await first.text();
throw new Error(`expected 402, got ${first.status}: ${t.slice(0, 400)}`);
}
const paymentRequired = await readPaymentRequired(first);
const paymentPayload = await http.createPaymentPayload(paymentRequired as any);
const payHeaders = http.encodePaymentSignatureHeader(paymentPayload);
const second = await fetch(RESOURCE, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
...payHeaders,
},
body: JSON.stringify({}),
});
const json = await second.json();
if (second.status !== 200) {
throw new Error(`tool call failed ${second.status}: ${JSON.stringify(json).slice(0, 500)}`);
}
const settleHeader =
second.headers.get("payment-response") ??
second.headers.get("PAYMENT-RESPONSE") ??
second.headers.get("x-payment-response");
const settled = settleHeader
? http.getPaymentSettleResponse((n) => second.headers.get(n))
: null;
console.log(JSON.stringify({ settleHeaderPresent: Boolean(settleHeader), settled, json }, null, 2));
}
main().catch((e) => {
console.error(e);
process.exit(1);
});curl (unsigned challenge only)
Signing EIP-712 / building PAYMENT-SIGNATURE in shell is impractical; use the TypeScript flow above. curl remains useful to inspect status codes and headers on the unpaid call.
curl -sS -D - \
-X POST "https://www.stratalize.com/api/x402/get_inflation_benchmark" \
-H "accept: application/json" \
-H "content-type: application/json" \
-d '{}' | head -n 40Request flow
On the wire, x402 v2 carries the challenge in the PAYMENT-REQUIRED header (base64-encoded JSON). Stratalize may also mirror requirements into the JSON body when the body would otherwise be empty (lib/x402/middleware.ts).
402 challenge (illustrative JSON)
Field names in live challenges follow x402 v2 (amount in atomic units, CAIP-2 network). Amount and copy are defined by the live challenge, not this document.
HTTP/2 402
content-type: application/json
payment-required: <base64-encoded-json>
{
"x402Version": 2,
"error": "Payment required",
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "<atomic units; per current pricing>",
"resource": "https://www.stratalize.com/api/x402/get_inflation_benchmark",
"description": "<tool description>",
"mimeType": "application/json",
"payTo": "0x5d51ECC50813E0937a5c43be298E9146796ad60d",
"maxTimeoutSeconds": 300,
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"extra": { "name": "USD Coin", "version": "2" },
"extensions": {
"bazaar": {
"discoverable": true,
"category": "finance",
"tags": ["attested", "finance", "inflation-benchmark"],
"inputSchema": { "type": "object", "properties": {} }
}
}
}
]
}200 OK (attested tool payload)
HTTP/2 200
content-type: application/json
payment-response: <base64-settlement-receipt>
{
"data": { "example": "tool-specific fields omitted" },
"_stratalize": {
"signed_at": "2026-04-30T12:34:56.789Z",
"signature": "<base64-ed25519-signature>",
"public_key_url": "https://www.stratalize.com/api/trust/signing-key",
"canonicalization": "JCS-RFC8785",
"version": "1"
}
}Attestation envelope is documented in /docs/attestation.
Settlement
Settlement occurs after successful tool execution and before the HTTP response is returned to the client. Stratalize does not settle when execution fails validation, returns a non-billable degraded path, or errors before a billable response is produced; the client receives an error status and no successful settlement receipt for that attempt.
Settlement is recorded on-chain through the CDP facilitator. The facilitator settlement object is exposed to HTTP clients in PAYMENT-RESPONSE / X-PAYMENT-RESPONSE (decode per @x402/core/http); use the transaction identifier returned there for on-chain reconciliation.
Failure modes
Status and machine-readable error fields may come from Stratalize route handlers, the x402 resource server, or the CDP facilitator verify/settle steps. Retry idempotent GETs for pricing; use exponential backoff on 503/5xx.
| HTTP | Signal | Meaning / action |
|---|---|---|
| 402 | Missing PAYMENT-SIGNATURE | Initial challenge: unpaid POST to a protected x402 route. Read PAYMENT-REQUIRED / body, build v2 PAYMENT-SIGNATURE, retry. |
| 402 | Facilitator verify rejection | Authorization failed CDP verify (signature, nonce, expiry, asset, amount, or network mismatch). Inspect JSON body and CDP invalidReason / invalidMessage when present; re-sign against the latest challenge. |
| 402 | Authorization time window | Authorization validAfter/validBefore outside facilitator rules or maxTimeoutSeconds. Re-fetch challenge; re-sign with a fresh timestamp window. |
| 402 | Insufficient authorization amount | Signed value below required atomic amount for the selected accepts entry. Fetch a new challenge; sign for the full required amount. |
| 402 | Facilitator settle rejection | Verify succeeded but on-chain settle failed or was rejected (success:false from facilitator). Inspect errorReason / errorMessage; do not retry blindly if the debit may be ambiguous—fetch a fresh challenge. |
| 404 | Unknown x402 SLA tool | Path not in the x402 SLA catalog (see /.well-known/x402.json). Tool may be MCP-only or misspelled. Use GET /api/x402/<tool> to confirm availability. |
| 402 / JSON-RPC | x402_required (MCP public surface) | Paid tool called on /api/mcp-public without payment. Use POST /api/x402/<tool> with PAYMENT-SIGNATURE per this page; discovery metadata at GET /api/x402/<tool>. |
| 503 | x402_init_failed | Facilitator client failed to initialize (missing or invalid CDP_API_KEY_ID / CDP_API_KEY_SECRET on the deployment). Transient from caller perspective; retry with backoff. |
| 503 | service_degraded / service degraded | Tool execution could not return billable data (e.g., upstream unavailable) or quality gate failed. No settlement for a failed billable path; read JSON reason and degraded flags. |
| 500 | Server misconfiguration | Internal dependency missing (e.g., signing or persistence). Retry later; no client-side payment fix. |
| 5xx | Other internal errors | Unhandled failure during execution or settlement attachment. Retry with backoff; treat response as untrusted until a successful 200 with attestation. |
Bazaar extensions
Each Stratalize x402 SLA tool registers Bazaar discovery metadata on the resource server. Discovery JSON is attached under extensions.bazaar on payment requirements (see lib/x402/sla-catalog.ts).
Example: finance tool discovery fragment
Category values follow the Stratalize catalog: governance, finance, healthcare, realestate, crypto, intelligence. Tags are indexed for search; live tags vary by tool name.
"extensions": {
"bazaar": {
"discoverable": true,
"category": "finance",
"tags": ["attested", "finance", "inflation-benchmark"],
"inputSchema": { "type": "object", "properties": { /* MCP-shaped */ } }
}
}Every successful JSON tool response includes an Ed25519-signed _stratalize envelope (RFC 8032, RFC 8785 JCS) per /docs/attestation.
SLO commitments
- 99.9% monthly uptime on the x402 paid surface.
- p95 latency under 800ms for atomic-tier tools; p95 under 2000ms for standard-tier tools (server-side processing, excluding client wallet and chain confirmation time).
- Authorization validity bounded by facilitator rules and by
accepts.maxTimeoutSecondsin the challenge (Stratalize SLA routes currently advertise300seconds in route config). - Settlement confirmation within 30 seconds under normal Base mainnet conditions after verify succeeds.
- Status page:
status.stratalize.com(planned; not shipped at time of this document).
Related: MCP (OAuth), Tool catalog, Docs home.