Free-first x402 buyer integration

Connect a wallet-enabled x402 client to Ontario.

Ontario can expose a paid HTTP or MCP tool, return a structured 402, and accept a signed retry. Your client still owns the wallet decision. Run the free checks first, then let a payment-capable x402 client sign locally only when the exact task, quote, network, asset, and budget match policy.

The buyer path that actually reaches payment

1. Discover

Find the tool and its policy

Fetch /.well-known/mcp.json, /.well-known/x402.json, and /.well-known/agent-payment-policy.json.

2. Verify

Use the free gate first

Run readiness and /api/agent/can-pay for the exact target, task, network, asset, and maximum amount.

3. Probe

Inspect the real 402

Call without payment. Require parseable accepts, bounded amount, matching payTo, resource, and expiry.

4. Sign

Use a local wallet bridge

A raw HTTP/MCP client cannot sign. Use an x402 client or MCP bridge with an explicit spend policy.

5. Retry

Send the payment payload

HTTP clients send the x402 payment header. Ontario MCP clients pass the base64 payload in _x402_payment.

6. Audit

Keep the receipt

Store the bounded endpoint, quote, amount, report, network, and PAYMENT-RESPONSE evidence.

Why a raw MCP call stops at 402

That is expected. MCP discovers and invokes tools, but a plain client does not have a wallet scheme registered. Ontario returns the payment requirements instead of guessing or spending. A payment-capable bridge performs the next step: parse the requirements, apply policy, sign locally, and retry. If the bridge is missing, the correct result is a structured 402, not an automatic payment.

The official x402 buyer flow follows the same handshake: the client receives 402 requirements, registers a payment scheme, signs locally, then retries the request. See the official MCP x402 guide and official buyer quickstart.

Free preflight: do this before any wallet code

curl -sS -X POST https://ontarioprotocol.com/api/agent/can-pay \
  -H 'Content-Type: application/json' \
  -d '{
    "endpoint": "https://ontarioprotocol.com/api/agent/pay",
    "agent_policy": "strict",
    "max_usdc": "0.002",
    "verify_live": true
  }'

Continue only when decision is allow. A free result is already enough when the task is browsing, discovery, readiness, or sandbox rehearsal. Payment is for the incremental task-specific answer only.

HTTP buyer example with a local x402 wallet client

This example uses the official x402 client packages. It is intentionally blocked unless the operator explicitly sets ALLOW_X402_PAYMENT=1. The key stays in the local process and is never sent to Ontario.

npm init -y
npm install @x402/fetch @x402/evm viem

ALLOW_X402_PAYMENT=1 \
EVM_PRIVATE_KEY='0xREPLACE_WITH_A_DEDICATED_LIMITED_WALLET_KEY' \
node --input-type=module <<'NODE'
import { x402Client, wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const base = "https://ontarioprotocol.com";
const endpoint = `${base}/api/agent/pay`;
const body = {
  query: "What evidence should an agent inspect before paying an x402 endpoint?",
  endpoint: `${base}/api/x402/agent-trust-scan`,
  max_usdc: "0.002",
  agent_policy: "strict"
};

if (process.env.ALLOW_X402_PAYMENT !== "1") {
  throw new Error("Refusing to sign: set ALLOW_X402_PAYMENT=1 after reviewing the quote.");
}
if (!process.env.EVM_PRIVATE_KEY || process.env.EVM_PRIVATE_KEY.includes("REPLACE_")) {
  throw new Error("Set EVM_PRIVATE_KEY only in the local wallet runtime; never commit or send it.");
}

const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY);
const client = new x402Client();
registerExactEvmScheme(client, { signer });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const response = await fetchWithPayment(endpoint, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(body)
});
const payload = await response.json();
const payment = new x402HTTPClient(client)
  .getPaymentSettleResponse((name) => response.headers.get(name));
console.log(JSON.stringify({ http_status: response.status, payload, payment }, null, 2));
NODE

This is an integration example, not a request for Ontario's wallet credentials. Review the live 402, verify the amount and resource, and use a wallet with a strict limit. Ontario does not run this payment as part of the guide.

MCP client handoff

For the Ontario MCP transport, call tools/list, then call the paid tool once without payment. The structured result contains the x402 requirements. A payment-aware MCP client or bridge should then call the same tool again with _x402_payment set to the base64 x402 PaymentPayload.

POST https://ontarioprotocol.com/mcp
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"agent_pay_query","arguments":{"query":"..."}}}

POST https://ontarioprotocol.com/mcp
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"agent_pay_query","arguments":{"query":"...","_x402_payment":"BASE64_PAYMENT_PAYLOAD"}}}

Stop conditions

  • can-pay returns review or deny.
  • The quote exceeds the wallet or task budget.
  • Network, asset, payTo, resource, or amount does not match policy.
  • The challenge is missing accepts, expiry, or a usable resource.
  • The free verifier, report, catalog, or sandbox already answers the task.
  • The task involves real-world goods, irreversible actions, or unclear user authorization.

What Ontario can and cannot prove

Ontario provides readiness and trust signals from observable endpoint behavior, manifest metadata, payment challenge behavior, report history, and public evidence. It does not guarantee endpoint safety, service quality, financial outcome, or future behavior. Agents should preserve their own policy decision and payment evidence.