x402 Documentation
How to call Ontario Protocol's paid endpoints from any agent that speaks HTTP. USDC on Base. No API keys.
New: x402 endpoint readiness quickstart
(generate a public report_id before asking agents to pay) ·
agent payment preflight guide
(apply /api/agent/can-pay policy before signing) ·
embed a readiness badge
(link badge → signed report; verify integrity + freshness) ·
list an x402 service
(validate payload, rehearse in sandbox, then submit paid listing) ·
x402 verification tools comparison
(which checks an agent should run before payment) ·
agent decision playbooks
(100 Search Console-informed pre-payment workflows) ·
Search Console indexing playbook
(core sitemap, evidence sitemap, and 404 triage).
Overview
Every paid endpoint on Ontario follows the x402 protocol:
- Client makes a normal request, no payment header.
- Server replies
HTTP 402with aPAYMENT-REQUIREDheader (base64 JSON). - Client signs a payment payload (EIP-3009 transferWithAuthorization for USDC) and resends with
PAYMENT-SIGNATUREheader. - Server forwards to a facilitator (Coinbase by default) for verify + settle, then returns
HTTP 200+PAYMENT-RESPONSEheader.
The flow, in pictures
Client Ontario Coinbase Facilitator | POST /api/x402/... | | |------------------------->| | | 402 PAYMENT-REQUIRED | | |<-------------------------| | | build + sign | | | PaymentPayload | | | POST + PAYMENT-SIGNATURE| | |------------------------->| POST /verify | | |----------------------------->| | | isValid: true | | |<-----------------------------| | | POST /settle | | |----------------------------->| | | success: true, tx: 0x... | | |<-----------------------------| | 200 + PAYMENT-RESPONSE | | |<-------------------------| |
curl
# 1. Probe — get the 402 with payment requirements curl -i -X POST https://ontarioprotocol.com/api/x402/agent-trust-scan \ -H "Content-Type: application/json" \ -d '{"agent_id":"my-agent","target_url":"https://example.com"}' # 2. Decode the PAYMENT-REQUIRED header (base64 → JSON): echo "$PAYMENT_REQUIRED_HEADER" | base64 -d | jq # 3. Sign the payload with your wallet (using x402 client SDK, see Python below) # 4. Resubmit with PAYMENT-SIGNATURE header: curl -X POST https://ontarioprotocol.com/api/x402/agent-trust-scan \ -H "Content-Type: application/json" \ -H "PAYMENT-SIGNATURE: $SIGNED_PAYLOAD_B64" \ -d '{"agent_id":"my-agent","target_url":"https://example.com"}'
Python
# pip install x402 web3 eth-account requests # Never paste real keys into source files. Use an env var and a dedicated low-balance wallet. from x402.client import X402Client from eth_account import Account import os acct = Account.from_key(os.environ["X402_PRIVATE_KEY"]) client = X402Client(account=acct, default_network="base") resp = client.post( "https://ontarioprotocol.com/api/x402/agent-trust-scan", json={ "agent_id": "my-agent", "target_url": "https://example.com", }, max_payment_atomic=10_000, # 0.01 USDC, prevents over-pay ) print(resp.json()["report"]["trust_score"])
TypeScript
// npm install x402 viem // Never hardcode keys. Use an env var (Node) and a dedicated low-balance wallet. import { createX402Client } from "x402"; import { privateKeyToAccount } from "viem/accounts"; const privateKey = process.env.X402_PRIVATE_KEY; if (!privateKey) throw new Error("Missing X402_PRIVATE_KEY"); const account = privateKeyToAccount(privateKey); const client = createX402Client({ account, defaultNetwork: "base" }); const res = await client.fetch( "https://ontarioprotocol.com/api/x402/agent-trust-scan", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ agent_id: "my-agent", target_url: "https://example.com", }), maxPaymentAtomic: 10_000n, } ); const data = await res.json();
Endpoints
| Endpoint | Method | Price | What you get |
|---|---|---|---|
/api/x402/agent-trust-scan |
POST | 0.01 USDC | Submit an agent's card URL or A2A endpoint, receive a structured trust report (security signals, identity claims, declared capabilities). Returns JSON. |
/api/x402/reputation/<agent_id> |
GET | 0.001 USDC | Look up an agent's accumulated reputation (sourced from on-chain EAS attestations on Base + recent scan history). |
/api/x402/list-agent |
POST | 0.10 USDC | Submit an AI agent for inclusion in the Ontario Protocol directory. Pays 0.10 USDC up front to deter spam. |
/api/x402/list-service |
POST | 0.50 USDC | Register your own x402-paid endpoint so AI agents discover it through Ontario Protocol's `/discover`. 0.50 USDC listing fee. |
/discover |
GET | Free | Machine-readable catalog of all services. |
/.well-known/x402.json |
GET | Free | Standardised manifest for agent auto-discovery. |
/facilitator/supported |
GET | Free | Lists (scheme, network) pairs the proxy supports. |
/facilitator/verify |
POST | Free | Verifies a signed payment off-chain via Coinbase facilitator. |
/facilitator/settle |
POST | Free* | Submits payment on-chain. Logs split + take rate to ledger. |
* "Free" meaning no fee to call the endpoint. Settlement transactions themselves cost gas + the marketplace's 1.5% take rate when applicable.
Marketplace: list a third-party service
POST /api/x402/list-service is a paid marketplace endpoint (0.50 USDC) used to register
a third-party x402-paid API so agents can find it through Ontario’s discovery surfaces.
On submission, Ontario runs the free readiness verifier against your endpoint and stores the resulting report.
- Immediately: your submission appears in
/listingsasPENDINGorREADY. - Only when ready: it enters
/discover(default filters) and/.well-known/x402.jsonwhen its stored report gradesready. - Trust boundary: readiness signals are not a safety guarantee; agents should still preflight and enforce budgets.
Request body
{
"name": "My Cool API",
"description": "What it does",
"category": "data",
"endpoint": "https://example.com/api/data",
"method": "GET",
"price_atomic": 10000,
"price_usdc": "0.01",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"network": "base",
"owner_url": "https://example.com",
"owner_contact": "ops@example.com",
"tags": ["dataset", "ai-agent", "x402"]
}
Tip: run /verify first and submit only after you reach grade=ready with a fresh report.
Before you pay the listing fee, you can validate your payload shape for free:
POST /api/x402/list-service/validate (no payment; does not store a listing).
Sandbox rehearsal (no on-chain rails; does not store a listing):
POST /sandbox/api/x402/list-service. It behaves like a paid x402 endpoint (HTTP 402 first, then accept a simulated payment payload),
so you can test your x402 client integration without spending money.
Machine-readable schema:
/.well-known/x402-list-service.schema.json
curl -sS https://ontarioprotocol.com/api/x402/list-service/validate \\
-H "Content-Type: application/json" \\
-d '{"name":"Example Service","description":"Paid endpoint","endpoint":"https://example.com/api/paid","method":"POST","price_usdc":"0.01","network":"base","owner_url":"https://example.com"}'
SDK examples: ontario_list_service.py ·
ontario-list-service.ts
Response body
The response includes the settlement context under payment and the stored listing under listing
(including verification_status, verification, and paid_listing_tx).
Directory: list an agent (optional)
POST /api/x402/list-agent (0.10 USDC) registers an agent card URL for the directory.
It uses the same auto-verification behavior (a readiness report is attempted and attached if available).
Validate your agent submission payload for free with POST /api/x402/list-agent/validate.
Machine-readable schema:
/.well-known/x402-list-agent.schema.json
curl -sS https://ontarioprotocol.com/api/x402/list-agent/validate \\
-H "Content-Type: application/json" \\
-d '{"name":"Example Agent","description":"Agent card","agent_card_url":"https://example.com/agent.json","owner_url":"https://example.com"}'
Errors
| Status | Meaning |
|---|---|
402 | Payment required. PAYMENT-REQUIRED header tells you the amount + asset. |
402 (after retry) | Payment was rejected — see error + stage in the JSON body. |
400 | Could not decode PAYMENT-SIGNATURE header. |
410 | Old SaaS URL. Use /discover instead. |
Facilitator
By default Ontario delegates verification + settlement to Coinbase's
hosted facilitator (limits and rate policies may apply — confirm against Coinbase docs).
With X402_USE_PROXY=1 in the server's .env,
requests pass through Ontario's proxy_facilitator, which
logs every settlement to a public, append-only SQLite ledger and applies
the 1.5% take rate on third-party listings.
See /treasury for the live, public ledger
summary or call /api/treasury/ledger
directly.
Cookbook: production-tested pay-and-scan script
The following is a production-style reference script for paying an x402 endpoint and then running an Ontario scan to record evidence. Download it, set env vars, and run:
# Install deps pip install x402 requests pyjwt[crypto] cryptography python-dotenv # Set env (or add to .env) export CDP_API_KEY="your-cdp-key-id" export CDP_API_SECRET="base64-ed25519-secret" export BUYER_WALLET_SECRET="base64-ec-p256-der-private" export BUYER_WALLET_ADDRESS="0x…your-wallet" # Run python pay_and_scan.py
⬇ Download pay_and_scan.py — The complete reference implementation (~210 lines).
Common pitfalls
⚠ EIP-712 domain name must be "USD Coin"
The Base mainnet USDC contract at
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
uses domain.name = "USD Coin" and version = "2"
for EIP-712 typed data. If your signature uses "USDC"
or version "1", the Coinbase facilitator will reject
with "invalid signature".
⚠ x402 Python SDK nonce is bytes, not hex
prepare_payment_header() may return the nonce
field as raw bytes. The CDP facilitator requires a hex
string. Cast it before signing:
if isinstance(nonce, bytes): nonce = nonce.hex()
⚠ x402Version field required in outer body
When calling the CDP facilitator directly, include
"x402Version": 1 in the top-level JSON body.
Omitting it produces a 400.